diff --git a/.github/actions/install-pypi/action.yml b/.github/actions/install-pypi/action.yml index 54cd9fe4b05..cc687e8dd39 100644 --- a/.github/actions/install-pypi/action.yml +++ b/.github/actions/install-pypi/action.yml @@ -15,6 +15,10 @@ inputs: python-version: description: 'What version of Python to use' required: true + old-build: + description: 'Whether to enable old builds for shapely and cartopy' + required: false + default: 'false' runs: using: composite @@ -30,13 +34,13 @@ runs: ci/extra_requirements.txt # This installs the stuff needed to build and install Shapely and CartoPy from source. - - name: Install CartoPy build dependencies - if: ${{ inputs.need-extras == 'true' }} + - name: Install CartoPy/Shapely build dependencies + if: ${{ inputs.old-build == 'true' }} shell: bash run: sudo apt-get install libgeos-dev - name: Disable Shapely Wheels - if: ${{ inputs.need-extras == 'true' }} + if: ${{ inputs.old-build == 'true' }} shell: bash run: echo "PIP_NO_BINARY=shapely" >> $GITHUB_ENV @@ -57,6 +61,7 @@ runs: -c ci/${{ inputs.version-file }} -c ci/${{ inputs.type }}_requirements.txt -c ci/extra_requirements.txt - name: Install additional test tools + if: ${{ inputs.type == 'test' }} shell: bash run: > python -m pip install coverage diff --git a/.github/workflows/docs-conda.yml b/.github/workflows/docs-conda.yml index 2cfe2dff471..469245dc9e4 100644 --- a/.github/workflows/docs-conda.yml +++ b/.github/workflows/docs-conda.yml @@ -28,6 +28,8 @@ jobs: os: Windows - python-version: 3.11 os: macOS + - python-version: 3.12 + os: macOS steps: - name: Checkout source diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a9a95b0ccfe..946e8925f3c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,21 +22,16 @@ jobs: # Build our docs on Linux against multiple Pythons # Docs: - name: ${{ matrix.python-version }} ${{ matrix.dep-versions }} + name: "Linux ${{ matrix.python-version }}" runs-on: ubuntu-latest strategy: fail-fast: false matrix: + python-version: [3.9, '3.10', 3.11] + check-links: [false] include: - - python-version: 3.9 - check-links: false - dep-versions: requirements.txt - - python-version: '3.10' - check-links: false - dep-versions: requirements.txt - - python-version: 3.11 + - python-version: 3.12 check-links: true - dep-versions: requirements.txt outputs: doc-version: ${{ steps.build-docs.outputs.doc-version }} @@ -52,6 +47,7 @@ jobs: with: type: 'doc' python-version: ${{ matrix.python-version }} + need-extras: true - name: Build docs id: build-docs diff --git a/.github/workflows/nightly-builds.yml b/.github/workflows/nightly-builds.yml index ec0fa569a2d..7112dca7098 100644 --- a/.github/workflows/nightly-builds.yml +++ b/.github/workflows/nightly-builds.yml @@ -14,9 +14,11 @@ on: - main paths: - .github/workflows/nightly-builds.yml + - .github/workflows/unstable-builds.yml pull_request: paths: - .github/workflows/nightly-builds.yml + - .github/workflows/unstable-builds.yml jobs: Builds: diff --git a/.github/workflows/tests-conda.yml b/.github/workflows/tests-conda.yml index 781521a0a15..bf6d1e50098 100644 --- a/.github/workflows/tests-conda.yml +++ b/.github/workflows/tests-conda.yml @@ -28,8 +28,13 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, '3.10', 3.11] + python-version: [3.9, 3.12] os: [macOS, Windows] + include: + - python-version: '3.10' + os: macOS + - python-version: 3.11 + os: Windows steps: - name: Checkout source @@ -66,3 +71,4 @@ jobs: uses: codecov/codecov-action@v3 with: name: ${{ github.workflow }} + fail_ci_if_error: true diff --git a/.github/workflows/tests-pypi.yml b/.github/workflows/tests-pypi.yml index fb9ca741eff..d9c5dab440d 100644 --- a/.github/workflows/tests-pypi.yml +++ b/.github/workflows/tests-pypi.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, '3.10', 3.11] + python-version: [3.9, '3.10', 3.11, 3.12] dep-versions: [Latest] no-extras: [''] include: @@ -34,7 +34,8 @@ jobs: - python-version: 3.9 dep-versions: Minimum no-extras: 'No Extras' - - python-version: 3.11 + - python-version: 3.12 + dep-versions: Latest no-extras: 'No Extras' steps: @@ -71,6 +72,7 @@ jobs: need-extras: ${{ matrix.no-extras != 'No Extras' }} type: 'test' python-version: ${{ matrix.python-version }} + old-build: ${{ matrix.no-extras != 'No Extras' && matrix.dep-versions == 'Minimum' }} - name: Run tests uses: ./.github/actions/run-tests @@ -94,3 +96,4 @@ jobs: uses: codecov/codecov-action@v3 with: name: ${{ github.workflow }} + fail_ci_if_error: true diff --git a/.github/workflows/unstable-builds.yml b/.github/workflows/unstable-builds.yml index c10e65da733..ef67567e6c5 100644 --- a/.github/workflows/unstable-builds.yml +++ b/.github/workflows/unstable-builds.yml @@ -33,7 +33,7 @@ jobs: need-extras: true type: test version-file: Prerelease - python-version: 3.11 + python-version: 3.12 - name: Run tests id: tests @@ -72,7 +72,7 @@ jobs: with: type: doc version-file: Prerelease - python-version: 3.11 + python-version: 3.12 - name: Build docs id: build diff --git a/docs/api/references.rst b/docs/api/references.rst index e4df24baa3e..c99e3d7d753 100644 --- a/docs/api/references.rst +++ b/docs/api/references.rst @@ -198,11 +198,10 @@ References .. [WMO8] WMO, 2020: Guide to Meteorological Instruments and Methods of Observation, Volume 1: Measurement of Meteorological Variables. - `WMO No.8 `_. + `WMO No.8 `_. .. [WMO306] WMO, 2011: Manual on Codes - International Codes, Volume I.1, Annex II to the WMO - Technical Regulations: Part A - Alphanumeric Codes. `WMO No.306 - `_. + Technical Regulations: Part A - Alphanumeric Codes. `WMO No.306 `_. -.. [WMO1966] WMO, 1966: International Meteorological Tables, `WMO-No. 188.TP.94 - `_. +.. [WMO1966] WMO, 1966: International Meteorological Tables, + `WMO-No. 188.TP.94 `_. diff --git a/docs/conf.py b/docs/conf.py index e8de320fa84..87493d09c4d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,8 +61,9 @@ 'reset_modules': [lambda conf, fname: sys.modules.pop('pint', None)] } -# By default, only generate all the areas when running in CI -metpy_generate_all_areas = 'GITHUB_ACTIONS' in os.environ +# By default, only generate all the areas when running on a release CI job +metpy_generate_all_areas = (not os.environ.get('GITHUB_REF', '').startswith('refs/pull') + and sys.version_info < (3, 12)) # Turn off code and image links for embedded mpl plots plot_html_show_source_link = False @@ -431,7 +432,9 @@ r'https://doi\.org/10\.1289/ehp\.1206273', # Couldn't fix these 403's with user agents r'https://doi\.org/10\.1029/2010GL045777', - r'https://doi\.org/10\.1098/rspa\.2004\.1430' + r'https://doi\.org/10\.1098/rspa\.2004\.1430', + # Currently giving certificate errors on GitHub + r'https://library.wmo.int/.*' ] # Dictionary of URL redirects allowed @@ -440,7 +443,8 @@ r'https://conda.io/docs/': r'https://conda.io/en/latest/', r'https://github.com/Unidata/MetPy/issues/new/choose': r'https://github.com/login.*choose', r'https://doi.org/.*': r'https://.*', - r'https://gitter.im/Unidata/MetPy': r'https://app.gitter.im/.*MetPy.*' + r'https://gitter.im/Unidata/MetPy': r'https://app.gitter.im/.*MetPy.*', + r'https://library.wmo.int/idurl/.*': r'https://library.wmo.int/.*' } # Domain-specific HTTP headers for requests diff --git a/docs/make_areas.py b/docs/make_areas.py index 5d565409b24..3df13db1921 100644 --- a/docs/make_areas.py +++ b/docs/make_areas.py @@ -52,6 +52,7 @@ def generate_area_file(app): if area in states_provinces: code = textwrap.dedent(f""" .. plot:: + :context: reset import matplotlib.pyplot as plt import cartopy.crs as ccrs @@ -75,6 +76,7 @@ def generate_area_file(app): else: code = textwrap.dedent(f""" .. plot:: + :context: reset import matplotlib.pyplot as plt import cartopy.crs as ccrs diff --git a/pyproject.toml b/pyproject.toml index cfae651fb52..d7c33a83700 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Atmospheric Science", "Intended Audience :: Science/Research", diff --git a/src/metpy/io/_tools.py b/src/metpy/io/_tools.py index ef2e5ee858f..174566cc615 100644 --- a/src/metpy/io/_tools.py +++ b/src/metpy/io/_tools.py @@ -57,7 +57,7 @@ def open_as_needed(filename, mode='rb'): return open(filename, mode, **kwargs) # noqa: SIM115 -class NamedStruct(Struct): +class NamedStruct: """Parse bytes using :class:`Struct` but provide named fields.""" def __init__(self, info, prefmt='', tuple_name=None): @@ -73,7 +73,12 @@ def __init__(self, info, prefmt='', tuple_name=None): elif not i[0]: # Skip items with no name conv_off += 1 self._tuple = namedtuple(tuple_name, ' '.join(n for n in names if n)) - super().__init__(prefmt + ''.join(f for f in fmts if f)) + self._struct = Struct(prefmt + ''.join(f for f in fmts if f)) + + @property + def size(self): + """Return the size of the struct in bytes.""" + return self._struct.size def _create(self, items): if self.converters: @@ -90,11 +95,11 @@ def make_tuple(self, *args, **kwargs): def unpack(self, s): """Parse bytes and return a namedtuple.""" - return self._create(super().unpack(s)) + return self._create(self._struct.unpack(s)) def unpack_from(self, buff, offset=0): """Read bytes from a buffer and return as a namedtuple.""" - return self._create(super().unpack_from(buff, offset)) + return self._create(self._struct.unpack_from(buff, offset)) def unpack_file(self, fobj): """Unpack the next bytes from a file object.""" @@ -103,12 +108,12 @@ def unpack_file(self, fobj): def pack(self, **kwargs): """Pack the arguments into bytes using the structure.""" t = self.make_tuple(**kwargs) - return super().pack(*t) + return self._struct.pack(*t) # This works around times when we have more than 255 items and can't use # NamedStruct. This is a CPython limit for arguments. -class DictStruct(Struct): +class DictStruct: """Parse bytes using :class:`Struct` but provide named fields using dictionary access.""" def __init__(self, info, prefmt=''): @@ -118,18 +123,23 @@ def __init__(self, info, prefmt=''): # Remove empty names self._names = [n for n in names if n] - super().__init__(prefmt + ''.join(f for f in formats if f)) + self._struct = Struct(prefmt + ''.join(f for f in formats if f)) + + @property + def size(self): + """Return the size of the struct in bytes.""" + return self._struct.size def _create(self, items): return dict(zip(self._names, items)) def unpack(self, s): """Parse bytes and return a dict.""" - return self._create(super().unpack(s)) + return self._create(self._struct.unpack(s)) def unpack_from(self, buff, offset=0): """Unpack the next bytes from a file object.""" - return self._create(super().unpack_from(buff, offset)) + return self._create(self._struct.unpack_from(buff, offset)) class Enum: