Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,24 @@ jobs:
fail-fast: false
matrix:
session: ["tests", "doctests-docs", "doctests-api"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
exclude:
- session: "doctests-docs"
python-version: "3.10"
- session: "doctests-docs"
python-version: "3.11"
- session: "doctests-docs"
python-version: "3.12"
- session: "doctests-docs"
python-version: "3.13"
- session: "doctests-api"
python-version: "3.10"
- session: "doctests-api"
python-version: "3.11"
- session: "doctests-api"
python-version: "3.12"
- session: "doctests-api"
python-version: "3.13"

defaults:
run:
Expand Down
1 change: 1 addition & 0 deletions docs/changelog_fragments/255.bug.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixes to work with Iris >= v3.16 and xarray > v2026.02.
1 change: 1 addition & 0 deletions docs/changelog_fragments/258.dev.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support and test against Python 3.14.
3 changes: 2 additions & 1 deletion docs/details/interface_support.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Support for Interfaces and Dependencies

Python Compatibility
--------------------
NcData currently supports Python >= 3.10, and is tested against 3.10, 3.11, 3.12 and 3.13.
NcData currently supports Python >= 3.10, and is tested against
3.10, 3.11, 3.12, 3.13 and 3.14.

NetCDF4 Compatibility
---------------------
Expand Down
6 changes: 3 additions & 3 deletions docs/userdocs/getting_started/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,9 @@ Example code snippets :
Dimensions: (dim0: 3)
Dimensions without coordinates: dim0
Data variables:
vx0 (dim0) int64 ... dask.array<chunksize=(3,), meta=numpy.ma.MaskedArray>
vx1 (dim0) int64 ... dask.array<chunksize=(3,), meta=numpy.ma.MaskedArray>
v_mag (dim0) float64 ... dask.array<chunksize=(3,), meta=numpy.ma.MaskedArray>
vx0 (dim0) ... dask.array<chunksize=(3,), ...
vx1 (dim0) ... dask.array<chunksize=(3,), ...
v_mag (dim0) ... dask.array<chunksize=(3,), ...
Attributes:
Conventions: CF-1.7

Expand Down
5 changes: 5 additions & 0 deletions lib/ncdata/dataset_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ def __setattr__(self, attr, value):
else:
self.setncattr(attr, value)

def set_auto_chartostring(self, value):
if bool(value):
msg = "Cannot enable 'auto_chartostring' for Nc4Dataselike or Nc4VariableLike."
raise ValueError(msg)


class Nc4DatasetLike(_Nc4DatalikeWithNcattrs):
"""
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering",
]
dynamic = [
Expand Down
2 changes: 1 addition & 1 deletion requirements/readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies:
- pip
- pydata-sphinx-theme
- pytest
- python<3.14
- python<3.15
- sphinx
- sphinxcontrib-napoleon
- sphinx-copybutton
Expand Down
21 changes: 21 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,24 @@ def __getitem__(self, keys):
"""Fetch indexed data section."""
self._accesses.append(keys)
return self._data[keys]


def version_tuple(version_string):
"""Make a comparable tuple from a package version string.

Allows only the final segment to have non-numeric parts.
Returns a tuple of the parts.
"""
parts = version_string.split(".")
last_part = parts[-1]
while len(last_part) and not last_part[-1].isdigit():
last_part = last_part[:-1]
if len(last_part):
parts[-1] = last_part
else:
parts = parts[:-1]
if any(str(int(part)) != part for part in parts):
raise ValueError(
f"invalid numerics in version string: {version_string!r}"
)
return tuple(int(part) for part in parts)
5 changes: 3 additions & 2 deletions tests/data_testcase_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ def _define_iris_testdata_testcases():
# "_ORCA2__votemper",
# ]
EXCLUDES = []
for filepath in _netcdf_testfile_paths:
for filepath in sorted(_netcdf_testfile_paths):
param_name = str(filepath)
# remove unwanted path elements
param_name = param_name.replace(str(_testdirpath), "")
Expand Down Expand Up @@ -434,7 +434,7 @@ def _define_unit_dtype_testcases():
name="vx",
dims=["strlen"],
dtype="S1",
data=np.array("abc", dtype="S1"),
data=np.array(list("abc"), dtype="S1"),
)
],
}
Expand Down Expand Up @@ -525,6 +525,7 @@ def standard_testcase(request, session_testdir):
# """
"small_rotPole_precipitation",
"small_FC_167",
"test_monotonic_coordinate",
],
# Xarray can save ~anything
"save": [r"test_monotonic_coordinate"],
Expand Down
18 changes: 12 additions & 6 deletions tests/integration/test_iris_load_and_save_equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ncdata.netcdf4 import from_nc4, to_nc4
from ncdata.utils import dataset_differences

from tests import version_tuple
from tests.data_testcase_schemas import session_testdir, standard_testcase
from tests.integration.equivalence_testing_utils import (
adjust_chunks,
Expand Down Expand Up @@ -56,12 +57,15 @@ def test_load_direct_vs_viancdata(
# yet account for this.
# TODO: fix in Nc4VariableLike, when we are sure of the interpretation
"label_and_climate__small_FC_167_mon_19601101",
# Some of the legacy UGRID unstructured files have incorrect encodings
# which are currently causing loading errors in Iris since UGRID loading
# became an always-on thing in v3.11
"unstructured_grid__theta_nodal_xios",
"ugrid__21_triangle_example",
]
if version_tuple(iris.__version__) < version_tuple("3.13"):
# TODO: remove backwards compatibility when we drop Python 3.10 support
specific_excludes.extend(
[
"ugrid__21_triangle_example",
"theta_nodal_xios",
]
)
if any(
name_fragment in standard_testcase.name
for name_fragment in specific_excludes
Expand Down Expand Up @@ -121,10 +125,12 @@ def test_save_direct_vs_viancdata(standard_testcase, tmp_path):
# Some of the legacy UGRID unstructured files have incorrect encodings
# which are currently causing loading errors in Iris since UGRID loading
# became an always-on thing in v3.11
"unstructured_grid__theta_nodal_xios",
"unstructured_grid__mesh_C12",
"ugrid__21_triangle_example",
]
if version_tuple(iris.__version__) < version_tuple("3.13"):
# TODO: remove backwards compatibility when we drop Python 3.10 support
specific_excludes.append("theta_nodal_xios")
if any(
name_fragment in standard_testcase.name
for name_fragment in specific_excludes
Expand Down
35 changes: 21 additions & 14 deletions tests/integration/test_iris_xarray_roundtrips.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from ncdata.utils import dataset_differences
from ncdata.xarray import from_xarray

from tests import version_tuple
from tests.data_testcase_schemas import (
BAD_LOADSAVE_TESTCASES,
session_testdir,
Expand Down Expand Up @@ -70,20 +71,34 @@ def test_roundtrip_ixi(standard_testcase, use_irislock, adjust_chunks):
+ BAD_LOADSAVE_TESTCASES["xarray"]["load"]
# TODO: remaining unresolved problems ...
+ [
# string dimension problem
"ds__dtype__string",
# outstanding dims-mismatch problems.
"testing__small_theta_colpex",
# all-masked variables --> changing dtype, since xarray>v2026.02.0
"testing__cell_methods",
# coordinate attributes on mesh coordinate variables
"testdata____unstructured_grid__data_C4",
"testdata____ugrid__21_triangle_example",
# Problem with units on time bounds
"label_and_climate__small_FC_167",
# Broken UGRID files now won't load in Iris >= 3.10
"unstructured_grid__mesh_C12",
"_unstructured_grid__theta_nodal_xios",
# **No** mesh files will now roundtrip, since xarray 2026.04.0.
# This is because xarray now normalises the array type when loading
# from ncdata, removing masks and converting to nanarrays (thus making ints
# into floats).
# Previously we could create xarray Datasets with masked integer variables
# as data -- though that was alwaysobviously a bit tricksy.
# Iris can't accept the new form as it insists on integer types for
# mesh connectivities :-(
# TODO: do something to get this working again?
"unstructured",
]
)
if version_tuple(iris.__version__) < version_tuple("3.16"):
# TODO: remove backwards compatibility when we drop Python 3.11 support
exclude_case_keys.extend(
[
"ds__dtype__string",
"ds__stringvar",
]
)
if any(key in standard_testcase.name for key in exclude_case_keys):
pytest.skip("excluded testcase")

Expand Down Expand Up @@ -209,14 +224,6 @@ def test_roundtrip_xix(
# float time(time) ;
# time:bounds = 'time_bnds'
# float time_bnds(time, time_bnds) ;
"label_and_climate__small_FC_167",
"rotated__xyt__small_rotPole_precipitation",
# This one fails to load in xarray, for somewhat unclear reasons
# NotImplementedError: Can not use auto rechunking with object dtype.
# We are unable to estimate the size in bytes of object data
"unstructured_grid__lfric_surface_mean",
# Iris loses the name of the unstructured dimension, causing multiple problems
"unstructured_grid__data_C4",
]
if any(key in standard_testcase.name for key in excluded_casename_keys):
pytest.skip("excluded testcase")
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/tests/test_version_tuple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Tests for the test utility :func:`tests.version_majorminor`.
"""

import pytest

from tests import version_tuple


def test_empty():
assert version_tuple("") == ()


def test_basic():
assert version_tuple("1.2.3") == (1, 2, 3)


def test_one():
assert version_tuple("1") == (1,)


@pytest.mark.parametrize("tail", ["alpha", ".alpha", "."])
@pytest.mark.parametrize("base", ["1", "1.2"])
def test_valid_tails(tail, base):
assert version_tuple(base + tail) == version_tuple(base)


@pytest.mark.parametrize("version", ["a1.", "1a.2", "a1.2", "1.2a."])
def test_invalid_nonnumerics(version):
with pytest.raises(ValueError, match="invalid"):
version_tuple(version)


def test_interlength_compares():
assert version_tuple("2") > version_tuple("1.99")
assert version_tuple("1.2.3") > version_tuple("1.1")
assert version_tuple("1.2.3") < version_tuple("1.3")
Loading