diff --git a/docs/conf.py b/docs/conf.py index feb8b5f4c6..75852a08e5 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -121,7 +121,6 @@ "_build", "jupyter_execute", "**.ipynb_checkpoints", - "user_guide/examples_v3", ".jupyter_cache", ] diff --git a/pixi.toml b/pixi.toml index a87fff64f1..f9f3e5706d 100644 --- a/pixi.toml +++ b/pixi.toml @@ -83,6 +83,9 @@ pytest-cov = "*" pytest-reportlog = "*" pytest-timeout = "*" +[feature.test.pypi-dependencies] +re-assert = "*" + [feature.test.tasks] tests = { cmd = "pytest", description = "Run the test suite." } tests-flaky = { cmd = "pytest -m 'flaky' --run-flaky-tests", description = "Run only flaky tests." } diff --git a/pyproject.toml b/pyproject.toml index 9fdc121af1..9b2cbbdf08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "cftime >=1.6.3", "numpy >=2.1.0", "dask >=2024.5.1", - "netCDF4 >=1.7.2", # TODO: should we use h5netcdf here instead of netCDF4? + "netCDF4 >=1.7.2", "zarr >=3", "tqdm >=4.50.0", "xarray >=2024.5.0", @@ -53,7 +53,6 @@ local_scheme = "no-local-version" [tool.pytest.ini_options] addopts = ["--strict-config", "--strict-markers"] xfail_strict = true -# testpaths = ["tests", "docs/examples"] # TODO v4: Re-enable once examples are back/fixed testpaths = ["tests"] python_files = ["test_*.py", "example_*.py", "*tutorial*"] minversion = "7" @@ -101,11 +100,6 @@ select = [ "NPY201", # numpy 2 deprecations ] -exclude = [ - "tests-v3/**", - 'docs/user_guide/examples_v3/**', -] # TODO v4: Remove once folders are gone - ignore = [ # # Rules intentionally excluded # line too long (82 > 79 characters) diff --git a/src/parcels/_core/field.py b/src/parcels/_core/field.py index 888cdcca30..ded5993edd 100644 --- a/src/parcels/_core/field.py +++ b/src/parcels/_core/field.py @@ -116,7 +116,7 @@ def time_interval(self): # TODO PR: Remove in favour of referencing model time_ return self.model.time_interval def __repr__(self): - return f"Field(name={self.name}, model={self.model})" + return f"Field(name={self.name!r}, model={self.model})" @property def interp_method(self): diff --git a/src/parcels/_datasets/structured/generated.py b/src/parcels/_datasets/structured/generated.py index 7eba61f559..05ad96dd6e 100644 --- a/src/parcels/_datasets/structured/generated.py +++ b/src/parcels/_datasets/structured/generated.py @@ -91,7 +91,7 @@ def radial_rotation_dataset(xdim=200, ydim=200): # Define 2D flat, square field ) -def moving_eddy_dataset(xdim=2, ydim=2): # TODO check if this also works with xdim=1, ydim=1 +def moving_eddy_dataset(xdim=2, ydim=2): """Create a dataset with an eddy moving in time. Note that there is no spatial variation in the flow.""" f, u_0, u_g = 1.0e-4, 0.3, 0.04 # Some constants diff --git a/src/parcels/_repr_utils.py b/src/parcels/_repr_utils.py index 9e2c828c59..1386761dc6 100644 --- a/src/parcels/_repr_utils.py +++ b/src/parcels/_repr_utils.py @@ -44,7 +44,6 @@ def fieldset_repr(fieldset: FieldSet) -> str: return textwrap.dedent(out).strip() -# TODO add land_value here after HG #2451 is merged def field_repr(field: Field, level: int = 0) -> str: """Return a pretty repr for Field""" with xr.set_options(display_expand_data=False): @@ -138,8 +137,7 @@ def particlefile_repr(pfile: Any) -> str: out = f"""<{type(pfile).__name__}> path : {pfile.path} outputdt : {pfile.outputdt!r} - metadata : -{_format_list_items_multiline(pfile.metadata, level=2, with_brackets=False)} + metadata : {_format_list_items_multiline(pfile.metadata, level=2, with_brackets=False)} """ return textwrap.dedent(out).strip() diff --git a/src/parcels/_typing.py b/src/parcels/_typing.py index dd98afc44e..7f1540e82b 100644 --- a/src/parcels/_typing.py +++ b/src/parcels/_typing.py @@ -60,7 +60,7 @@ def _validate_against_pure_literal(value, typing_literal): Can't be used with ``Literal[...] | None`` etc. as its not a pure literal. """ - # TODO remove once https://github.com/pydata/xarray/issues/11209 is resolved - Xarray objects don't work normally in `in` statements + # Xarray objects don't work normally in `in` statements - see https://github.com/pydata/xarray/issues/11209 (this is unlikely to be resolved anytime soon) if _is_xarray_object(value): raise ValueError(f"Invalid input type {type(value)}") diff --git a/tests-v3/test_reprs.py b/tests-v3/test_reprs.py deleted file mode 100644 index 18407e583d..0000000000 --- a/tests-v3/test_reprs.py +++ /dev/null @@ -1,115 +0,0 @@ -import re -from datetime import timedelta -from typing import Any - -import numpy as np - -import parcels -from parcels import Grid, ParticleFile, Variable -from parcels.grid import RectilinearGrid -from tests.utils import create_fieldset_unit_mesh, create_simple_pset - - -def assert_simple_repr(class_: type, kwargs: dict[str, Any]): - """Test that the repr of an object contains all the arguments. This only works for objects where the repr matches the calling signature.""" - obj = class_(**kwargs) - obj_repr = repr(obj) - - for param in kwargs.keys(): - assert param in obj_repr - # skip `assert repr(value) in obj_repr` as this is not always true if init does processing on the value - assert class_.__name__ in obj_repr - - -def valid_indentation(s: str) -> bool: - """Make sure that all lines in string is indented with a multiple of 4 spaces.""" - if s.startswith(" "): - return False - - lines = s.split("\n") - for line in lines: - line = re.sub("^( {4})+", "", line) - if line.startswith(" "): - return False - return True - - -def test_check_indentation(): - valid = """ -test - test -test - test - test - test""" - assert valid_indentation(valid) - invalid = """ -test - test - invalid! -""" - assert not valid_indentation(invalid) - - -def test_particletype_repr(): - kwargs = dict(pclass=parcels.Particle) - assert_simple_repr(parcels.particle.ParticleType, kwargs) - - -def test_grid_repr(): - """Test arguments are in the repr of a Grid object""" - kwargs = dict(lon=np.array([1, 2, 3]), lat=np.array([4, 5, 6]), time=None, mesh="spherical") - assert_simple_repr(Grid, kwargs) - - -def test_variable_repr(): - """Test arguments are in the repr of the Variable object.""" - kwargs = dict(name="test", dtype=np.float32, initial=0, to_write=False) - assert_simple_repr(Variable, kwargs) - - -def test_rectilineargrid_repr(): - """ - Test arguments are in the repr of a RectilinearGrid object. - - Mainly to test inherited repr is correct. - """ - kwargs = dict(lon=np.array([1, 2, 3]), lat=np.array([4, 5, 6]), time=None, mesh="spherical") - assert_simple_repr(RectilinearGrid, kwargs) - - -def test_particlefile_repr(): - pset = create_simple_pset() - kwargs = dict( - name="file.zarr", particleset=pset, outputdt=timedelta(hours=1), chunks=None, create_new_zarrfile=False - ) - assert_simple_repr(ParticleFile, kwargs) - - -def test_field_repr(): - field = create_fieldset_unit_mesh().U - assert valid_indentation(repr(field)) - - -def test_vectorfield_repr(): - field = create_fieldset_unit_mesh().UV - assert isinstance(field, parcels.VectorField) - assert valid_indentation(repr(field)) - - -def test_fieldset_repr(): - fieldset = create_fieldset_unit_mesh() - assert valid_indentation(repr(fieldset)) - - -def test_particleset_repr(): - pset = create_simple_pset() - valid_indentation(repr(pset)) - - pset = create_simple_pset(n=15) - valid_indentation(repr(pset)) - - -def capture(s): - with open("file.txt", "a") as f: - f.write(s) diff --git a/tests/test_field.py b/tests/test_field.py index 3050aba899..924335a86c 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -2,6 +2,7 @@ import numpy as np import pytest +from re_assert import Matches from parcels import Field, VectorField from parcels._core.fieldset import FieldSet @@ -39,6 +40,14 @@ def test_field_init_param_types(): Field(name="while", model=model) +def test_field_repr(fieldset): + Matches(r"Field\(name=.*, model=.*\)").assert_matches(repr(fieldset.U)) + + +def test_vectorfield_repr(fieldset): + Matches(r"\<.*VectorField object at.*\>").assert_matches(repr(fieldset.UV)) + + # TODO: Move to test_model.py ? def test_field_init_fail_on_float_time_dim(): """Test that accessing time_interval fails when dataset has float time dimension. diff --git a/tests/test_fieldset.py b/tests/test_fieldset.py index b1b6b16f67..f83f8b126d 100644 --- a/tests/test_fieldset.py +++ b/tests/test_fieldset.py @@ -7,6 +7,7 @@ import pandas as pd import pytest import xarray as xr +from re_assert import Matches import parcels.tutorial import tests @@ -40,6 +41,10 @@ def test_fieldset_init_wrong_types(): FieldSet([1.0, 2.0, 3.0]) +def test_fieldset_repr(fieldset): + Matches(r"\<.*FieldSet object at.*\>").assert_matches(repr(fieldset)) + + def test_fieldset_add_context(fieldset): fieldset.add_context("test_context", 1.0) assert fieldset.test_context == 1.0 diff --git a/tests/test_particlefile.py b/tests/test_particlefile.py index 38645de8d4..39646a51dc 100755 --- a/tests/test_particlefile.py +++ b/tests/test_particlefile.py @@ -9,11 +9,13 @@ import pyarrow.parquet as pq import pytest import xarray as xr +from re_assert import Matches import parcels.tutorial from parcels import ( Field, FieldSet, + Particle, ParticleFile, ParticleSet, ParticleSetWarning, @@ -21,7 +23,7 @@ Variable, convert, ) -from parcels._core.particle import Particle, get_default_particle +from parcels._core.particle import get_default_particle from parcels._core.particlefile import get_schema from parcels._core.utils.time import TimeInterval, timedelta_to_float from parcels._datasets.structured.generated import peninsula_dataset @@ -30,6 +32,17 @@ from tests.common_kernels import DoNothing +def test_particlefile_repr(tmp_parquet): + pfile_repr = repr(ParticleFile(tmp_parquet, outputdt=np.timedelta64(1, "s"))) + match = Matches( + r"""\ + path : .* + outputdt : 1.0 + metadata : .*""", + ) + match.assert_matches(pfile_repr) + + def test_metadata(fieldset, tmp_parquet): pset = ParticleSet(fieldset, pclass=Particle, x=0, y=0) diff --git a/tests/test_particleset.py b/tests/test_particleset.py index d0721b7ed3..5166c6c8ca 100644 --- a/tests/test_particleset.py +++ b/tests/test_particleset.py @@ -5,6 +5,7 @@ import numpy as np import pytest import xarray as xr +from re_assert import Matches from parcels import ( FieldSet, @@ -35,6 +36,10 @@ def test_create_empty_pset(fieldset): assert pset.size == 0 +def test_particleset_repr(fieldset): + Matches(r"\<.*ParticleSet object at.*\>").assert_matches(repr(ParticleSet(fieldset, pclass=Particle))) + + @pytest.mark.parametrize("offset", [0, 1, 200]) def test_pset_with_pids(fieldset, offset, npart=100): lon = np.linspace(0, 1, npart) diff --git a/tests/test_xgrid.py b/tests/test_xgrid.py index c224372815..2d28d4aef9 100644 --- a/tests/test_xgrid.py +++ b/tests/test_xgrid.py @@ -5,6 +5,7 @@ import pytest import xarray as xr from numpy.testing import assert_allclose +from re_assert import Matches from parcels import FieldSet from parcels._core.index_search import ( @@ -56,6 +57,10 @@ def test_grid_init_param_types(ds): XGrid.from_dataset(ds, mesh="invalid") +def test_xgrid_repr(fieldset): + Matches(r"\<.*XGrid object at.*\>").assert_matches(repr(fieldset.U.grid)) + + @pytest.mark.parametrize("ds, attr, expected", test_cases) def test_xgrid_properties_ground_truth(ds, attr, expected): grid = FieldSet.from_sgrid_conventions(ds, mesh="flat").data_g.grid