diff --git a/.github/actions/install-pypi/action.yml b/.github/actions/install-pypi/action.yml index 933444ce526..54cd9fe4b05 100644 --- a/.github/actions/install-pypi/action.yml +++ b/.github/actions/install-pypi/action.yml @@ -1,16 +1,16 @@ name: 'Install Using PyPI' description: 'Setup environment and install using the PyPI-based toolchain' inputs: - need-cartopy: - description: 'Whether Cartopy is needed' - required: true - default: 'true' + need-extras: + description: 'Whether to install the extras' + required: false + default: 'false' type: description: 'Whether test or doc build' required: true version-file: description: 'Name of the version file to use for installation' - required: true + required: false default: 'requirements.txt' python-version: description: 'What version of Python to use' @@ -27,31 +27,43 @@ runs: cache-dependency-path: | ci/${{ inputs.type }}_requirements.txt ci/${{ inputs.version-file }} + 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-cartopy == 'true' }} + if: ${{ inputs.need-extras == 'true' }} shell: bash run: sudo apt-get install libgeos-dev - name: Disable Shapely Wheels + if: ${{ inputs.need-extras == 'true' }} shell: bash run: echo "PIP_NO_BINARY=shapely" >> $GITHUB_ENV - - name: Install dependencies + - name: Set dependency groups for install shell: bash - run: python -m pip install -r ci/${{ inputs.type }}_requirements.txt -c ci/${{ inputs.version-file }} + run: | + if [[ ${{ inputs.need-extras }} == 'true' ]] + then + echo "DEP_GROUPS=${{ inputs.type }},extras" >> $GITHUB_ENV + else + echo "DEP_GROUPS=${{ inputs.type }}" >> $GITHUB_ENV + fi - - name: Install extra dependencies - if: ${{ inputs.need-cartopy == 'true' }} + - name: Install shell: bash - run: python -m pip install -r ci/extra_requirements.txt -c ci/${{ inputs.version-file }} + run: > + python -m pip install .[${{ env.DEP_GROUPS }}] + -c ci/${{ inputs.version-file }} -c ci/${{ inputs.type }}_requirements.txt -c ci/extra_requirements.txt + + - name: Install additional test tools + shell: bash + run: > + python -m pip install coverage + -c ci/${{ inputs.version-file }} -c ci/${{ inputs.type }}_requirements.txt -c ci/extra_requirements.txt - name: Download Cartopy Maps - if: ${{ inputs.need-cartopy == 'true' }} + if: ${{ inputs.need-extras == 'true' }} shell: bash run: ci/download_cartopy_maps.py - - name: Install - shell: bash - run: python -m pip install -c ci/${{ inputs.version-file }} . diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1f1f431d208..a9a95b0ccfe 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,7 +50,6 @@ jobs: - name: Install using PyPI uses: ./.github/actions/install-pypi with: - need-cartopy: true type: 'doc' python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/tests-pypi.yml b/.github/workflows/tests-pypi.yml index e7a34129182..fb9ca741eff 100644 --- a/.github/workflows/tests-pypi.yml +++ b/.github/workflows/tests-pypi.yml @@ -26,14 +26,15 @@ jobs: fail-fast: false matrix: python-version: [3.9, '3.10', 3.11] - dep-versions: [requirements.txt] + dep-versions: [Latest] no-extras: [''] include: + - python-version: 3.9 + dep-versions: Minimum - python-version: 3.9 dep-versions: Minimum no-extras: 'No Extras' - python-version: 3.11 - dep-versions: requirements.txt no-extras: 'No Extras' steps: @@ -52,12 +53,14 @@ jobs: from pathlib import Path # Read our pyproject.toml - config = tomllib.load(open('pyproject.toml', 'rb')) + config = tomllib.load(open('pyproject.toml', 'rb'))['project'] + opt_deps = config['optional-dependencies'] # Generate a Minimum dependency file - with (Path('ci') / 'Minimum').open('wt') as out: - for dep in config['project']['dependencies']: - if dep: + for src, fname in [(config['dependencies'], 'requirements.txt'), + (opt_deps['test'], 'test_requirements.txt'), (opt_deps['extras'], 'extra_requirements.txt')]: + with (Path('ci') / fname).open('wt') as out: + for dep in src: dep = dep.split(';')[0] out.write(dep.replace('>=', '==') + '\n') EOF @@ -65,15 +68,14 @@ jobs: - name: Install from PyPI uses: ./.github/actions/install-pypi with: - need-cartopy: ${{ matrix.no-extras != 'No Extras' }} + need-extras: ${{ matrix.no-extras != 'No Extras' }} type: 'test' - version-file: ${{ matrix.dep-versions }} python-version: ${{ matrix.python-version }} - name: Run tests uses: ./.github/actions/run-tests with: - run-doctests: ${{ matrix.dep-versions == 'requirements.txt' && matrix.no-extras != 'No Extras' }} + run-doctests: ${{ matrix.dep-versions == 'Latest' && matrix.no-extras != 'No Extras' }} key: pypi-${{ matrix.python-version }}-${{ matrix.dep-versions }}-${{ matrix.no-extras }}-${{ runner.os }} codecov: diff --git a/.github/workflows/unstable-builds.yml b/.github/workflows/unstable-builds.yml index f275ae51b85..c10e65da733 100644 --- a/.github/workflows/unstable-builds.yml +++ b/.github/workflows/unstable-builds.yml @@ -30,7 +30,7 @@ jobs: - name: Install using PyPI uses: ./.github/actions/install-pypi with: - need-cartopy: true + need-extras: true type: test version-file: Prerelease python-version: 3.11 @@ -70,7 +70,6 @@ jobs: - name: Install using PyPI uses: ./.github/actions/install-pypi with: - need-cartopy: true type: doc version-file: Prerelease python-version: 3.11 diff --git a/ci/extra_requirements.txt b/ci/extra_requirements.txt index 59a88b74a77..31ba3f6dfaa 100644 --- a/ci/extra_requirements.txt +++ b/ci/extra_requirements.txt @@ -1,2 +1,3 @@ cartopy==0.22.0 +dask==2023.10.0 shapely==2.0.2 diff --git a/ci/requirements.txt b/ci/requirements.txt index 9e91f497089..f0bbe4f1a6e 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -1,4 +1,4 @@ -matplotlib==3.7.2 +matplotlib==3.8.0 numpy==1.26.0 pandas==2.1.1 pooch==1.7.0 diff --git a/ci/test_requirements.txt b/ci/test_requirements.txt index 8b7f2da0b6d..9c18f1ce1a9 100644 --- a/ci/test_requirements.txt +++ b/ci/test_requirements.txt @@ -3,4 +3,3 @@ pytest==7.4.2 pytest-mpl==0.16.1 netCDF4==1.6.4 coverage==7.3.2 -dask==2023.10.0 diff --git a/pyproject.toml b/pyproject.toml index 744932f9bf8..f6b032aaee1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ ] requires-python = ">=3.9" dependencies = [ - "matplotlib>=3.3.0", + "matplotlib>=3.5.0", "numpy>=1.20.0", "pandas>=1.2.0", "pint>=0.15", @@ -41,24 +41,28 @@ gini = "metpy.io.gini:GiniXarrayBackend" [project.optional-dependencies] doc = [ + "metpy[examples]", + "myst-parser", + "netCDF4", "sphinx", "sphinx-gallery>=0.4", - "myst-parser", - "netCDF4" + "sphinx-design", + "pydata-sphinx-theme" ] examples = [ - "cartopy>=0.17.0", "geopandas>=0.6.0", - "matplotlib>=3.3.0", - "shapely>=1.6.0" + "metpy[extras]" ] test = [ - "packaging>=21.0", - "pytest>=2.4", - "pytest-mpl", - "cartopy>=0.17.0", "netCDF4", - "shapely>=1.6.0" + "packaging>=21.0", + "pytest>=6.2", + "pytest-mpl" +] +extras = [ + "cartopy>=0.21.0", + "dask>=2020.12.0", + "shapely>=1.6.4" ] [project.urls] diff --git a/src/metpy/testing.py b/src/metpy/testing.py index 2cca9f731a7..da9416ad216 100644 --- a/src/metpy/testing.py +++ b/src/metpy/testing.py @@ -10,8 +10,10 @@ import contextlib import functools +import matplotlib import numpy as np import numpy.testing +from packaging.version import Version from pint import DimensionalityError import pytest import xarray as xr @@ -21,6 +23,38 @@ from .deprecation import MetpyDeprecationWarning from .units import units +MPL_VERSION = Version(matplotlib.__version__) + + +def mpl_version_before(ver): + """Return whether the active matplotlib is before a certain version. + + Parameters + ---------- + ver : str + The version string for a certain release + + Returns + ------- + bool : whether the current version was released before the passed in one + """ + return MPL_VERSION < Version(ver) + + +def mpl_version_equal(ver): + """Return whether the active matplotlib is equal to a certain version. + + Parameters + ---------- + ver : str + The version string for a certain release + + Returns + ------- + bool : whether the current version is equal to the passed in one + """ + return MPL_VERSION == Version(ver) + def needs_module(module): """Decorate a test function or fixture as requiring a module. diff --git a/tests/plots/baseline/test_declarative_contour_convert_units.png b/tests/plots/baseline/test_declarative_contour_convert_units.png index 1f1112a3adb..9674f0bc63d 100644 Binary files a/tests/plots/baseline/test_declarative_contour_convert_units.png and b/tests/plots/baseline/test_declarative_contour_convert_units.png differ diff --git a/tests/plots/baseline/test_declarative_contour_options.png b/tests/plots/baseline/test_declarative_contour_options.png index d98911f174e..9c819ce0f67 100644 Binary files a/tests/plots/baseline/test_declarative_contour_options.png and b/tests/plots/baseline/test_declarative_contour_options.png differ diff --git a/tests/plots/test_declarative.py b/tests/plots/test_declarative.py index 8e15c694497..aa4e871cd45 100644 --- a/tests/plots/test_declarative.py +++ b/tests/plots/test_declarative.py @@ -20,7 +20,7 @@ from metpy.io.metar import parse_metar_file from metpy.plots import (ArrowPlot, BarbPlot, ContourPlot, FilledContourPlot, ImagePlot, MapPanel, PanelContainer, PlotGeometry, PlotObs, RasterPlot) -from metpy.testing import needs_cartopy +from metpy.testing import mpl_version_before, needs_cartopy from metpy.units import units @@ -150,7 +150,7 @@ def test_declarative_titles(): return pc.figure -@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.069) +@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.072) @needs_cartopy def test_declarative_smooth_contour(): """Test making a contour plot using smooth_contour.""" @@ -179,7 +179,7 @@ def test_declarative_smooth_contour(): return pc.figure -@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.09) +@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.12) @needs_cartopy def test_declarative_smooth_contour_calculation(): """Test making a contour plot using smooth_contour.""" @@ -334,7 +334,8 @@ def test_declarative_contour_cam(): return pc.figure -@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.036) +@pytest.mark.mpl_image_compare(remove_text=True, + tolerance=3.71 if mpl_version_before('3.8') else 0.026) @needs_cartopy def test_declarative_contour_options(): """Test making a contour plot.""" @@ -394,7 +395,8 @@ def test_declarative_layers_plot_options(): return pc.figure -@pytest.mark.mpl_image_compare(remove_text=True, tolerance=0.025) +@pytest.mark.mpl_image_compare(remove_text=True, + tolerance=2.74 if mpl_version_before('3.8') else 0.014) @needs_cartopy def test_declarative_contour_convert_units(): """Test making a contour plot.""" diff --git a/tests/plots/test_skewt.py b/tests/plots/test_skewt.py index 1fa5cad4bd6..ab5d5c14933 100644 --- a/tests/plots/test_skewt.py +++ b/tests/plots/test_skewt.py @@ -11,10 +11,9 @@ import pytest from metpy.plots import Hodograph, SkewT +from metpy.testing import mpl_version_before, mpl_version_equal from metpy.units import units -MPL_VERSION = matplotlib.__version__[:5] - @pytest.mark.mpl_image_compare(remove_text=True, style='default', tolerance=0.069) def test_skewt_api(): @@ -86,7 +85,7 @@ def test_skewt_default_aspect_empty(): @pytest.mark.mpl_image_compare(tolerance=0., remove_text=True, style='default') def test_skewt_mixing_line_args(): """Test plot_mixing_lines accepting kwargs for mixing ratio and pressure levels.""" - # Explicitly pass default values as kwargs the, should recreate NWS SkewT PDF as above + # Explicitly pass default values as kwargs, should recreate NWS SkewT PDF as above fig = plt.figure(figsize=(12, 9)) skew = SkewT(fig, rotation=43) mlines = np.array([0.0004, 0.001, 0.002, 0.004, 0.007, 0.01, 0.016, 0.024, 0.032]) @@ -156,8 +155,8 @@ def test_skewt_units(): skew.ax.axvline(-10, color='orange') # On Matplotlib <= 3.6, ax[hv]line() doesn't trigger unit labels - assert skew.ax.get_xlabel() == ('degree_Celsius' if MPL_VERSION == '3.7.0' else '') - assert skew.ax.get_ylabel() == ('hectopascal' if MPL_VERSION == '3.7.0' else '') + assert skew.ax.get_xlabel() == ('degree_Celsius' if mpl_version_equal('3.7.0') else '') + assert skew.ax.get_ylabel() == ('hectopascal' if mpl_version_equal('3.7.0') else '') # Clear them for the image test skew.ax.set_xlabel('') @@ -320,7 +319,7 @@ def test_hodograph_api(): @pytest.mark.mpl_image_compare(remove_text=True, - tolerance=0.6 if MPL_VERSION.startswith('3.3') else 0.) + tolerance=0.6 if mpl_version_before('3.5') else 0.) def test_hodograph_units(): """Test passing quantities to Hodograph.""" fig = plt.figure(figsize=(9, 9)) diff --git a/tests/plots/test_util.py b/tests/plots/test_util.py index 3dab3dcd868..2f973c4f0d1 100644 --- a/tests/plots/test_util.py +++ b/tests/plots/test_util.py @@ -5,16 +5,13 @@ from datetime import datetime -import matplotlib import matplotlib.pyplot as plt import numpy as np import pytest import xarray as xr from metpy.plots import add_metpy_logo, add_timestamp, add_unidata_logo, convert_gempak_color -from metpy.testing import get_test_data - -MPL_VERSION = matplotlib.__version__[:3] +from metpy.testing import get_test_data, mpl_version_before @pytest.mark.mpl_image_compare(tolerance=2.638, remove_text=True) @@ -94,7 +91,7 @@ def test_add_logo_invalid_size(): add_metpy_logo(fig, size='jumbo') -@pytest.mark.mpl_image_compare(tolerance={'3.3': 1.072}.get(MPL_VERSION, 0), +@pytest.mark.mpl_image_compare(tolerance=1.072 if mpl_version_before('3.5') else 0, remove_text=True) def test_gempak_color_image_compare(): """Test creating a plot with all the GEMPAK colors.""" @@ -114,7 +111,7 @@ def test_gempak_color_image_compare(): return fig -@pytest.mark.mpl_image_compare(tolerance={'3.3': 1.215}.get(MPL_VERSION, 0), +@pytest.mark.mpl_image_compare(tolerance=1.215 if mpl_version_before('3.5') else 0, remove_text=True) def test_gempak_color_xw_image_compare(): """Test creating a plot with all the GEMPAK colors using xw style."""