From faaf7252d5dcbf867c56b9642551224a17dda032 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:32:37 +0200 Subject: [PATCH 1/6] Move test_datasets.py --- tests/{test_datasets.py => datasets/test_structured.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_datasets.py => datasets/test_structured.py} (100%) diff --git a/tests/test_datasets.py b/tests/datasets/test_structured.py similarity index 100% rename from tests/test_datasets.py rename to tests/datasets/test_structured.py From a4e6662bbc73d3eb1a4601e6f17a7a5530e1e066 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:34:59 +0200 Subject: [PATCH 2/6] Refactor strategies --- tests/strategies.py | 58 +++++++++++++++++++++++++++++++++++++++ tests/utils/test_time.py | 59 ++++------------------------------------ 2 files changed, 64 insertions(+), 53 deletions(-) create mode 100644 tests/strategies.py diff --git a/tests/strategies.py b/tests/strategies.py new file mode 100644 index 0000000000..e4ef55ea60 --- /dev/null +++ b/tests/strategies.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from datetime import datetime + +import numpy as np +from cftime import datetime as cftime_datetime +from hypothesis import strategies as st + +from parcels._core.utils.time import ( + TimeInterval, +) + +calendar_strategy = st.sampled_from( + [ + "gregorian", + "proleptic_gregorian", + "365_day", + "360_day", + "julian", + "366_day", + np.datetime64, + datetime, + np.timedelta64, + ] +) + + +@st.composite +def np_timedelta64_strategy(draw): + """Strategy for generating np.timedelta64 objects.""" + return np.timedelta64(draw(st.integers(1, 60 * 60 * 24 * 100 * 365)), "s") + + +@st.composite +def datetime_strategy(draw, calendar=None): + if calendar is None: + calendar = draw(calendar_strategy) + if calendar is np.timedelta64: + return draw(np_timedelta64_strategy()) + + year = draw(st.integers(1900, 2100)) + month = draw(st.integers(1, 12)) + day = draw(st.integers(1, 28)) + if calendar is datetime: + return datetime(year, month, day) + if calendar is np.datetime64: + return np.datetime64(datetime(year, month, day)) + + return cftime_datetime(year, month, day, calendar=calendar) + + +@st.composite +def time_interval_strategy(draw, left=None, calendar=None): + if left is None: + left = draw(datetime_strategy(calendar=calendar)) + right = left + draw(np_timedelta64_strategy()) + + return TimeInterval(left, right) diff --git a/tests/utils/test_time.py b/tests/utils/test_time.py index 26cc39c1c9..64fa38141f 100644 --- a/tests/utils/test_time.py +++ b/tests/utils/test_time.py @@ -6,8 +6,8 @@ import pytest from cftime import datetime as cftime_datetime from hypothesis import given -from hypothesis import strategies as st +import tests.strategies as pst # parcels strategies from parcels._core.utils.time import ( TimeInterval, _get_cf_attrs, @@ -15,53 +15,6 @@ timedelta_to_float, ) -calendar_strategy = st.sampled_from( - [ - "gregorian", - "proleptic_gregorian", - "365_day", - "360_day", - "julian", - "366_day", - np.datetime64, - datetime, - np.timedelta64, - ] -) - - -@st.composite -def np_timedelta64_strategy(draw): - """Strategy for generating np.timedelta64 objects.""" - return np.timedelta64(draw(st.integers(1, 60 * 60 * 24 * 100 * 365)), "s") - - -@st.composite -def datetime_strategy(draw, calendar=None): - if calendar is None: - calendar = draw(calendar_strategy) - if calendar is np.timedelta64: - return draw(np_timedelta64_strategy()) - - year = draw(st.integers(1900, 2100)) - month = draw(st.integers(1, 12)) - day = draw(st.integers(1, 28)) - if calendar is datetime: - return datetime(year, month, day) - if calendar is np.datetime64: - return np.datetime64(datetime(year, month, day)) - - return cftime_datetime(year, month, day, calendar=calendar) - - -@st.composite -def time_interval_strategy(draw, left=None, calendar=None): - if left is None: - left = draw(datetime_strategy(calendar=calendar)) - right = left + draw(np_timedelta64_strategy()) - - return TimeInterval(left, right) - @pytest.mark.parametrize( "left,right", @@ -83,7 +36,7 @@ def test_time_interval_initialization(left, right): TimeInterval(right, left) -@given(time_interval_strategy()) +@given(pst.time_interval_strategy()) def test_time_interval_contains(interval): left = 0 right = timedelta_to_float(interval.right - interval.left) @@ -94,12 +47,12 @@ def test_time_interval_contains(interval): assert interval.is_all_time_in_interval(middle) -@given(time_interval_strategy(calendar="365_day"), time_interval_strategy(calendar="365_day")) +@given(pst.time_interval_strategy(calendar="365_day"), pst.time_interval_strategy(calendar="365_day")) def test_time_interval_intersection_commutative(interval1, interval2): assert interval1.intersection(interval2) == interval2.intersection(interval1) -@given(time_interval_strategy()) +@given(pst.time_interval_strategy()) def test_time_interval_intersection_with_self(interval): assert interval.intersection(interval) == interval @@ -111,7 +64,7 @@ def test_time_interval_repr(): assert repr(interval) == expected -@given(time_interval_strategy()) +@given(pst.time_interval_strategy()) def test_time_interval_equality(interval): assert interval == interval @@ -222,7 +175,7 @@ def test_timedelta_to_float_exceptions(): timedelta_to_float("invalid_type") -@given(datetime_strategy()) +@given(pst.datetime_strategy()) def test_datetime_get_cf_attrs(dt): attrs = _get_cf_attrs(dt) assert "seconds" in attrs["units"] From c37d655721550bc95aabf98e0e26830810a56fa3 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:47:21 +0200 Subject: [PATCH 3/6] Rename strategies --- tests/strategies.py | 16 ++++++++-------- tests/utils/test_time.py | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/strategies.py b/tests/strategies.py index e4ef55ea60..ba398a57d1 100644 --- a/tests/strategies.py +++ b/tests/strategies.py @@ -10,7 +10,7 @@ TimeInterval, ) -calendar_strategy = st.sampled_from( +cf_calendar = st.sampled_from( [ "gregorian", "proleptic_gregorian", @@ -26,17 +26,17 @@ @st.composite -def np_timedelta64_strategy(draw): +def np_timedelta64(draw): """Strategy for generating np.timedelta64 objects.""" return np.timedelta64(draw(st.integers(1, 60 * 60 * 24 * 100 * 365)), "s") @st.composite -def datetime_strategy(draw, calendar=None): +def datetime_various(draw, calendar=None): if calendar is None: - calendar = draw(calendar_strategy) + calendar = draw(cf_calendar) if calendar is np.timedelta64: - return draw(np_timedelta64_strategy()) + return draw(np_timedelta64()) year = draw(st.integers(1900, 2100)) month = draw(st.integers(1, 12)) @@ -50,9 +50,9 @@ def datetime_strategy(draw, calendar=None): @st.composite -def time_interval_strategy(draw, left=None, calendar=None): +def time_interval(draw, left=None, calendar=None): if left is None: - left = draw(datetime_strategy(calendar=calendar)) - right = left + draw(np_timedelta64_strategy()) + left = draw(datetime_various(calendar=calendar)) + right = left + draw(np_timedelta64()) return TimeInterval(left, right) diff --git a/tests/utils/test_time.py b/tests/utils/test_time.py index 64fa38141f..d8b0d10612 100644 --- a/tests/utils/test_time.py +++ b/tests/utils/test_time.py @@ -36,7 +36,7 @@ def test_time_interval_initialization(left, right): TimeInterval(right, left) -@given(pst.time_interval_strategy()) +@given(pst.time_interval()) def test_time_interval_contains(interval): left = 0 right = timedelta_to_float(interval.right - interval.left) @@ -47,12 +47,12 @@ def test_time_interval_contains(interval): assert interval.is_all_time_in_interval(middle) -@given(pst.time_interval_strategy(calendar="365_day"), pst.time_interval_strategy(calendar="365_day")) +@given(pst.time_interval(calendar="365_day"), pst.time_interval(calendar="365_day")) def test_time_interval_intersection_commutative(interval1, interval2): assert interval1.intersection(interval2) == interval2.intersection(interval1) -@given(pst.time_interval_strategy()) +@given(pst.time_interval()) def test_time_interval_intersection_with_self(interval): assert interval.intersection(interval) == interval @@ -64,7 +64,7 @@ def test_time_interval_repr(): assert repr(interval) == expected -@given(pst.time_interval_strategy()) +@given(pst.time_interval()) def test_time_interval_equality(interval): assert interval == interval @@ -175,7 +175,7 @@ def test_timedelta_to_float_exceptions(): timedelta_to_float("invalid_type") -@given(pst.datetime_strategy()) +@given(pst.datetime_various()) def test_datetime_get_cf_attrs(dt): attrs = _get_cf_attrs(dt) assert "seconds" in attrs["units"] From abc72c69db00e46b82e25ed8953b587f1e4885fb Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:10:53 +0200 Subject: [PATCH 4/6] Fix pixi warning --- pixi.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixi.toml b/pixi.toml index b75ff1ae8d..08c92f64de 100644 --- a/pixi.toml +++ b/pixi.toml @@ -55,7 +55,7 @@ tqdm = "4.50.*" xarray = "2025.8.*" pandas = "2.2.*" pyarrow = "20.0.*" -uxarray = "2026.04.1" +uxarray = "==2026.04.1" dask = "2024.6.*" zarr = "3.0.*" xgcm = { version = "0.9.*", channel = "conda-forge" } From 0369aeb8e84825b028e9e6e997beae819eb5ea4b Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:15:46 +0200 Subject: [PATCH 5/6] Consolidate into strategies subpackage --- tests/strategies/__init__.py | 4 ++++ tests/{strategies.py => strategies/time.py} | 0 tests/utils/test_sgrid.py | 14 +++++++------- tests/utils/test_time.py | 10 +++++----- 4 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 tests/strategies/__init__.py rename tests/{strategies.py => strategies/time.py} (100%) diff --git a/tests/strategies/__init__.py b/tests/strategies/__init__.py new file mode 100644 index 0000000000..71e676d0aa --- /dev/null +++ b/tests/strategies/__init__.py @@ -0,0 +1,4 @@ +from . import sgrid +from . import time + +__all__ = ['sgrid', 'time'] \ No newline at end of file diff --git a/tests/strategies.py b/tests/strategies/time.py similarity index 100% rename from tests/strategies.py rename to tests/strategies/time.py diff --git a/tests/utils/test_sgrid.py b/tests/utils/test_sgrid.py index 78339948fa..27383a336a 100644 --- a/tests/utils/test_sgrid.py +++ b/tests/utils/test_sgrid.py @@ -7,7 +7,7 @@ from hypothesis import assume, example, given from parcels._core.utils import sgrid -from tests.strategies import sgrid as sgrid_strategies +import tests.strategies as pst def create_example_grid2dmetadata(with_vertical_dimensions: bool, with_node_coordinates: bool): @@ -183,7 +183,7 @@ def dummy_comodo_3d_ds() -> xr.Dataset: sgrid.FaceNodePadding("edge2", "node2", sgrid.Padding.LOW), ) ) -@given(sgrid_strategies.mappings) +@given(pst.sgrid.mappings) def test_edge_node_mapping_metadata_roundtrip(edge_node_padding): serialized = sgrid.dump_mappings(edge_node_padding) parsed = sgrid.load_mappings(serialized) @@ -204,7 +204,7 @@ def test_load_dump_mappings(input_, expected): @example(grid2dmetadata) -@given(sgrid_strategies.grid2Dmetadata()) +@given(pst.sgrid.grid2Dmetadata()) def test_Grid2DMetadata_roundtrip(grid: sgrid.Grid2DMetadata): attrs = grid.to_attrs() parsed = sgrid.Grid2DMetadata.from_attrs(attrs) @@ -212,14 +212,14 @@ def test_Grid2DMetadata_roundtrip(grid: sgrid.Grid2DMetadata): @example(grid3dmetadata) -@given(sgrid_strategies.grid3Dmetadata()) +@given(pst.sgrid.grid3Dmetadata()) def test_Grid3DMetadata_roundtrip(grid: sgrid.Grid3DMetadata): attrs = grid.to_attrs() parsed = sgrid.Grid3DMetadata.from_attrs(attrs) assert parsed == grid -@given(sgrid_strategies.grid_metadata) +@given(pst.sgrid.grid_metadata) def test_parse_grid_attrs(grid: sgrid.AttrsSerializable): attrs = grid.to_attrs() parsed = sgrid.parse_grid_attrs(attrs) @@ -227,7 +227,7 @@ def test_parse_grid_attrs(grid: sgrid.AttrsSerializable): @example(grid2dmetadata) -@given(sgrid_strategies.grid2Dmetadata()) +@given(pst.sgrid.grid2Dmetadata()) def test_parse_sgrid_2d(grid_metadata: sgrid.Grid2DMetadata): """Test the ingestion of datasets in XGCM to ensure that it matches the SGRID metadata provided""" ds = dummy_sgrid_2d_ds(grid_metadata) @@ -249,7 +249,7 @@ def test_parse_sgrid_2d(grid_metadata: sgrid.Grid2DMetadata): assert coords[sgrid.SGRID_PADDING_TO_XGCM_POSITION[obj.padding]] == obj.node -@given(sgrid_strategies.grid3Dmetadata()) +@given(pst.sgrid.grid3Dmetadata()) def test_parse_sgrid_3d(grid_metadata: sgrid.Grid3DMetadata): """Test the ingestion of datasets in XGCM to ensure that it matches the SGRID metadata provided""" ds = dummy_sgrid_3d_ds(grid_metadata) diff --git a/tests/utils/test_time.py b/tests/utils/test_time.py index d8b0d10612..67d7a4f8e8 100644 --- a/tests/utils/test_time.py +++ b/tests/utils/test_time.py @@ -36,7 +36,7 @@ def test_time_interval_initialization(left, right): TimeInterval(right, left) -@given(pst.time_interval()) +@given(pst.time.time_interval()) def test_time_interval_contains(interval): left = 0 right = timedelta_to_float(interval.right - interval.left) @@ -47,12 +47,12 @@ def test_time_interval_contains(interval): assert interval.is_all_time_in_interval(middle) -@given(pst.time_interval(calendar="365_day"), pst.time_interval(calendar="365_day")) +@given(pst.time.time_interval(calendar="365_day"), pst.time.time_interval(calendar="365_day")) def test_time_interval_intersection_commutative(interval1, interval2): assert interval1.intersection(interval2) == interval2.intersection(interval1) -@given(pst.time_interval()) +@given(pst.time.time_interval()) def test_time_interval_intersection_with_self(interval): assert interval.intersection(interval) == interval @@ -64,7 +64,7 @@ def test_time_interval_repr(): assert repr(interval) == expected -@given(pst.time_interval()) +@given(pst.time.time_interval()) def test_time_interval_equality(interval): assert interval == interval @@ -175,7 +175,7 @@ def test_timedelta_to_float_exceptions(): timedelta_to_float("invalid_type") -@given(pst.datetime_various()) +@given(pst.time.datetime_various()) def test_datetime_get_cf_attrs(dt): attrs = _get_cf_attrs(dt) assert "seconds" in attrs["units"] From c9cc6e31574faba0501a760e4fd7794c9cf56ba7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:25:19 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/strategies/__init__.py | 5 ++--- tests/utils/test_sgrid.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/strategies/__init__.py b/tests/strategies/__init__.py index 71e676d0aa..5a8e17c885 100644 --- a/tests/strategies/__init__.py +++ b/tests/strategies/__init__.py @@ -1,4 +1,3 @@ -from . import sgrid -from . import time +from . import sgrid, time -__all__ = ['sgrid', 'time'] \ No newline at end of file +__all__ = ["sgrid", "time"] diff --git a/tests/utils/test_sgrid.py b/tests/utils/test_sgrid.py index 27383a336a..d48cb12535 100644 --- a/tests/utils/test_sgrid.py +++ b/tests/utils/test_sgrid.py @@ -6,8 +6,8 @@ import xgcm from hypothesis import assume, example, given -from parcels._core.utils import sgrid import tests.strategies as pst +from parcels._core.utils import sgrid def create_example_grid2dmetadata(with_vertical_dimensions: bool, with_node_coordinates: bool):