diff --git a/Changelog.rst b/Changelog.rst index 53e8886d84..2509314659 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -3,12 +3,19 @@ version 3.17.0 **2025-??-??** +* Set a new minimum version of `numpy`: ``2.0.0`` + (https://github.com/NCAS-CMS/cf-python/issues/843) * Replace dataset aggregation functionality (CFA) with that imported from `cfdm` (https://github.com/NCAS-CMS/cf-python/issues/841) * New keyword parameter to `cf.Field.compute_vertical_coordinates`: ``key`` (https://github.com/NCAS-CMS/cf-python/issues/802) -* Changed dependency: ``1.12.0.0<=cfdm<1.12.1.0`` -* Changed dependency: ``h5py>=3.12.0`` +* Changed dependency: ``Python>=3.9.0`` +* Changed dependency: ``numpy>=2.0.0`` +* Changed dependency: ``cfdm>=1.12.0.0, <1.12.1.0`` +* Changed optional dependency: ``esmpy>=8.7.0`` +* Removed dependency (now incorporated into ``cfdm``): ``h5py`` +* Removed dependency (now incorporated into ``cfdm``): ``h5netcdf`` +* Removed dependency (now incorporated into ``cfdm``): ``s3fs`` ---- @@ -287,8 +294,8 @@ version 3.15.0 * Handled the renaming of the ESMF Python interface from `ESMF` to `esmpy` at version 8.4.0. Both module names are accepted for now. * Changed dependency: ``1.10.1.0<=cfdm<1.10.2.0`` -* Changed (optional) dependency: ``8.0.0<=esmpy`` -* Changed (optional) dependency: ``1.10.0<=scipy`` +* Changed optional dependency: ``8.0.0<=esmpy`` +* Changed optional dependency: ``1.10.0<=scipy`` ---- diff --git a/cf/__init__.py b/cf/__init__.py index 05066c691d..1e36ba3041 100644 --- a/cf/__init__.py +++ b/cf/__init__.py @@ -80,7 +80,6 @@ """ -__Conventions__ = "CF-1.11" __date__ = "2025-01-28" __version__ = "3.16.3" @@ -95,154 +94,143 @@ "packaging", "scipy", ) - x = ", ".join(_requires) _error0 = f"cf v{__version__} requires the modules {x}. " +import importlib.util +from platform import python_version + +_found_esmpy = bool(importlib.util.find_spec("esmpy")) + try: - import cfdm + import packaging + from packaging.version import Version except ImportError as error1: raise ImportError(_error0 + str(error1)) +else: + _minimum_vn = "20.0" + if Version(packaging.__version__) < Version(_minimum_vn): + raise RuntimeError( + f"Bad packaging version: cf requires packaging>={_minimum_vn}. " + f"Got {packaging.__version__} at {packaging.__file__}" + ) -__cf_version__ = cfdm.core.__cf_version__ - -from packaging.version import Version -import importlib.util -import platform - -# ESMF renamed its Python module to `esmpy` at ESMF version 8.4.0. Allow -# either for now for backwards compatibility. -_found_esmpy = bool( - importlib.util.find_spec("esmpy") or importlib.util.find_spec("ESMF") -) +try: + import cfdm +except ImportError as error1: + raise ImportError(_error0 + str(error1)) +else: + # Check the version of cfdm + _minimum_vn = "1.12.0.0" + _maximum_vn = "1.12.1.0" + _cfdm_version = Version(cfdm.__version__) + if ( + _cfdm_version < Version(_minimum_vn) or + _cfdm_version >= Version(_maximum_vn) + ): + raise RuntimeError( + "Bad cfdm version: cf requires " + f"{_minimum_vn}<=cfdm<{_maximum_vn}. " + f"Got {cfdm.__version__} at {cfdm.__file__}" + ) + +__cf_version__ = cfdm.__cf_version__ +__Conventions__ = f"CF-{__cf_version__}" try: import netCDF4 except ImportError as error1: raise ImportError(_error0 + str(error1)) +else: + _minimum_vn = "1.7.2" + if Version(netCDF4.__version__) < Version(_minimum_vn): + raise RuntimeError( + f"Bad netCDF4 version: cf requires netCDF4>={_minimum_vn}. " + f"Got {netCDF4.__version__} at {netCDF4.__file__}" + ) try: import numpy as np except ImportError as error1: raise ImportError(_error0 + str(error1)) +else: + _minimum_vn = "2.0.0" + if Version(np.__version__) < Version(_minimum_vn): + raise ValueError( + f"Bad numpy version: cf requires numpy>={_minimum_vn} " + f"Got {np.__version__} at {np.__file__}" + ) try: import cftime except ImportError as error1: raise ImportError(_error0 + str(error1)) +else: + _minimum_vn = "1.6.4" + if Version(cftime.__version__) < Version(_minimum_vn): + raise RuntimeError( + f"Bad cftime version: cf requires cftime>={_minimum_vn}. " + f"Got {cftime.__version__} at {cftime.__file__}" + ) try: import cfunits except ImportError as error1: raise ImportError(_error0 + str(error1)) +else: + _minimum_vn = "3.3.7" + if Version(cfunits.__version__) < Version(_minimum_vn): + raise RuntimeError( + f"Bad cfunits version: cf requires cfunits>={_minimum_vn}. " + f"Got {cfunits.__version__} at {cfunits.__file__}" + ) try: import psutil except ImportError as error1: raise ImportError(_error0 + str(error1)) +else: + _minimum_vn = "0.6.0" + if Version(psutil.__version__) < Version(_minimum_vn): + raise RuntimeError( + f"Bad psutil version: cf requires psutil>={_minimum_vn}. " + f"Got {psutil.__version__} at {psutil.__file__}" + ) try: import dask except ImportError as error1: raise ImportError(_error0 + str(error1)) - -try: - import packaging -except ImportError as error1: - raise ImportError(_error0 + str(error1)) +else: + _minimum_vn = "2024.6.1" + _maximum_vn = "2024.7.1" + if ( + Version(dask.__version__) < Version(_minimum_vn) or + Version(dask.__version__) > Version(_maximum_vn) + ): + raise ValueError( + "Bad dask version: cf requires " + f"{_minimum_vn}<=dask<={_maximum_vn}. " + f"Got {dask.__version__} at {dask.__file__}" + ) try: import scipy except ImportError as error1: raise ImportError(_error0 + str(error1)) - -# Check the version of packaging -_minimum_vn = "20.0" -if Version(packaging.__version__) < Version(_minimum_vn): - raise RuntimeError( - f"Bad packaging version: cf requires packaging>={_minimum_vn}. " - f"Got {packaging.__version__} at {packaging.__file__}" - ) - -# Check the version of psutil -_minimum_vn = "0.6.0" -if Version(psutil.__version__) < Version(_minimum_vn): - raise RuntimeError( - f"Bad psutil version: cf requires psutil>={_minimum_vn}. " - f"Got {psutil.__version__} at {psutil.__file__}" - ) - -# Check the version of netCDF4 -_minimum_vn = "1.6.5" -if Version(netCDF4.__version__) < Version(_minimum_vn): - raise RuntimeError( - f"Bad netCDF4 version: cf requires netCDF4>={_minimum_vn}. " - f"Got {netCDF4.__version__} at {netCDF4.__file__}" - ) - -# Check the version of cftime -_minimum_vn = "1.6.2" -if Version(cftime.__version__) < Version(_minimum_vn): - raise RuntimeError( - f"Bad cftime version: cf requires cftime>={_minimum_vn}. " - f"Got {cftime.__version__} at {cftime.__file__}" - ) - -# Check the version of numpy -_minimum_vn = "1.22" -_maximum_vn = "2.0" -if not Version(_minimum_vn) <= Version(np.__version__) < Version(_maximum_vn): - raise ValueError( - "Bad numpy version: cf requires _minimum_vn}<=numpy<{_maximum_vn}. " - f"Got {np.__version__} at {np.__file__}" - ) - -# Check the version of cfunits -_minimum_vn = "3.3.7" -if Version(cfunits.__version__) < Version(_minimum_vn): - raise RuntimeError( - f"Bad cfunits version: cf requires cfunits>={_minimum_vn}. " - f"Got {cfunits.__version__} at {cfunits.__file__}" - ) - -# Check the version of cfdm -_minimum_vn = "1.12.0.0" -_maximum_vn = "1.12.1.0" -_cfdm_version = Version(cfdm.__version__) -if not Version(_minimum_vn) <= _cfdm_version < Version(_maximum_vn): - raise RuntimeError( - f"Bad cfdm version: cf requires {_minimum_vn}<=cfdm<{_maximum_vn}. " - f"Got {cfdm.__version__} at {cfdm.__file__}" - ) - -# Check the version of dask - -_minimum_vn = "2024.6.1" -_maximum_vn = "2024.7.1" -if ( - not Version(_minimum_vn) - <= Version(dask.__version__) - <= Version(_maximum_vn) -): +else: + _minimum_vn = "1.10.0" + if Version(scipy.__version__) < Version(_minimum_vn): + raise RuntimeError( + f"Bad scipy version: cf requires scipy>={_minimum_vn}. " + f"Got {scipy.__version__} at {scipy.__file__}" + ) + +_minimum_vn = "3.9.0" +if Version(python_version()) < Version(_minimum_vn): raise ValueError( - "Bad dask version: cf requires {_minimum_vn}<=dask<={_maximum_vn}. " - f"Got {dask.__version__} at {dask.__file__}" - ) - -# Check the version of Python -_minimum_vn = "3.8.0" -if Version(platform.python_version()) < Version(_minimum_vn): - raise ValueError( - f"Bad python version: cf requires python version {_minimum_vn} " - f"or later. Got {platform.python_version()}" - ) - -# Check the version of scipy -_minimum_vn = "1.10.0" -if Version(scipy.__version__) < Version(_minimum_vn): - raise RuntimeError( - f"Bad scipy version: cf requires scipy>={_minimum_vn}. " - f"Got {scipy.__version__} at {scipy.__file__}" + f"Bad python version: cf requires python>={_minimum_vn}. " + f"Got {python_version()}" ) del _minimum_vn, _maximum_vn diff --git a/cf/data/collapse/dask_collapse.py b/cf/data/collapse/dask_collapse.py index a9245ff10e..1238860a32 100644 --- a/cf/data/collapse/dask_collapse.py +++ b/cf/data/collapse/dask_collapse.py @@ -858,9 +858,17 @@ def cf_sample_size_chunk(x, dtype="i8", computing_meta=False, **kwargs): return x x = cfdm_to_memory(x) - if np.ma.isMA(x): - N = chunk.sum(np.ones_like(x, dtype=dtype), **kwargs) + # Note: We're not using `np.ones_like` here (like we used to) + # because numpy currently (numpy==2.2.3) has a bug that + # produces a RuntimeWarning: "numpy/ma/core.py:502: + # RuntimeWarning: invalid value encountered in cast + # fill_value = np.asarray(fill_value, + # dtype=ndtype)". See + # https://github.com/numpy/numpy/issues/28255 for more + # details. + x = np.ma.array(np.ones((x.shape), dtype=x.dtype), mask=x.mask) + N = chunk.sum(x, **kwargs) else: if dtype: kwargs["dtype"] = dtype diff --git a/cf/functions.py b/cf/functions.py index bc86d41bfd..475cd8d56f 100644 --- a/cf/functions.py +++ b/cf/functions.py @@ -2755,6 +2755,7 @@ def dirname(path, normalise=False, uri=None, isdir=False, sep=False): dirname.__doc__ = cfdm.dirname.__doc__.replace("cfdm.", "cf.") from functools import partial + dirname2 = partial(cfdm.dirname) dirname2.__doc__ = cfdm.dirname.__doc__.replace("cfdm.", "cf.") diff --git a/cf/mixin/fielddomain.py b/cf/mixin/fielddomain.py index 6d78ac2027..30ba3e1aac 100644 --- a/cf/mixin/fielddomain.py +++ b/cf/mixin/fielddomain.py @@ -692,8 +692,7 @@ def _point_not_in_cell(nodes_x, nodes_y, point): if ind is not None: mask_component_shape = [] masked_subspace_size = 1 - # TODONUMPY2: https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword - ind = np.array(ind, copy=False) + ind = np.array(ind) for i, (axis, start, stop) in enumerate( zip(canonical_axes, ind.min(axis=1), ind.max(axis=1)) diff --git a/cf/mixin/propertiesdata.py b/cf/mixin/propertiesdata.py index d6086d0471..158bd2dc17 100644 --- a/cf/mixin/propertiesdata.py +++ b/cf/mixin/propertiesdata.py @@ -49,14 +49,6 @@ class PropertiesData(Properties): _special_properties = ("units", "calendar") - def __array__(self, *dtype): - """Returns a numpy array representation of the data.""" - data = self.get_data(None) - if data is not None: - return data.__array__(*dtype) - - raise ValueError(f"{self.__class__.__name__} has no data") - def __contains__(self, value): """Called to implement membership test operators. diff --git a/cf/regrid/regrid.py b/cf/regrid/regrid.py index cbe53e616a..e3d4ad79a9 100644 --- a/cf/regrid/regrid.py +++ b/cf/regrid/regrid.py @@ -2186,7 +2186,7 @@ def create_esmpy_mesh(grid, mask=None): # esmpy. min_id = node_ids.min() if min_id < 1: - node_ids += min_id + 1 + node_ids = node_ids + min_id + 1 # Add nodes. This must be done before `add_elements`. esmpy_mesh.add_nodes( diff --git a/cf/test/test_Data.py b/cf/test/test_Data.py index e007e89b39..b25342afce 100644 --- a/cf/test/test_Data.py +++ b/cf/test/test_Data.py @@ -2621,7 +2621,7 @@ def test_Data_percentile_median(self): a1 = np.nanpercentile(filled, q, keepdims=keepdims) mask = np.isnan(a1) if mask.any(): - a1 = np.ma.masked_where(mask, a1, copy=False) + a1 = np.ma.masked_where(mask, a1) b1 = d.percentile(q, squeeze=not keepdims) self.assertEqual(b1.shape, a1.shape) @@ -3613,6 +3613,7 @@ def test_Data_sample_size(self): b = np.ma.asanyarray(b) e = d.sample_size(axes=axis, squeeze=True) + e = np.ma.array(e.array) self.assertTrue((e.mask == b.mask).all()) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 8ea7cd5d11..b864489e5c 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -32,7 +32,7 @@ Windows Subsystem for Linux (WSL) **Python versions** ------------------- -The cf package is only for Python 3.8 or newer. +The cf package is only for Python 3.9 or newer. ---- @@ -108,7 +108,7 @@ visualisation package `_, run: :caption: *Install with conda.* $ conda install -c conda-forge cf-python cf-plot udunits2 - $ conda install -c conda-forge "esmpy>=8.0.0" + $ conda install -c conda-forge "esmpy>=8.7.0" The second of the two ``conda`` commands is required for :ref:`regridding ` to work. @@ -190,26 +190,18 @@ installed, which: Required ^^^^^^^^ -* `Python `_, 3.8.0 or newer. +* `Python `_, 3.9.0 or newer. -* `numpy `_, versions 1.15 up to, but not - including, 2.0. +* `numpy `_, versions 2.0.0 or newer. * `dask `_, versions 2024.6.0 to 2024.7.1 inclusive. -* `netCDF4 `_, 1.6.5 or newer. +* `netCDF4 `_, 1.7.2 or newer. -* `cftime `_, version 1.6.2 or newer +* `cftime `_, version 1.6.4 or newer (note that this package may be installed with netCDF4). -* `h5netcdf `_, version 1.3.0 - newer. - -* `h5py `_, version 3.12.0 or newer. - -* `s3fs `_, version 2024.6.0 or newer. - * `scipy `_, version 1.10.0 or newer. * `cfdm `_, version 1.12.0.0 or up to, @@ -256,11 +248,11 @@ environments for which these features are not required. * `esmpy `_, previously named `ESMF` with the old module name also being accepted for import, - version 8.0.0 or newer. This is easily installed via conda with + version 8.7.0 or newer. This is easily installed via conda with .. code-block:: console - $ conda install -c conda-forge "esmpy>=8.0.0" + $ conda install -c conda-forge "esmpy>=8.7.0" or may be installed from source. diff --git a/requirements.txt b/requirements.txt index 755493c32b..e2d521eb0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,9 @@ -netCDF4>=1.6.5 -cftime>=1.6.2 -numpy>=1.22,<2.0 +netCDF4>=1.7.2 +cftime>=1.6.4 +numpy>=2.0.0 cfdm>=1.12.0.0, <1.12.1.0 psutil>=0.6.0 cfunits>=3.3.7 dask>=2024.6.0,<=2024.7.1 packaging>=20.0 scipy>=1.10.0 -h5netcdf>=1.3.0 -h5py>=3.12.0 -s3fs>=2024.6.0 diff --git a/setup.py b/setup.py index af397e6c93..668c39cd13 100755 --- a/setup.py +++ b/setup.py @@ -294,16 +294,16 @@ def compile(): "Operating System :: MacOS", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], packages=find_packages(), package_data={"cf": package_data}, scripts=["scripts/cfa"], - python_requires=">=3.8", + python_requires=">=3.9", install_requires=install_requires, tests_require=tests_require, extras_require=extras_require,