Repository: SciTools/cartopy
Pull request #2380 (2024-05-01 19:55:56)

MNT: drop py39 support and bump min package versions
<!--

Thanks for contributing to cartopy!
Please use this template as a guide to streamline the pull request you are about to make.

Remember: it is significantly easier to merge small pull requests. Consider whether this pull
request could be broken into smaller parts before submitting.

-->



## Rationale

<!-- Please provide detail as to *why* you are making this change. -->
According to the [NEP29 Support Table](https://numpy.org/neps/nep-0029-deprecation_policy.html#support-table) we should now be supporting Python 3.10+ and Numpy 1.23+.  [SPEC0](https://scientific-python.org/specs/spec-0000/) says we should support Matplotlib 3.6+ and Scipy 1.9+.  For other packages I tried to go for something that is a little older than 2 years.  This is more lenient than for the core packages considered by SPEC0, but some of them have pretty erratic release frequencies and it just seemed simpler to go for 2 years+ than try to decide how much less than 2 years is reasonable.

## Implications

<!-- If applicable, to the best of your knowledge, what are the implications of this change? -->


<!--
## Checklist

 * If this is a new feature, please provide an example of its use in the description. We may want to make a
   follow-on pull request to put the example in the gallery!

 * Ensure there is a suitable item in the cartopy test suite for the change you are proposing.

 * Note that you will automatically be asked to sign the
   [Contributor Licence Agreement](https://cla-assistant.io/SciTools/)
   (CLA), if you have not already done so.

-->


rcomer: OWSlib 0.26 doesn't play with PyProj 3.3.  So bumped OWSlib further to 0.27. https://github.com/geopython/OWSLib/issues/823
lgolston: Can also bump ruff target setting to py310 at: https://github.com/rcomer/cartopy/blob/14beb1bb64aac3b1b1a2852cd9918961e9179ca1/pyproject.toml#L101
lgolston: Maybe drop py39 too from CIBW_BUILD at https://github.com/SciTools/cartopy/blob/14beb1bb64aac3b1b1a2852cd9918961e9179ca1/.github/workflows/release.yml#L93
rcomer: Thanks for picking those up @lgolston!  Now fixed.
dopplershift: > Can also bump ruff target setting to py310 at: https://github.com/rcomer/cartopy/blob/14beb1bb64aac3b1b1a2852cd9918961e9179ca1/pyproject.toml#L101

I think we can actually do even better and just remove `target-version` altogether. The [default config](https://docs.astral.sh/ruff/settings/#target-version) will look at the `python-requires` set more broadly in `pyproject.toml`.

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 47e13cba..a598fb39 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -90,7 +90,7 @@ jobs:
           )
           echo "include=$MATRIX" >> $GITHUB_OUTPUT
         env:
-          CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-*"
+          CIBW_BUILD: "cp310-* cp311-* cp312-*"
           # Skip 32 bit builds and musllinux due to lack of numpy wheels
           CIBW_SKIP: "*-win32 *_i686 *-musllinux*"
           CIBW_ARCHS_MACOS: x86_64 arm64
diff --git a/INSTALL b/INSTALL
index 69819215..11240f2e 100644
--- a/INSTALL
+++ b/INSTALL
@@ -59,14 +59,14 @@ To use it::
 
 Further information about the required dependencies can be found here:
 
-**Python** 3.9 or later (https://www.python.org/)
+**Python** 3.10 or later (https://www.python.org/)
     Python 2 support was removed in v0.19.
 
-**Matplotlib** 3.5 or later (https://matplotlib.org/)
+**Matplotlib** 3.6 or later (https://matplotlib.org/)
     Python package for 2D plotting. Python package required for any
     graphical capabilities.
 
-**Shapely** 1.7.1 or later (https://github.com/shapely/shapely)
+**Shapely** 1.8 or later (https://github.com/shapely/shapely)
     Python package for the manipulation and analysis of planar geometric objects.
 
 **pyshp** 2.3 or later (https://pypi.python.org/pypi/pyshp)
@@ -83,17 +83,17 @@ to install these optional dependencies. They are also included in some of the
 optional groups when installing. For example, use `pip install .[ows]` to install
 the optional OWS libraries.
 
-**Pillow** 6.1.0 or later (https://python-pillow.org)
+**Pillow** 9.1 or later (https://python-pillow.org)
     A popular fork of PythonImagingLibrary.
 
 **pykdtree** 1.2.2 or later (https://github.com/storpipfugl/pykdtree)
     A fast kd-tree implementation that is used for faster warping
     of images than SciPy.
 
-**SciPy** 1.6.3 or later (https://www.scipy.org/)
+**SciPy** 1.9 or later (https://www.scipy.org/)
     A Python package for scientific computing.
 
-**OWSLib** 0.24.1 or later (https://pypi.python.org/pypi/OWSLib)
+**OWSLib** 0.27 or later (https://pypi.python.org/pypi/OWSLib)
      A Python package for client programming with the Open Geospatial
      Consortium (OGC) web service, and which gives access to Cartopy ogc
      clients.
diff --git a/environment.yml b/environment.yml
index 851a8050..8cc6d7ac 100644
--- a/environment.yml
+++ b/environment.yml
@@ -8,21 +8,21 @@ name: cartopy-dev
 channels:
   - conda-forge
 dependencies:
-  - cython>=0.29.24
-  - numpy>=1.21
-  - shapely>=1.7.1
+  - cython>=0.29.28
+  - numpy>=1.23
+  - shapely>=1.8
   - pyshp>=2.3
   - pyproj>=3.3.1
+  - packaging>=21
   # The testing label has the proper version of freetype included
-  - conda-forge/label/testing::matplotlib-base>=3.5
+  - conda-forge/label/testing::matplotlib-base>=3.6
 
   # OWS
-  - owslib>=0.24.1
-  - pillow>=6.1.0
+  - owslib>=0.27
+  - pillow>=9.1
   # Plotting
-  - scipy>=1.6.3
+  - scipy>=1.9
   # Testing
-  - packaging>=20
   - pytest
   - pytest-mpl
   - pytest-xdist
diff --git a/lib/cartopy/mpl/__init__.py b/lib/cartopy/mpl/__init__.py
index 18595979..f5de7c8e 100644
--- a/lib/cartopy/mpl/__init__.py
+++ b/lib/cartopy/mpl/__init__.py
@@ -8,5 +8,5 @@ import packaging.version
 
 
 _MPL_VERSION = packaging.version.parse(matplotlib.__version__)
-_MPL_36 = _MPL_VERSION.release[:2] >= (3, 6)
+_MPL_37 = _MPL_VERSION.release[:2] >= (3, 7)
 _MPL_38 = _MPL_VERSION.release[:2] >= (3, 8)
diff --git a/lib/cartopy/mpl/geoaxes.py b/lib/cartopy/mpl/geoaxes.py
index 005dd8e8..9ad698fb 100644
--- a/lib/cartopy/mpl/geoaxes.py
+++ b/lib/cartopy/mpl/geoaxes.py
@@ -580,18 +580,11 @@ class GeoAxes(matplotlib.axes.Axes):
         self.dataLim.intervalx = self.projection.x_limits
         self.dataLim.intervaly = self.projection.y_limits
 
-    if mpl.__version__ >= '3.6':
-        def clear(self):
-            """Clear the current Axes and add boundary lines."""
-            result = super().clear()
-            self.__clear()
-            return result
-    else:
-        def cla(self):
-            """Clear the current Axes and add boundary lines."""
-            result = super().cla()
-            self.__clear()
-            return result
+    def clear(self):
+        """Clear the current Axes and add boundary lines."""
+        result = super().clear()
+        self.__clear()
+        return result
 
     def format_coord(self, x, y):
         """
diff --git a/pyproject.toml b/pyproject.toml
index 862805ff..6e16bf74 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,7 @@ authors = [
 ]
 description = "A Python library for cartographic visualizations with Matplotlib"
 readme = "README.md"
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 keywords = ["cartography", "map", "transform", "projection", "pyproj", "shapely", "shapefile"]
 license = {file = "LICENSE"}
 classifiers = [
@@ -36,7 +36,6 @@ classifiers = [
     'Programming Language :: C++',
     'Programming Language :: Python',
     'Programming Language :: Python :: 3',
-    'Programming Language :: Python :: 3.9',
     'Programming Language :: Python :: 3.10',
     'Programming Language :: Python :: 3.11',
     'Programming Language :: Python :: 3.12',
@@ -46,10 +45,10 @@ classifiers = [
     'Topic :: Scientific/Engineering :: Visualization',
 ]
 dependencies = [
-    "numpy>=1.21",
-    "matplotlib>=3.5",
-    "shapely>=1.7",
-    "packaging>=20",
+    "numpy>=1.23",
+    "matplotlib>=3.6",
+    "shapely>=1.8",
+    "packaging>=21",
     "pyshp>=2.3",
     "pyproj>=3.3.1",
 ]
@@ -58,8 +57,8 @@ dynamic = ["version"]
 [project.optional-dependencies]
 doc = ["pydata-sphinx-theme", "sphinx", "sphinx-gallery"]
 speedups = ["pykdtree", "fiona"]
-ows = ["OWSLib>=0.20.0", "pillow>=6.1.0"]
-plotting = ["pillow>=6.1.0", "scipy>=1.3.1"]
+ows = ["OWSLib>=0.27.0", "pillow>=9.1"]
+plotting = ["pillow>=9.1", "scipy>=1.9"]
 srtm = ["beautifulsoup4"]
 test = ["pytest>=5.1.2", "pytest-mpl>=0.11", "pytest-xdist", "pytest-cov", "coveralls"]
 
@@ -99,7 +98,6 @@ testpaths = ["lib"]
 python_files = ["test_*.py"]
 
 [tool.ruff]
-target-version = "py39"
 lint.select = ["E", "F", "I", "W"]
 
 [tool.ruff.lint.isort]


Test patch:
diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml
index d6245879..68bb8baa 100644
--- a/.github/workflows/ci-testing.yml
+++ b/.github/workflows/ci-testing.yml
@@ -17,7 +17,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: [3.9, '3.10', '3.11', '3.12']
+        python-version: ['3.10', '3.11', '3.12']
         use-network: [true]
         include:
           - os: ubuntu-latest
@@ -37,11 +37,11 @@ jobs:
 
       - name: Minimum packages
         if: |
-          matrix.python-version == '3.9' && matrix.os == 'ubuntu-latest' &&
+          matrix.python-version == '3.10' && matrix.os == 'ubuntu-latest' &&
           (github.event_name == 'push' || github.event_name == 'pull_request')
         id: minimum-packages
         run: |
-          pip install cython==0.29.24 matplotlib==3.5.3 numpy==1.21 owslib==0.24.1 pyproj==3.3.1 scipy==1.6.3 shapely==1.7.1 pyshp==2.3.1
+          pip install cython==0.29.28 matplotlib==3.6 numpy==1.23 owslib==0.27 pyproj==3.3.1 scipy==1.9 shapely==1.8 pyshp==2.3.1
 
       - name: Coverage packages
         id: coverage
diff --git a/lib/cartopy/tests/mpl/test_crs.py b/lib/cartopy/tests/mpl/test_crs.py
index fd2a0db7..18a9e522 100644
--- a/lib/cartopy/tests/mpl/test_crs.py
+++ b/lib/cartopy/tests/mpl/test_crs.py
@@ -7,12 +7,12 @@ import matplotlib.pyplot as plt
 import pytest
 
 import cartopy.crs as ccrs
-from cartopy.mpl import _MPL_36
+from cartopy.mpl import _MPL_37
 
 
 @pytest.mark.natural_earth
 @pytest.mark.mpl_image_compare(
-    filename="igh_land.png", tolerance=0.5 if _MPL_36 else 3.6)
+    filename="igh_land.png", tolerance=0.5 if _MPL_37 else 3.6)
 def test_igh_land():
     crs = ccrs.InterruptedGoodeHomolosine(emphasis="land")
     ax = plt.axes(projection=crs)
@@ -23,7 +23,7 @@ def test_igh_land():
 
 @pytest.mark.natural_earth
 @pytest.mark.mpl_image_compare(filename="igh_ocean.png",
-                               tolerance=0.5 if _MPL_36 else 4.5)
+                               tolerance=0.5 if _MPL_37 else 4.5)
 def test_igh_ocean():
     crs = ccrs.InterruptedGoodeHomolosine(
         central_longitude=-160, emphasis="ocean"
diff --git a/lib/cartopy/tests/mpl/test_gridliner.py b/lib/cartopy/tests/mpl/test_gridliner.py
index c886e610..8934e3d8 100644
--- a/lib/cartopy/tests/mpl/test_gridliner.py
+++ b/lib/cartopy/tests/mpl/test_gridliner.py
@@ -14,7 +14,6 @@ import pytest
 from shapely.geos import geos_version
 
 import cartopy.crs as ccrs
-from cartopy.mpl import _MPL_36
 from cartopy.mpl.geoaxes import GeoAxes
 from cartopy.mpl.gridliner import (
     LATITUDE_FORMATTER,
@@ -537,10 +536,7 @@ def test_gridliner_title_adjust():
     plt.rcParams['axes.titley'] = None
 
     fig = plt.figure(layout='constrained')
-    if _MPL_36:
-        fig.get_layout_engine().set(h_pad=1/8)
-    else:
-        fig.set_constrained_layout_pads(h_pad=1/8)
+    fig.get_layout_engine().set(h_pad=1/8)
     for n, proj in enumerate(projs, 1):
         ax = fig.add_subplot(2, 2, n, projection=proj)
         ax.coastlines()