diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 9b5e850..7cc8015 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -12,7 +12,7 @@ on:
workflow_dispatch:
env:
- python-version: "3.12"
+ python-version: "3.13"
jobs:
build:
@@ -65,12 +65,12 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["3.10", "3.11", "3.12"]
+ python-version: ["3.11", "3.12", "3.13"]
dependencies: ["pinned"]
include:
- - python-version: "3.12"
+ - python-version: "3.13"
dependencies: "latest"
- - python-version: "3.10"
+ - python-version: "3.11"
dependencies: "minimum"
@@ -135,7 +135,7 @@ jobs:
uses: actions/cache@v3
with:
path: '.mypy_cache'
- key: mypy-${{ runner.os }}-py${{ env.python-version }}-${{ hashFiles('requirements.txt') }}
+ key: mypy-${{ runner.os }}-py${{ env.python-version }}-${{ hashFiles(format('continuous-integration/requirements-{0}.txt', env.python-version)) }}
- run: flake8 src/ tests/
- run: isort --diff --check-only src/ tests/
diff --git a/continuous-integration/requirements-3.11.txt b/continuous-integration/requirements-3.11.txt
index 3a4718d..177b6de 100644
--- a/continuous-integration/requirements-3.11.txt
+++ b/continuous-integration/requirements-3.11.txt
@@ -2,75 +2,76 @@
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
-# pip-compile --extra=testing --output-file=./continuous-integration/requirements-3.11.txt setup.cfg
+# pip-compile --extra=testing --output-file=./continuous-integration/requirements-3.11.txt --unsafe-package=emsarray pyproject.toml
#
-bokeh==3.4.2
+bokeh==3.6.2
# via dask
-bottleneck==1.4.0
- # via emsarray (setup.cfg)
-cartopy==0.23.0
- # via emsarray (setup.cfg)
-certifi==2024.6.2
+bottleneck==1.4.2
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+cartopy==0.24.1
+ # via emsarray
+certifi==2024.12.14
# via
# netcdf4
# pyproj
# requests
-cftime==1.6.4
+cftime==1.6.4.post1
# via
# cfunits
# netcdf4
cfunits==3.3.7
- # via emsarray (setup.cfg)
-charset-normalizer==3.3.2
+ # via emsarray
+charset-normalizer==3.4.1
# via requests
-click==8.1.7
+click==8.1.8
# via
# dask
# distributed
-cloudpickle==3.0.0
+cloudpickle==3.1.1
# via
# dask
# distributed
-contourpy==1.2.1
+contourpy==1.3.1
# via
# bokeh
# matplotlib
-coverage[toml]==7.5.4
+coverage[toml]==7.6.10
# via pytest-cov
cycler==0.12.1
# via matplotlib
-dask[array,complete,dataframe,diagnostics,distributed]==2024.6.2
+dask[array,complete,dataframe,diagnostics,distributed]==2025.1.0
# via
- # dask-expr
# distributed
# xarray
-dask-expr==1.1.6
- # via dask
-distributed==2024.6.2
+distributed==2025.1.0
# via dask
-flake8==7.1.0
- # via emsarray (setup.cfg)
-fonttools==4.53.0
+flake8==7.1.1
+ # via emsarray (pyproject.toml)
+fonttools==4.55.4
# via matplotlib
-fsspec==2024.6.1
+fsspec==2024.12.0
# via dask
-geojson==3.1.0
- # via emsarray (setup.cfg)
-idna==3.7
+geojson==3.2.0
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+idna==3.10
# via requests
-importlib-metadata==8.0.0
+importlib-metadata==8.6.1
# via dask
iniconfig==2.0.0
# via pytest
isort==5.13.2
- # via emsarray (setup.cfg)
-jinja2==3.1.4
+ # via emsarray (pyproject.toml)
+jinja2==3.1.5
# via
# bokeh
# dask
# distributed
# pytest-mpl
-kiwisolver==1.4.5
+kiwisolver==1.4.8
# via matplotlib
locket==1.0.0
# via
@@ -78,24 +79,26 @@ locket==1.0.0
# partd
lz4==4.3.3
# via dask
-markupsafe==2.1.5
+markupsafe==3.0.2
# via jinja2
-matplotlib==3.9.0
+matplotlib==3.10.0
# via
# cartopy
- # emsarray (setup.cfg)
+ # emsarray
# pytest-mpl
mccabe==0.7.0
# via flake8
-msgpack==1.0.8
+msgpack==1.1.0
# via distributed
-mypy==1.10.1
- # via emsarray (setup.cfg)
+mypy==1.14.1
+ # via emsarray (pyproject.toml)
mypy-extensions==1.0.0
# via mypy
-netcdf4==1.7.1.post1
- # via emsarray (setup.cfg)
-numpy==2.0.0
+netcdf4==1.7.2
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+numpy==2.2.2
# via
# bokeh
# bottleneck
@@ -104,129 +107,132 @@ numpy==2.0.0
# cfunits
# contourpy
# dask
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# netcdf4
# pandas
# pandas-stubs
- # pyarrow
# pykdtree
# shapely
# xarray
-packaging==24.1
+packaging==24.2
# via
# bokeh
# cartopy
# cfunits
# dask
# distributed
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# pooch
# pytest
# pytest-mpl
# xarray
-pandas==2.2.2
+pandas==2.2.3
# via
# bokeh
# dask
- # dask-expr
# xarray
-pandas-stubs==2.2.2.240603
- # via emsarray (setup.cfg)
+pandas-stubs==2.2.3.241126
+ # via emsarray (pyproject.toml)
partd==1.4.2
# via dask
-pillow==10.4.0
+pillow==11.1.0
# via
# bokeh
# matplotlib
# pytest-mpl
-platformdirs==4.2.2
+platformdirs==4.3.6
# via pooch
pluggy==1.5.0
# via pytest
pooch==1.8.2
- # via emsarray (setup.cfg)
-psutil==6.0.0
+ # via emsarray
+psutil==6.1.1
# via distributed
-pyarrow==16.1.0
- # via
- # dask
- # dask-expr
-pyarrow-hotfix==0.6
+pyarrow==19.0.0
# via dask
-pycodestyle==2.12.0
+pycodestyle==2.12.1
# via flake8
pyflakes==3.2.0
# via flake8
-pykdtree==1.3.12
- # via emsarray (setup.cfg)
-pyparsing==3.1.2
+pykdtree==1.3.13
+ # via emsarray
+pyparsing==3.2.1
# via matplotlib
-pyproj==3.6.1
+pyproj==3.7.0
# via cartopy
pyshp==2.3.1
# via
# cartopy
- # emsarray (setup.cfg)
-pytest==8.2.2
+ # emsarray
+ # emsarray (pyproject.toml)
+pytest==8.3.4
# via
- # emsarray (setup.cfg)
+ # emsarray (pyproject.toml)
# pytest-cov
# pytest-mpl
-pytest-cov==5.0.0
- # via emsarray (setup.cfg)
+pytest-cov==6.0.0
+ # via emsarray (pyproject.toml)
pytest-mpl==0.17.0
- # via emsarray (setup.cfg)
+ # via emsarray (pyproject.toml)
python-dateutil==2.9.0.post0
# via
# matplotlib
# pandas
-pytz==2024.1
+pytz==2024.2
# via pandas
-pyyaml==6.0.1
+pyyaml==6.0.2
# via
# bokeh
# dask
# distributed
requests==2.32.3
# via pooch
-shapely==2.0.4
+shapely==2.0.6
# via
# cartopy
- # emsarray (setup.cfg)
-six==1.16.0
+ # emsarray
+ # emsarray (pyproject.toml)
+six==1.17.0
# via python-dateutil
sortedcontainers==2.4.0
# via distributed
tblib==3.0.0
# via distributed
-toolz==0.12.1
+toolz==1.0.0
# via
# dask
# distributed
# partd
-tornado==6.4.1
+tornado==6.4.2
# via
# bokeh
# distributed
-types-pytz==2024.1.0.20240417
+types-pytz==2024.2.0.20241221
# via
- # emsarray (setup.cfg)
+ # emsarray (pyproject.toml)
# pandas-stubs
typing-extensions==4.12.2
# via mypy
-tzdata==2024.1
+tzdata==2025.1
# via pandas
-urllib3==2.2.2
+urllib3==2.3.0
# via
# distributed
# requests
-xarray[parallel]==2024.6.0
- # via emsarray (setup.cfg)
-xyzservices==2024.6.0
+xarray[parallel]==2025.1.1
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+xyzservices==2025.1.0
# via bokeh
zict==3.0.0
# via distributed
-zipp==3.19.2
+zipp==3.21.0
# via importlib-metadata
+
+# The following packages are considered to be unsafe in a requirements file:
+# emsarray
diff --git a/continuous-integration/requirements-3.12.txt b/continuous-integration/requirements-3.12.txt
index 86f5c78..a282b8c 100644
--- a/continuous-integration/requirements-3.12.txt
+++ b/continuous-integration/requirements-3.12.txt
@@ -2,73 +2,74 @@
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
-# pip-compile --extra=testing --output-file=./continuous-integration/requirements-3.12.txt setup.cfg
+# pip-compile --extra=testing --output-file=./continuous-integration/requirements-3.12.txt --unsafe-package=emsarray pyproject.toml
#
-bokeh==3.4.2
+bokeh==3.6.2
# via dask
-bottleneck==1.4.0
- # via emsarray (setup.cfg)
-cartopy==0.23.0
- # via emsarray (setup.cfg)
-certifi==2024.6.2
+bottleneck==1.4.2
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+cartopy==0.24.1
+ # via emsarray
+certifi==2024.12.14
# via
# netcdf4
# pyproj
# requests
-cftime==1.6.4
+cftime==1.6.4.post1
# via
# cfunits
# netcdf4
cfunits==3.3.7
- # via emsarray (setup.cfg)
-charset-normalizer==3.3.2
+ # via emsarray
+charset-normalizer==3.4.1
# via requests
-click==8.1.7
+click==8.1.8
# via
# dask
# distributed
-cloudpickle==3.0.0
+cloudpickle==3.1.1
# via
# dask
# distributed
-contourpy==1.2.1
+contourpy==1.3.1
# via
# bokeh
# matplotlib
-coverage[toml]==7.5.4
+coverage[toml]==7.6.10
# via pytest-cov
cycler==0.12.1
# via matplotlib
-dask[array,complete,dataframe,diagnostics,distributed]==2024.6.2
+dask[array,complete,dataframe,diagnostics,distributed]==2025.1.0
# via
- # dask-expr
# distributed
# xarray
-dask-expr==1.1.6
- # via dask
-distributed==2024.6.2
+distributed==2025.1.0
# via dask
-flake8==7.1.0
- # via emsarray (setup.cfg)
-fonttools==4.53.0
+flake8==7.1.1
+ # via emsarray (pyproject.toml)
+fonttools==4.55.4
# via matplotlib
-fsspec==2024.6.1
+fsspec==2024.12.0
# via dask
-geojson==3.1.0
- # via emsarray (setup.cfg)
-idna==3.7
+geojson==3.2.0
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+idna==3.10
# via requests
iniconfig==2.0.0
# via pytest
isort==5.13.2
- # via emsarray (setup.cfg)
-jinja2==3.1.4
+ # via emsarray (pyproject.toml)
+jinja2==3.1.5
# via
# bokeh
# dask
# distributed
# pytest-mpl
-kiwisolver==1.4.5
+kiwisolver==1.4.8
# via matplotlib
locket==1.0.0
# via
@@ -76,24 +77,26 @@ locket==1.0.0
# partd
lz4==4.3.3
# via dask
-markupsafe==2.1.5
+markupsafe==3.0.2
# via jinja2
-matplotlib==3.9.0
+matplotlib==3.10.0
# via
# cartopy
- # emsarray (setup.cfg)
+ # emsarray
# pytest-mpl
mccabe==0.7.0
# via flake8
-msgpack==1.0.8
+msgpack==1.1.0
# via distributed
-mypy==1.10.1
- # via emsarray (setup.cfg)
+mypy==1.14.1
+ # via emsarray (pyproject.toml)
mypy-extensions==1.0.0
# via mypy
-netcdf4==1.7.1.post1
- # via emsarray (setup.cfg)
-numpy==2.0.0
+netcdf4==1.7.2
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+numpy==2.2.2
# via
# bokeh
# bottleneck
@@ -102,127 +105,130 @@ numpy==2.0.0
# cfunits
# contourpy
# dask
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# netcdf4
# pandas
# pandas-stubs
- # pyarrow
# pykdtree
# shapely
# xarray
-packaging==24.1
+packaging==24.2
# via
# bokeh
# cartopy
# cfunits
# dask
# distributed
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# pooch
# pytest
# pytest-mpl
# xarray
-pandas==2.2.2
+pandas==2.2.3
# via
# bokeh
# dask
- # dask-expr
# xarray
-pandas-stubs==2.2.2.240603
- # via emsarray (setup.cfg)
+pandas-stubs==2.2.3.241126
+ # via emsarray (pyproject.toml)
partd==1.4.2
# via dask
-pillow==10.4.0
+pillow==11.1.0
# via
# bokeh
# matplotlib
# pytest-mpl
-platformdirs==4.2.2
+platformdirs==4.3.6
# via pooch
pluggy==1.5.0
# via pytest
pooch==1.8.2
- # via emsarray (setup.cfg)
-psutil==6.0.0
+ # via emsarray
+psutil==6.1.1
# via distributed
-pyarrow==16.1.0
- # via
- # dask
- # dask-expr
-pyarrow-hotfix==0.6
+pyarrow==19.0.0
# via dask
-pycodestyle==2.12.0
+pycodestyle==2.12.1
# via flake8
pyflakes==3.2.0
# via flake8
-pykdtree==1.3.12
- # via emsarray (setup.cfg)
-pyparsing==3.1.2
+pykdtree==1.3.13
+ # via emsarray
+pyparsing==3.2.1
# via matplotlib
-pyproj==3.6.1
+pyproj==3.7.0
# via cartopy
pyshp==2.3.1
# via
# cartopy
- # emsarray (setup.cfg)
-pytest==8.2.2
+ # emsarray
+ # emsarray (pyproject.toml)
+pytest==8.3.4
# via
- # emsarray (setup.cfg)
+ # emsarray (pyproject.toml)
# pytest-cov
# pytest-mpl
-pytest-cov==5.0.0
- # via emsarray (setup.cfg)
+pytest-cov==6.0.0
+ # via emsarray (pyproject.toml)
pytest-mpl==0.17.0
- # via emsarray (setup.cfg)
+ # via emsarray (pyproject.toml)
python-dateutil==2.9.0.post0
# via
# matplotlib
# pandas
-pytz==2024.1
+pytz==2024.2
# via pandas
-pyyaml==6.0.1
+pyyaml==6.0.2
# via
# bokeh
# dask
# distributed
requests==2.32.3
# via pooch
-shapely==2.0.4
+shapely==2.0.6
# via
# cartopy
- # emsarray (setup.cfg)
-six==1.16.0
+ # emsarray
+ # emsarray (pyproject.toml)
+six==1.17.0
# via python-dateutil
sortedcontainers==2.4.0
# via distributed
tblib==3.0.0
# via distributed
-toolz==0.12.1
+toolz==1.0.0
# via
# dask
# distributed
# partd
-tornado==6.4.1
+tornado==6.4.2
# via
# bokeh
# distributed
-types-pytz==2024.1.0.20240417
+types-pytz==2024.2.0.20241221
# via
- # emsarray (setup.cfg)
+ # emsarray (pyproject.toml)
# pandas-stubs
typing-extensions==4.12.2
# via mypy
-tzdata==2024.1
+tzdata==2025.1
# via pandas
-urllib3==2.2.2
+urllib3==2.3.0
# via
# distributed
# requests
-xarray[parallel]==2024.6.0
- # via emsarray (setup.cfg)
-xyzservices==2024.6.0
+xarray[parallel]==2025.1.1
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+xyzservices==2025.1.0
# via bokeh
zict==3.0.0
# via distributed
+
+# The following packages are considered to be unsafe in a requirements file:
+# emsarray
diff --git a/continuous-integration/requirements-3.10.txt b/continuous-integration/requirements-3.13.txt
similarity index 55%
rename from continuous-integration/requirements-3.10.txt
rename to continuous-integration/requirements-3.13.txt
index 2aa1748..f89c8fc 100644
--- a/continuous-integration/requirements-3.10.txt
+++ b/continuous-integration/requirements-3.13.txt
@@ -1,78 +1,75 @@
#
-# This file is autogenerated by pip-compile with Python 3.10
+# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
-# pip-compile --extra=testing --output-file=./continuous-integration/requirements-3.10.txt setup.cfg
+# pip-compile --extra=testing --output-file=./continuous-integration/requirements-3.13.txt --unsafe-package=emsarray pyproject.toml
#
-bokeh==3.4.2
+bokeh==3.6.2
# via dask
-bottleneck==1.4.0
- # via emsarray (setup.cfg)
-cartopy==0.23.0
- # via emsarray (setup.cfg)
-certifi==2024.6.2
+bottleneck==1.4.2
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+cartopy==0.24.1
+ # via emsarray
+certifi==2024.12.14
# via
# netcdf4
# pyproj
# requests
-cftime==1.6.4
+cftime==1.6.4.post1
# via
# cfunits
# netcdf4
cfunits==3.3.7
- # via emsarray (setup.cfg)
-charset-normalizer==3.3.2
+ # via emsarray
+charset-normalizer==3.4.1
# via requests
-click==8.1.7
+click==8.1.8
# via
# dask
# distributed
-cloudpickle==3.0.0
+cloudpickle==3.1.1
# via
# dask
# distributed
-contourpy==1.2.1
+contourpy==1.3.1
# via
# bokeh
# matplotlib
-coverage[toml]==7.5.4
+coverage[toml]==7.6.10
# via pytest-cov
cycler==0.12.1
# via matplotlib
-dask[array,complete,dataframe,diagnostics,distributed]==2024.6.2
+dask[array,complete,dataframe,diagnostics,distributed]==2025.1.0
# via
- # dask-expr
# distributed
# xarray
-dask-expr==1.1.6
- # via dask
-distributed==2024.6.2
+distributed==2025.1.0
# via dask
-exceptiongroup==1.2.1
- # via pytest
-flake8==7.1.0
- # via emsarray (setup.cfg)
-fonttools==4.53.0
+flake8==7.1.1
+ # via emsarray (pyproject.toml)
+fonttools==4.55.4
# via matplotlib
-fsspec==2024.6.1
+fsspec==2024.12.0
# via dask
-geojson==3.1.0
- # via emsarray (setup.cfg)
-idna==3.7
+geojson==3.2.0
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+idna==3.10
# via requests
-importlib-metadata==8.0.0
- # via dask
iniconfig==2.0.0
# via pytest
isort==5.13.2
- # via emsarray (setup.cfg)
-jinja2==3.1.4
+ # via emsarray (pyproject.toml)
+jinja2==3.1.5
# via
# bokeh
# dask
# distributed
# pytest-mpl
-kiwisolver==1.4.5
+kiwisolver==1.4.8
# via matplotlib
locket==1.0.0
# via
@@ -80,24 +77,26 @@ locket==1.0.0
# partd
lz4==4.3.3
# via dask
-markupsafe==2.1.5
+markupsafe==3.0.2
# via jinja2
-matplotlib==3.9.0
+matplotlib==3.10.0
# via
# cartopy
- # emsarray (setup.cfg)
+ # emsarray
# pytest-mpl
mccabe==0.7.0
# via flake8
-msgpack==1.0.8
+msgpack==1.1.0
# via distributed
-mypy==1.10.1
- # via emsarray (setup.cfg)
+mypy==1.14.1
+ # via emsarray (pyproject.toml)
mypy-extensions==1.0.0
# via mypy
-netcdf4==1.7.1.post1
- # via emsarray (setup.cfg)
-numpy==2.0.0
+netcdf4==1.7.2
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+numpy==2.2.2
# via
# bokeh
# bottleneck
@@ -106,134 +105,130 @@ numpy==2.0.0
# cfunits
# contourpy
# dask
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# netcdf4
# pandas
# pandas-stubs
- # pyarrow
# pykdtree
# shapely
# xarray
-packaging==24.1
+packaging==24.2
# via
# bokeh
# cartopy
# cfunits
# dask
# distributed
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# pooch
# pytest
# pytest-mpl
# xarray
-pandas==2.2.2
+pandas==2.2.3
# via
# bokeh
# dask
- # dask-expr
# xarray
-pandas-stubs==2.2.2.240603
- # via emsarray (setup.cfg)
+pandas-stubs==2.2.3.241126
+ # via emsarray (pyproject.toml)
partd==1.4.2
# via dask
-pillow==10.4.0
+pillow==11.1.0
# via
# bokeh
# matplotlib
# pytest-mpl
-platformdirs==4.2.2
+platformdirs==4.3.6
# via pooch
pluggy==1.5.0
# via pytest
pooch==1.8.2
- # via emsarray (setup.cfg)
-psutil==6.0.0
+ # via emsarray
+psutil==6.1.1
# via distributed
-pyarrow==16.1.0
- # via
- # dask
- # dask-expr
-pyarrow-hotfix==0.6
+pyarrow==19.0.0
# via dask
-pycodestyle==2.12.0
+pycodestyle==2.12.1
# via flake8
pyflakes==3.2.0
# via flake8
-pykdtree==1.3.12
- # via emsarray (setup.cfg)
-pyparsing==3.1.2
+pykdtree==1.3.13
+ # via emsarray
+pyparsing==3.2.1
# via matplotlib
-pyproj==3.6.1
+pyproj==3.7.0
# via cartopy
pyshp==2.3.1
# via
# cartopy
- # emsarray (setup.cfg)
-pytest==8.2.2
+ # emsarray
+ # emsarray (pyproject.toml)
+pytest==8.3.4
# via
- # emsarray (setup.cfg)
+ # emsarray (pyproject.toml)
# pytest-cov
# pytest-mpl
-pytest-cov==5.0.0
- # via emsarray (setup.cfg)
+pytest-cov==6.0.0
+ # via emsarray (pyproject.toml)
pytest-mpl==0.17.0
- # via emsarray (setup.cfg)
+ # via emsarray (pyproject.toml)
python-dateutil==2.9.0.post0
# via
# matplotlib
# pandas
-pytz==2024.1
+pytz==2024.2
# via pandas
-pyyaml==6.0.1
+pyyaml==6.0.2
# via
# bokeh
# dask
# distributed
requests==2.32.3
# via pooch
-shapely==2.0.4
+shapely==2.0.6
# via
# cartopy
- # emsarray (setup.cfg)
-six==1.16.0
+ # emsarray
+ # emsarray (pyproject.toml)
+six==1.17.0
# via python-dateutil
sortedcontainers==2.4.0
# via distributed
tblib==3.0.0
# via distributed
-tomli==2.0.1
- # via
- # coverage
- # mypy
- # pytest
-toolz==0.12.1
+toolz==1.0.0
# via
# dask
# distributed
# partd
-tornado==6.4.1
+tornado==6.4.2
# via
# bokeh
# distributed
-types-pytz==2024.1.0.20240417
+types-pytz==2024.2.0.20241221
# via
- # emsarray (setup.cfg)
+ # emsarray (pyproject.toml)
# pandas-stubs
typing-extensions==4.12.2
# via mypy
-tzdata==2024.1
+tzdata==2025.1
# via pandas
-urllib3==2.2.2
+urllib3==2.3.0
# via
# distributed
# requests
-xarray[parallel]==2024.6.0
- # via emsarray (setup.cfg)
-xyzservices==2024.6.0
+xarray[parallel]==2025.1.1
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+xyzservices==2025.1.0
# via bokeh
zict==3.0.0
# via distributed
-zipp==3.19.2
- # via importlib-metadata
+
+# The following packages are considered to be unsafe in a requirements file:
+# emsarray
diff --git a/continuous-integration/requirements-minimum.txt b/continuous-integration/requirements-minimum.txt
index b636764..e550cdf 100644
--- a/continuous-integration/requirements-minimum.txt
+++ b/continuous-integration/requirements-minimum.txt
@@ -1,65 +1,63 @@
#
-# This file is autogenerated by pip-compile with Python 3.10
+# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
-# pip-compile --extra=complete --output-file=continuous-integration/requirements-minimum.txt setup.cfg
+# pip-compile --extra=complete --output-file=./continuous-integration/requirements-minimum.txt --strip-extras --unsafe-package=certifi --unsafe-package=emsarray --unsafe-package=pytz --unsafe-package=tzdata pyproject.toml
#
-bokeh~=3.3.0
+bokeh~=3.4.3
# via dask
-bottleneck~=1.3.0
- # via emsarray (setup.cfg)
-cartopy~=0.22.0
- # via emsarray (setup.cfg)
-certifi
+bottleneck~=1.3.8
# via
- # netcdf4
- # pyproj
- # requests
-cftime~=1.6.4
+ # emsarray
+ # emsarray (pyproject.toml)
+cartopy~=0.22.0
+ # via emsarray
+cftime~=1.6.4.post1
# via
# cfunits
# netcdf4
cfunits~=3.3.7
- # via emsarray (setup.cfg)
-charset-normalizer~=3.2.0
+ # via emsarray
+charset-normalizer~=3.3.2
# via requests
-click~=8.1.7
+click~=8.1.8
# via
# dask
# distributed
-cloudpickle~=2.2.0
+cloudpickle~=3.0.0
# via
# dask
# distributed
-contourpy~=1.1.0
+contourpy~=1.2.1
# via
# bokeh
# matplotlib
-cycler~=0.11.0
+cycler~=0.12.1
# via matplotlib
-dask[array,complete,dataframe,diagnostics,distributed]~=2023.8.0
+dask~=2024.1.1
# via
- # dask-expr
# distributed
# xarray
-distributed~=2023.8.0
+distributed~=2024.1.1
# via dask
-fonttools~=4.42.0
+fonttools~=4.47.2
# via matplotlib
-fsspec~=2023.6.0
+fsspec~=2023.12.2
# via dask
-geojson~=3.0.0
- # via emsarray (setup.cfg)
-idna~=3.4.0
+geojson~=3.1.0
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+idna~=3.6
# via requests
-importlib-metadata~=6.8.0
+importlib-metadata~=7.0.2
# via dask
-jinja2~=3.1.4
+jinja2~=3.1.5
# via
# bokeh
# dask
# distributed
-kiwisolver~=1.4.5
+kiwisolver~=1.4.8
# via matplotlib
locket~=1.0.0
# via
@@ -69,15 +67,17 @@ lz4~=4.3.3
# via dask
markupsafe~=2.1.5
# via jinja2
-matplotlib~=3.8.0
+matplotlib~=3.8.4
# via
# cartopy
- # emsarray (setup.cfg)
+ # emsarray
msgpack~=1.0.8
# via distributed
-netcdf4~=1.6.0
- # via emsarray (setup.cfg)
-numpy~=1.24.0
+netcdf4~=1.6.5
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+numpy~=1.25.2
# via
# bokeh
# bottleneck
@@ -86,7 +86,8 @@ numpy~=1.24.0
# cfunits
# contourpy
# dask
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# netcdf4
# pandas
@@ -94,94 +95,101 @@ numpy~=1.24.0
# pykdtree
# shapely
# xarray
-packaging~=23.1.0
+packaging~=23.2
# via
# bokeh
# cartopy
# cfunits
# dask
# distributed
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
# matplotlib
# pooch
# xarray
-pandas~=2.1.0
+pandas~=2.2.3
# via
# bokeh
# dask
- # dask-expr
# xarray
partd~=1.4.2
# via dask
-pillow~=10.0.0
+pillow~=10.2.0
# via
# bokeh
# matplotlib
-platformdirs~=3.10.0
+platformdirs~=4.1.0
# via pooch
-pooch~=1.7.0
- # via emsarray (setup.cfg)
-psutil~=5.9.0
+pooch~=1.8.2
+ # via emsarray
+psutil~=5.9.8
# via distributed
-pyarrow~=13.0.0
- # via
- # dask
- # dask-expr
-pyarrow-hotfix~=0.6.0
+pyarrow~=15.0.2
+ # via dask
+pyarrow-hotfix~=0.6
# via dask
-pykdtree~=1.3.12
- # via emsarray (setup.cfg)
-pyparsing~=3.1.2
+pykdtree~=1.3.13
+ # via emsarray
+pyparsing~=3.1.4
# via matplotlib
pyproj~=3.6.1
# via cartopy
pyshp~=2.3.1
# via
# cartopy
- # emsarray (setup.cfg)
-python-dateutil~=2.8.0
+ # emsarray
+ # emsarray (pyproject.toml)
+python-dateutil~=2.8.2
# via
# matplotlib
# pandas
-pytz
- # via pandas
-pyyaml~=6.0.1
+pyyaml~=6.0.2
# via
# bokeh
# dask
# distributed
requests~=2.31.0
# via pooch
-shapely~=2.0.4
+shapely~=2.0.6
# via
# cartopy
- # emsarray (setup.cfg)
+ # emsarray
+ # emsarray (pyproject.toml)
six~=1.16.0
# via python-dateutil
sortedcontainers~=2.4.0
# via distributed
-tblib~=2.0.0
+tblib~=3.0.0
# via distributed
toolz~=0.12.1
# via
# dask
# distributed
# partd
-tornado~=6.3.0
+tornado~=6.4.2
# via
# bokeh
# distributed
-tzdata
- # via pandas
-urllib3~=2.0.0
+urllib3~=2.1.0
# via
# distributed
# requests
-xarray[parallel]~=2023.8.0
- # via emsarray (setup.cfg)
-xyzservices~=2023.7.0
+xarray~=2024.1.1
+ # via
+ # emsarray
+ # emsarray (pyproject.toml)
+xyzservices~=2023.10.1
# via bokeh
zict~=3.0.0
# via distributed
-zipp~=3.16.0
+zipp~=3.17.0
# via importlib-metadata
+
+# The following packages are considered to be unsafe in a requirements file:
+# certifi
+# emsarray
+# pytz
+# tzdata
+pytz
+certifi
+tzdata
diff --git a/docs/internal/ci-dependencies.rst b/docs/internal/ci-dependencies.rst
new file mode 100644
index 0000000..d1a7bdc
--- /dev/null
+++ b/docs/internal/ci-dependencies.rst
@@ -0,0 +1,59 @@
+==============================================
+Dependency versions for continuous integration
+==============================================
+
+In order to make CI reproducible we pin the installed Python dependencies.
+These dependencies should be updated every few months in order to stay relevant.
+
+CI tests are also run against the latest of all dependencies in a separate job.
+This job is allowed to fail without breaking the build.
+It acts as a canary so we can see new breakages as they happen
+without needing to fix issues from new package versions in unrelated pull requests.
+
+Supported Python versions and dependency versions follows `SPEC-0000 `_.
+
+Updating supported Python versions
+==================================
+
+To add a new supported version of Python the following changes must be made:
+
+* Update the pinned dependencies:
+ add a new version to the list in ``scripts/update_pinned_dependencies.sh``, and
+ rebuild the pinned dependencies by running the script.
+* Update ``.github/workflows/ci.yaml``:
+ set ``env.python-version`` to the new version,
+ add the new version to the ``test`` job ``python-version`` matrix variable, and
+ update the version associated with the ``dependencies: "latest"`` matrix job.
+* Update ``tox.ini``:
+ add the new version to the ``envlist`` for pinned dependencies, and
+ update the version used in the latest dependencies.
+* Add a release note.
+
+To remove support for an old version of Python the following changes must be made:
+
+* Update the pinned dependencies:
+ remove the old version from the list in ``scripts/update_pinned_dependencies.sh``,
+ remove the old ``continuous-integration/requirements-X.YY.txt`` file, and
+ rebuild the pinned dependencies by running the script.
+* Update ``.github/workflows/ci.yaml``:
+ remove the version from the ``test`` job ``python-version`` matrix variable, and
+ update the version associated with the ``dependencies: "minimum"`` matrix job.
+* Update ``tox.ini``:
+ remove the old version from the ``envlist`` for pinned dependencies, and
+ update the version used in the minimum dependencies.
+* Update ``pyproject.toml``:
+ update the ``[project] requires-python`` field, and
+ update the ``[tool.mypy] python_version`` field.
+* Add a release note.
+
+Updating pinned dependencies
+============================
+
+To update the list of pinned dependencies used for CI
+run the ``scripts/update_pinned_dependencies.sh`` script.
+This will create an isolated conda prefix for each of the supported Python versions,
+install `pip-tools `_,
+and create a fresh requirements document.
+Commit the changes to the pinned requirements,
+run the test suite against all supported Python versions, and
+fix any new issues that appear.
diff --git a/docs/internal/index.rst b/docs/internal/index.rst
index 2fd0c85..553484a 100644
--- a/docs/internal/index.rst
+++ b/docs/internal/index.rst
@@ -8,3 +8,4 @@ Internal documentation
:maxdepth: 1
releasing
+ ci-dependencies
diff --git a/docs/releases/development.rst b/docs/releases/development.rst
index b97a90e..3c0ecf7 100644
--- a/docs/releases/development.rst
+++ b/docs/releases/development.rst
@@ -6,3 +6,7 @@ Next release (in development)
connectivity(:issue:`165`, :pr:`168`).
* Fix datasets hash_key generation when geometry encoding
is missing a dtype (:issue:`166`, :pr:`167`).
+* Bumped minimum versions of Python and package dependencies in line with
+ `SPEC-0000 `_.
+ Support for Python 3.10 was dropped, and support for Python 3.13 was added
+ (:pr:`169`).
diff --git a/pyproject.toml b/pyproject.toml
index d1d65e7..356e0a6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,16 +12,16 @@ authors = [
{name = "Coastal Environmental Modelling team, Oceans and Atmosphere, CSIRO", email = "coasts@csiro.au"},
]
license = {file = "LICENSE"}
-requires-python = ">=3.10"
+requires-python = ">=3.11"
dependencies = [
"bottleneck >=1.3",
- "geojson >=3.0",
+ "geojson >=3.1",
"netcdf4 >=1.6.4",
- "numpy >=1.24",
- "packaging >=23.1",
+ "numpy >=1.25",
+ "packaging >=23.2",
"shapely >=2.0",
"pyshp >=2.3",
- "xarray[parallel] >=2023.8",
+ "xarray[parallel] >=2024.1",
]
dynamic = ["readme"]
@@ -40,7 +40,7 @@ plot = [
]
tutorial = [
- "pooch >=1.7",
+ "pooch >=1.8",
]
complete = [
@@ -109,7 +109,7 @@ mpl-use-full-test-name = true
# mpl-baseline-path = "tests/baseline_images"
[tool.mypy]
-python_version = "3.10"
+python_version = "3.11"
plugins = ["numpy.typing.mypy_plugin"]
disallow_untyped_defs = true
diff --git a/scripts/min_deps_check.py b/scripts/min_deps_check.py
index 86a5a79..6c2a4d5 100755
--- a/scripts/min_deps_check.py
+++ b/scripts/min_deps_check.py
@@ -1,21 +1,28 @@
#!/usr/bin/env python
"""
-Fetch from conda database all available versions of the emsarray dependencies and their
-publication date. Compare it against continuous-integration/min-deps.yaml to verify the
-policy on obsolete dependencies is being followed. Print a pretty report :)
+Fetch from PyPI all available versions of the emsarray dependencies and their
+publication date. Compare it against continuous-integration/requirements-minimum.txt
+to verify the policy on obsolete dependencies is being followed.
+Update the pinned dependencies using `pip-compile`.
Based heavily on `min_deps_check.py` from xarray
but rewritten to pull requirements from a Python requirements.txt and available versions from PyPI.
Needs the following extra deps installed:
- $ pip3 install packaging requests python-dateutil
+ $ pip3 install packaging requests python-dateutil pip-tools
+
+This is automatically run as part of the `scripts/update_pinned_dependencies.sh` script.
See also
========
https://github.com/pydata/xarray/blob/v2024.06.0/ci/min_deps_check.py
"""
+import dataclasses
import datetime
+import enum
+import itertools
+import subprocess
import sys
from collections.abc import Iterator
@@ -24,6 +31,23 @@
import requests
from dateutil.relativedelta import relativedelta
+
+class VersionStatus(enum.StrEnum):
+ older = '<'
+ current = '~='
+ newer = '>'
+
+
+@dataclasses.dataclass
+class Dependency:
+ package_name: str
+ requirements_version: packaging.version.Version
+ requirements_date: datetime.date | None
+ policy_version: packaging.version.Version
+ policy_date: datetime.date | None
+ status: VersionStatus
+
+
IGNORE_DEPS: set[str] = {
'certifi',
'pytz',
@@ -49,13 +73,12 @@ def warning(msg: str) -> None:
def parse_requirements(
- fname: str
+ filename: str
) -> Iterator[tuple[str, packaging.specifiers.Specifier, packaging.version.Version]]:
- """Load requirements/min-all-deps.yml
-
- Yield (package name, major version, minor version, patch version)
"""
- for line_number, line in enumerate(open(fname), start=1):
+ Parse a requirements file, yield (package name, specifier, version) for each requirement.
+ """
+ for line_number, line in enumerate(open(filename), start=1):
if '#' in line:
line = line[:line.index('#')]
line = line.strip()
@@ -71,9 +94,6 @@ def parse_requirements(
continue
specifier = next(iter(requirement.specifier))
- if specifier.operator != '~=':
- error(f"Specificity for dependency {requirement.name} should be '~='")
-
version = packaging.version.parse(specifier.version)
if version.micro is None:
warning(
@@ -110,7 +130,7 @@ def process_pkg(
pkg: str,
specifier: packaging.specifiers.Specifier,
version: packaging.version.Version,
-) -> tuple[str, str, str, str, str, str]:
+) -> Dependency:
"""Compare package version from requirements file to available versions in conda.
Return row to build pandas dataframe:
@@ -148,7 +168,7 @@ def process_pkg(
policy_version = packaging.version.parse(policy_specifier.version)
# Find the release date of the policy version
- policy_release_date = min(
+ policy_date = min(
(
release_date
for release_version, release_date
@@ -158,17 +178,17 @@ def process_pkg(
)
if version in policy_specifier:
- status = "~="
+ status = VersionStatus.current
else:
if version < policy_version:
- status = '<'
+ status = VersionStatus.older
warning(
f"Requirement {pkg} {version} was published on {req_published:%Y-%m-%d} "
f"which is older than the required {policy_months} months of support. "
f"Minimum policy supported version is {pkg}{policy_specifier}."
)
elif version > policy_version:
- status = '> (!)'
+ status = VersionStatus.newer
if req_published is None:
error(
f"Package version is newer than policy version. "
@@ -186,26 +206,96 @@ def process_pkg(
f"Update requirement to {pkg}{policy_specifier}."
)
- return (
- pkg,
- str(version),
- req_published.strftime("%Y-%m-%d") if req_published else "-",
- str(policy_version),
- policy_release_date.strftime("%Y-%m-%d") if policy_release_date else "-",
- status,
+ return Dependency(
+ package_name=pkg,
+ requirements_version=version,
+ requirements_date=req_published,
+ policy_version=policy_version,
+ policy_date=policy_date,
+ status=status,
)
def main() -> None:
+ if len(sys.argv) < 2:
+ print(f"Usage: {sys.argv[0]} continuous-integration/requirements-minimum.txt")
+ sys.exit(1)
+
requirements_file = sys.argv[1]
- rows = [process_pkg(pkg, specifier, version) for pkg, specifier, version in parse_requirements(requirements_file)]
+ dependencies = [
+ process_pkg(pkg, specifier, version)
+ for pkg, specifier, version in parse_requirements(requirements_file)
+ ]
print()
print("Package Required Status Policy ")
print("-------------------- ----------------------- ------ -----------------------")
fmt = "{0:20} {1:10} ({2:10}) {5:^6} {3:10} ({4:10})"
- for row in rows:
- print(fmt.format(*row))
+ for d in dependencies:
+ requirements_date = (
+ d.requirements_date.strftime("%Y-%m-%d")
+ if d.requirements_date is not None
+ else "-"
+ )
+ policy_date = (
+ d.policy_date.strftime("%Y-%m-%d")
+ if d.policy_date is not None
+ else "-"
+ )
+ print(
+ f"{d.package_name:20} {d.requirements_version!s:10} ({requirements_date:10}) "
+ f"{d.status:^6} {d.policy_version!s:10} ({policy_date:10})"
+ )
+
+ # These packages need upgrading
+ upgrade_args = list(itertools.chain.from_iterable(
+ ['--upgrade-package', f'{d.package_name}~={d.policy_version}']
+ for d in dependencies
+ if d.status is VersionStatus.older
+ ))
+
+ # These packages should be kept where they are
+ maintain_args = list(itertools.chain.from_iterable(
+ ['--upgrade-package', f'{d.package_name}~={d.requirements_version}']
+ for d in dependencies
+ if d.status in {VersionStatus.current, VersionStatus.newer}
+ ))
+ # These packages should be installed fresh every time, so ignore them for now.
+ ignored_args = list(itertools.chain.from_iterable(
+ ['--unsafe-package', ignored]
+ for ignored in IGNORE_DEPS
+ ))
+ cmd = [
+ 'pip-compile',
+ '--quiet',
+ '--extra', 'complete',
+ '--strip-extras',
+ '--unsafe-package', 'emsarray',
+ '--no-allow-unsafe',
+ # '--no-header',
+ # '--no-annotate',
+ '--output-file', requirements_file,
+ ] + upgrade_args + maintain_args + ignored_args + [
+ 'pyproject.toml',
+ ]
+
+ # pip-compile always prints '==' specifiers,
+ # but we want the latest point release in the minimum policy version.
+ subprocess.check_call(cmd)
+ cmd = [
+ 'sed',
+ '-i',
+ 's/==/~=/',
+ requirements_file,
+ ]
+ subprocess.check_call(cmd)
+
+ # CI will install strictly the requirements present in this file and none more,
+ # so append the ignored deps at the end with no version specifiers.
+ with open(requirements_file, "a") as f:
+ for ignored in IGNORE_DEPS:
+ f.write(ignored)
+ f.write("\n")
if errors:
print("\nErrors:")
diff --git a/scripts/update_pinned_dependencies.sh b/scripts/update_pinned_dependencies.sh
index 39a2b77..23a5f40 100755
--- a/scripts/update_pinned_dependencies.sh
+++ b/scripts/update_pinned_dependencies.sh
@@ -2,7 +2,7 @@
set -e
-PYTHON_VERSIONS=('3.10' '3.11' '3.12')
+PYTHON_VERSIONS=('3.11' '3.12' '3.13')
HERE="$( cd -- "$( realpath -- "$( dirname -- "$0" )" )" && pwd )"
PROJECT_ROOT="$( dirname "$HERE" )"
@@ -11,6 +11,27 @@ cd "$PROJECT_ROOT"
conda_venv_root=$( mktemp -d emsarray-conda-environments.XXXXXXX )
echo "Working in ${conda_venv_root}"
+version="${PYTHON_VERSIONS[0]}"
+requirements_file="./continuous-integration/requirements-minimum.txt"
+echo "Updating $requirements_file"
+conda_prefix="${conda_venv_root}/py-min"
+conda create \
+ --yes --quiet \
+ --prefix="${conda_prefix}" \
+ --no-default-packages
+conda install \
+ --yes \
+ --prefix="${conda_prefix}" \
+ --channel conda-forge \
+ "python=${version}" pip
+conda run \
+ --prefix="${conda_prefix}" \
+ pip install pip-tools packaging requests python-dateutil pip-tools
+conda run \
+ --prefix="${conda_prefix}" \
+ python3 ./scripts/min_deps_check.py "$requirements_file"
+conda env remove --yes --prefix="${conda_prefix}"
+
for version in "${PYTHON_VERSIONS[@]}" ; do
requirements_file="./continuous-integration/requirements-${version}.txt"
echo "Updating $requirements_file"
@@ -23,6 +44,7 @@ for version in "${PYTHON_VERSIONS[@]}" ; do
conda install \
--yes \
--prefix="${conda_prefix}" \
+ --channel conda-forge \
"python=${version}" \
pip-tools
conda run \
@@ -31,8 +53,11 @@ for version in "${PYTHON_VERSIONS[@]}" ; do
--upgrade \
--extra="testing" \
--output-file="${requirements_file}" \
- setup.cfg
+ --unsafe-package emsarray \
+ --no-allow-unsafe \
+ pyproject.toml
conda env remove --yes --prefix="${conda_prefix}"
done
+
echo rm -rf "$conda_venv_root"
diff --git a/src/emsarray/conventions/ugrid.py b/src/emsarray/conventions/ugrid.py
index 0c1bd52..7010b7f 100644
--- a/src/emsarray/conventions/ugrid.py
+++ b/src/emsarray/conventions/ugrid.py
@@ -1006,7 +1006,7 @@ def edge_count(self) -> int:
return self.dataset.sizes[self.edge_dimension]
# By computing the edge_node array we can determine how many edges exist
- return self.edge_node_array.shape[0]
+ return cast(int, self.edge_node_array.shape[0])
@property
def face_count(self) -> int:
diff --git a/src/emsarray/operations/cache.py b/src/emsarray/operations/cache.py
index 4e80cf5..06bc1f0 100644
--- a/src/emsarray/operations/cache.py
+++ b/src/emsarray/operations/cache.py
@@ -19,6 +19,7 @@
"""
import hashlib
import marshal
+from typing import cast
import numpy
import xarray
@@ -146,7 +147,7 @@ def make_cache_key(dataset: xarray.Dataset, hash: "hashlib._Hash | None" = None)
and should not be relied upon.
"""
if hash is None:
- hash = hashlib.blake2b(digest_size=32)
+ hash = cast("hashlib._Hash", hashlib.blake2b(digest_size=32))
dataset.ems.hash_geometry(hash)
diff --git a/src/emsarray/operations/geometry.py b/src/emsarray/operations/geometry.py
index 2ef472d..62229e8 100644
--- a/src/emsarray/operations/geometry.py
+++ b/src/emsarray/operations/geometry.py
@@ -7,7 +7,7 @@
import pathlib
from collections.abc import Generator, Iterable, Iterator
from contextlib import contextmanager
-from typing import IO, Any, TypeVar
+from typing import IO, Any, Generic, TypeVar
import geojson
import shapefile
@@ -19,7 +19,7 @@
T = TypeVar('T')
-class _dumpable_iterator(list):
+class _dumpable_iterator(Generic[T], list):
"""
Wrap an iterator / generator so it can be used in `json.dumps()`.
No guarantees that it works for anything else!
diff --git a/src/emsarray/operations/triangulate.py b/src/emsarray/operations/triangulate.py
index 8981814..b0a2bcc 100644
--- a/src/emsarray/operations/triangulate.py
+++ b/src/emsarray/operations/triangulate.py
@@ -158,7 +158,7 @@ def _add_triangles(face_index: int, vertex_triangles: numpy.ndarray) -> None:
vertex_triangles = _triangulate_polygons_by_length(same_length_polygons)
for face_index, triangles in zip(same_length_face_indices, vertex_triangles):
- _add_triangles(face_index, triangles)
+ _add_triangles(int(face_index), triangles)
# Triangulate each concave polygon using a slower manual method.
# Anecdotally concave polygons are very rare,
@@ -166,7 +166,7 @@ def _add_triangles(face_index: int, vertex_triangles: numpy.ndarray) -> None:
for face_index in polygon_is_concave:
polygon = polygons[face_index]
triangles = _triangulate_concave_polygon(polygon)
- _add_triangles(face_index, triangles)
+ _add_triangles(int(face_index), triangles)
# Check that we have handled each triangle we expected.
assert current_face == total_triangles
diff --git a/src/emsarray/utils.py b/src/emsarray/utils.py
index 6e54c02..642c022 100644
--- a/src/emsarray/utils.py
+++ b/src/emsarray/utils.py
@@ -228,7 +228,7 @@ def fix_time_units_for_ems(
"""
with netCDF4.Dataset(dataset_path, 'r+') as dataset:
- variable = dataset.variables[variable_name]
+ variable = dataset.variables[str(variable_name)]
units = cast(str, variable.getncattr('units'))
calendar = cast(str, variable.getncattr('calendar') or DEFAULT_CALENDAR)
@@ -668,7 +668,11 @@ def wind_dimension(
return xarray.DataArray(data=new_data, dims=new_dims)
-def datetime_from_np_time(np_time: numpy.datetime64) -> datetime.datetime:
+def datetime_from_np_time(
+ np_time: numpy.datetime64,
+ *,
+ tz: datetime.tzinfo = datetime.timezone.utc,
+) -> datetime.datetime:
"""
Convert a numpy :class:`~numpy.datetime64`
to a python :class:`~datetime.datetime`.
@@ -682,8 +686,24 @@ def datetime_from_np_time(np_time: numpy.datetime64) -> datetime.datetime:
A conversion that truncates data is not reported as an error.
If you're using numpy datetime64 with attosecond accuracy,
the Python datetime formatting methods are insufficient for your needs anyway.
+
+ Parameters
+ ==========
+ np_time : numpy.datetime64
+ The numpy datetime64 to convert to a Python datetime.
+ tz : datetime.tzinfo
+ The timezone that the numpy datetime is in.
+ Defaults to UTC, as xarray will convert all time variables to UTC when
+ opening files.
+
+ Returns
+ =======
+ datetime.datetime
+ A timezone aware Python datetime.datetime instance.
"""
- return datetime.datetime.fromtimestamp(np_time.item() / 10**9)
+ epoc = numpy.datetime64('1970-01-01')
+ timestamp = (np_time - epoc).astype('timedelta64[ns]')
+ return datetime.datetime.fromtimestamp(timestamp.astype(float) / 1e9, tz=tz)
class RequiresExtraException(Exception):
diff --git a/tox.ini b/tox.ini
index 8dba14e..a3832d3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,9 +3,9 @@ isolated_build = true
package = wheel
wheel_build_env = .pkg
envlist =
- py{310,311,312}-pytest-pinned
- py310-pytest-minimum
- py312-pytest-latest
+ py{311,312,313}-pytest-pinned
+ py311-pytest-minimum
+ py313-pytest-latest
lint,docs
skip_missing_interpreters = true
@@ -18,12 +18,12 @@ sitepackages = false
passenv =
UDUNITS2_XML_PATH
-[testenv:py{310,311,312}-pytest-{pinned,latest,minimum}]
+[testenv:py{311,312,313}-pytest-{pinned,latest,minimum}]
description = "Run the pytest test suite against a specific Python version and dependencies"
deps =
- py310-pinned: -rcontinuous-integration/requirements-3.10.txt
py311-pinned: -rcontinuous-integration/requirements-3.11.txt
py312-pinned: -rcontinuous-integration/requirements-3.12.txt
+ py313-pinned: -rcontinuous-integration/requirements-3.13.txt
minimum: -rcontinuous-integration/requirements-minimum.txt
extras =
latest: testing