From cc83f67f43d1f7548e7ddd1c8a5f4834ff374df6 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 7 Jan 2026 11:42:59 +0100 Subject: [PATCH 1/6] Adding reprs for FieldSet, Field, VectorField and XGrid --- src/parcels/_core/field.py | 11 +++--- src/parcels/_core/fieldset.py | 4 ++ src/parcels/_core/xgrid.py | 4 ++ src/parcels/_reprs.py | 72 +++++++++++++++++++++++++++-------- 4 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/parcels/_core/field.py b/src/parcels/_core/field.py index 988636f13d..3c661a8447 100644 --- a/src/parcels/_core/field.py +++ b/src/parcels/_core/field.py @@ -24,7 +24,7 @@ from parcels._core.uxgrid import UxGrid from parcels._core.xgrid import XGrid, _transpose_xfield_data_to_tzyx, assert_all_field_dims_have_axis from parcels._python import assert_same_function_signature -from parcels._reprs import default_repr +from parcels._reprs import field_repr, vectorfield_repr from parcels._typing import VectorType from parcels.interpolators import ( ZeroInterpolator, @@ -148,6 +148,9 @@ def __init__( if "time" not in self.data.coords: raise ValueError("Field data is missing a 'time' coordinate.") + def __repr__(self): + return field_repr(self) + @property def units(self): return self._units @@ -272,11 +275,7 @@ def __init__( self._vector_interp_method = vector_interp_method def __repr__(self): - return f"""<{type(self).__name__}> - name: {self.name!r} - U: {default_repr(self.U)} - V: {default_repr(self.V)} - W: {default_repr(self.W)}""" + return vectorfield_repr(self) @property def vector_interp_method(self): diff --git a/src/parcels/_core/fieldset.py b/src/parcels/_core/fieldset.py index 1477479d83..85fcc7235c 100644 --- a/src/parcels/_core/fieldset.py +++ b/src/parcels/_core/fieldset.py @@ -18,6 +18,7 @@ from parcels._core.uxgrid import UxGrid from parcels._core.xgrid import _DEFAULT_XGCM_KWARGS, XGrid from parcels._logger import logger +from parcels._reprs import fieldset_repr from parcels._typing import Mesh from parcels.interpolators import UxPiecewiseConstantFace, UxPiecewiseLinearNode, XConstantField, XLinear @@ -75,6 +76,9 @@ def __getattr__(self, name): else: raise AttributeError(f"FieldSet has no attribute '{name}'") + def __repr__(self): + return fieldset_repr(self) + @property def time_interval(self): """Returns the valid executable time interval of the FieldSet, diff --git a/src/parcels/_core/xgrid.py b/src/parcels/_core/xgrid.py index 86805a60f9..2de3de9987 100644 --- a/src/parcels/_core/xgrid.py +++ b/src/parcels/_core/xgrid.py @@ -9,6 +9,7 @@ from parcels._core.basegrid import BaseGrid from parcels._core.index_search import _search_1d_array, _search_indices_curvilinear_2d +from parcels._reprs import xgrid_repr from parcels._typing import assert_valid_mesh _XGRID_AXES = Literal["X", "Y", "Z"] @@ -135,6 +136,9 @@ def from_dataset(cls, ds: xr.Dataset, mesh, xgcm_kwargs=None): grid = xgcm.Grid(ds, **xgcm_kwargs) return cls(grid, mesh=mesh) + def __repr__(self): + return xgrid_repr(self) + @property def axes(self) -> list[_XGRID_AXES]: return _get_xgrid_axes(self.xgcm_grid) diff --git a/src/parcels/_reprs.py b/src/parcels/_reprs.py index 6ea42992ac..dcd3123f55 100644 --- a/src/parcels/_reprs.py +++ b/src/parcels/_reprs.py @@ -5,16 +5,67 @@ import textwrap from typing import TYPE_CHECKING, Any +import xarray as xr + if TYPE_CHECKING: from parcels import Field, FieldSet, ParticleSet -def field_repr(field: Field) -> str: # TODO v4: Rework or remove entirely +def fieldset_repr(fieldset: FieldSet) -> str: + """Return a pretty repr for FieldSet""" + fields = [f for f in fieldset.fields.values() if getattr(f.__class__, "__name__", "") == "Field"] + vfields = [f for f in fieldset.fields.values() if getattr(f.__class__, "__name__", "") == "VectorField"] + + fields_repr = "\n".join([repr(f) for f in fields]) + vfields_repr = "\n".join([vectorfield_repr(vf, from_fieldset_repr=True) for vf in vfields]) + + out = f"""<{type(fieldset).__name__}> + fields: +{textwrap.indent(fields_repr, 8 * " ")} + vectorfields: +{textwrap.indent(vfields_repr, 8 * " ")} +""" + return textwrap.dedent(out).strip() + + +def field_repr(field: Field, offset: int = 0) -> str: """Return a pretty repr for Field""" - out = f"""<{type(field).__name__}> - name : {field.name!r} - data : {field.data!r} - extrapolate time: {field.allow_time_extrapolation!r} + with xr.set_options(display_expand_data=False): + out = f"""<{type(field).__name__} {field.name!r}> + Parcels attributes: + name : {field.name!r} + interp_method : {field.interp_method!r} + time_interval : {field.time_interval!r} + units : {field.units!r} + igrid : {field.igrid!r} + DataArray: +{textwrap.indent(repr(field.data), 8 * " ")} +{textwrap.indent(repr(field.grid), 4 * " ")} +""" + return textwrap.indent(out, " " * offset).strip() + + +def vectorfield_repr(fieldset: FieldSet, from_fieldset_repr=False) -> str: + """Return a pretty repr for VectorField""" + out = f"""<{type(fieldset).__name__} {fieldset.name!r}> + Parcels attributes: + name : {fieldset.name!r} + vector_interp_method : {fieldset.vector_interp_method!r} + vector_type : {fieldset.vector_type!r} + {field_repr(fieldset.U, offset=4) if not from_fieldset_repr else ""} + {field_repr(fieldset.V, offset=4) if not from_fieldset_repr else ""} + {field_repr(fieldset.W, offset=4) if not from_fieldset_repr and fieldset.W else ""}""" + return out + + +def xgrid_repr(grid: Any) -> str: + """Return a pretty repr for Grid""" + out = f"""<{type(grid).__name__}> + Parcels attributes: + mesh : {grid._mesh} + spatialhash : {grid._spatialhash} + xgcm Grid: +{textwrap.indent(repr(grid.xgcm_grid), 8 * " ")} """ return textwrap.dedent(out).strip() @@ -62,17 +113,6 @@ def particleset_repr(pset: ParticleSet) -> str: return textwrap.dedent(out).strip() -def fieldset_repr(fieldset: FieldSet) -> str: # TODO v4: Rework or remove entirely - """Return a pretty repr for FieldSet""" - fields_repr = "\n".join([repr(f) for f in fieldset.fields.values()]) - - out = f"""<{type(fieldset).__name__}> - fields: -{textwrap.indent(fields_repr, 8 * " ")} -""" - return textwrap.dedent(out).strip() - - def default_repr(obj: Any): if is_builtin_object(obj): return repr(obj) From 88a1c58ab4ee9c8ff73c1edd770bcb23dc402670 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 7 Jan 2026 11:44:43 +0100 Subject: [PATCH 2/6] Removing particleset._repeat_starttime As it is not used anymore in v4 --- src/parcels/_core/particleset.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parcels/_core/particleset.py b/src/parcels/_core/particleset.py index 8dea8efaa4..4cfaf4b55f 100644 --- a/src/parcels/_core/particleset.py +++ b/src/parcels/_core/particleset.py @@ -70,7 +70,6 @@ def __init__( **kwargs, ): self._data = None - self._repeat_starttime = None self._kernel = None self.fieldset = fieldset From 15867e6fceafae9b616ca5d6d85a27f6e6c57d35 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 7 Jan 2026 14:56:50 +0100 Subject: [PATCH 3/6] Also adding particleset, particle, variable etc wrappers --- src/parcels/_core/particle.py | 7 +- src/parcels/_core/particlefile.py | 8 +-- src/parcels/_core/particleset.py | 2 +- src/parcels/_core/particlesetview.py | 10 ++- src/parcels/_core/utils/time.py | 4 +- src/parcels/_reprs.py | 101 +++++++++++++++++++-------- 6 files changed, 88 insertions(+), 44 deletions(-) diff --git a/src/parcels/_core/particle.py b/src/parcels/_core/particle.py index a9a187f30b..86ddc5138c 100644 --- a/src/parcels/_core/particle.py +++ b/src/parcels/_core/particle.py @@ -9,7 +9,7 @@ from parcels._core.statuscodes import StatusCode from parcels._core.utils.string import _assert_str_and_python_varname from parcels._core.utils.time import TimeInterval -from parcels._reprs import _format_list_items_multiline +from parcels._reprs import particleclass_repr, variable_repr __all__ = ["Particle", "ParticleClass", "Variable"] _TO_WRITE_OPTIONS = [True, False, "once"] @@ -70,7 +70,7 @@ def name(self): return self._name def __repr__(self): - return f"Variable(name={self._name!r}, dtype={self.dtype!r}, initial={self.initial!r}, to_write={self.to_write!r}, attrs={self.attrs!r})" + return variable_repr(self) class ParticleClass: @@ -92,8 +92,7 @@ def __init__(self, variables: list[Variable]): self.variables = variables def __repr__(self): - vars = [repr(v) for v in self.variables] - return f"ParticleClass(variables={_format_list_items_multiline(vars)})" + return particleclass_repr(self) def add_variable(self, variable: Variable | list[Variable]): """Add a new variable to the Particle class. This returns a new Particle class with the added variable(s). diff --git a/src/parcels/_core/particlefile.py b/src/parcels/_core/particlefile.py index 52f20af2e0..d60ca0b5c4 100644 --- a/src/parcels/_core/particlefile.py +++ b/src/parcels/_core/particlefile.py @@ -16,6 +16,7 @@ import parcels from parcels._core.particle import ParticleClass from parcels._core.utils.time import timedelta_to_float +from parcels._reprs import particlefile_repr if TYPE_CHECKING: from parcels._core.particle import Variable @@ -96,12 +97,7 @@ def __init__(self, store, outputdt, chunks=None, create_new_zarrfile=True): # TODO v4: Add check that if create_new_zarrfile is False, the store already exists def __repr__(self) -> str: - return ( - f"{type(self).__name__}(" - f"outputdt={self.outputdt!r}, " - f"chunks={self.chunks!r}, " - f"create_new_zarrfile={self.create_new_zarrfile!r})" - ) + return particlefile_repr(self) def set_metadata(self, parcels_grid_mesh: Literal["spherical", "flat"]): self.metadata.update( diff --git a/src/parcels/_core/particleset.py b/src/parcels/_core/particleset.py index 4cfaf4b55f..da583a32c6 100644 --- a/src/parcels/_core/particleset.py +++ b/src/parcels/_core/particleset.py @@ -166,7 +166,7 @@ def __getattr__(self, name): def __getitem__(self, index): """Get a single particle by index.""" - return ParticleSetView(self._data, index=index) + return ParticleSetView(self._data, index=index, ptype=self._ptype) def __setattr__(self, name, value): if name in ["_data"]: diff --git a/src/parcels/_core/particlesetview.py b/src/parcels/_core/particlesetview.py index c0ce88c04f..426d1d8d6f 100644 --- a/src/parcels/_core/particlesetview.py +++ b/src/parcels/_core/particlesetview.py @@ -1,12 +1,15 @@ import numpy as np +from parcels._reprs import particlesetview_repr + class ParticleSetView: """Class to be used in a kernel that links a View of the ParticleSet (on the kernel level) to a ParticleSet.""" - def __init__(self, data, index): + def __init__(self, data, index, ptype): self._data = data self._index = index + self._ptype = ptype def __getattr__(self, name): # Return a proxy that behaves like the underlying numpy array but @@ -25,11 +28,14 @@ def __getattr__(self, name): return self._data[name][self._index] def __setattr__(self, name, value): - if name in ["_data", "_index"]: + if name in ["_data", "_index", "_ptype"]: object.__setattr__(self, name, value) else: self._data[name][self._index] = value + def __repr__(self): + return particlesetview_repr(self) + def __getitem__(self, index): # normalize single-element tuple indexing (e.g., (inds,)) if isinstance(index, tuple) and len(index) == 1: diff --git a/src/parcels/_core/utils/time.py b/src/parcels/_core/utils/time.py index fe62813ef0..72843815e5 100644 --- a/src/parcels/_core/utils/time.py +++ b/src/parcels/_core/utils/time.py @@ -6,6 +6,8 @@ import cftime import numpy as np +from parcels._reprs import timeinterval_repr + if TYPE_CHECKING: from parcels._typing import TimeLike @@ -61,7 +63,7 @@ def is_all_time_in_interval(self, time: float): return (0 <= item).all() and (item <= self.time_length_as_flt).all() def __repr__(self) -> str: - return f"TimeInterval(left={self.left!r}, right={self.right!r})" + return timeinterval_repr(self) def __eq__(self, other: object) -> bool: if not isinstance(other, TimeInterval): diff --git a/src/parcels/_reprs.py b/src/parcels/_reprs.py index dcd3123f55..5d8ea2a33f 100644 --- a/src/parcels/_reprs.py +++ b/src/parcels/_reprs.py @@ -5,6 +5,7 @@ import textwrap from typing import TYPE_CHECKING, Any +import numpy as np import xarray as xr if TYPE_CHECKING: @@ -28,7 +29,8 @@ def fieldset_repr(fieldset: FieldSet) -> str: return textwrap.dedent(out).strip() -def field_repr(field: Field, offset: int = 0) -> str: +# 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): out = f"""<{type(field).__name__} {field.name!r}> @@ -42,7 +44,7 @@ def field_repr(field: Field, offset: int = 0) -> str: {textwrap.indent(repr(field.data), 8 * " ")} {textwrap.indent(repr(field.grid), 4 * " ")} """ - return textwrap.indent(out, " " * offset).strip() + return textwrap.indent(out, " " * level * 4).strip() def vectorfield_repr(fieldset: FieldSet, from_fieldset_repr=False) -> str: @@ -52,9 +54,9 @@ def vectorfield_repr(fieldset: FieldSet, from_fieldset_repr=False) -> str: name : {fieldset.name!r} vector_interp_method : {fieldset.vector_interp_method!r} vector_type : {fieldset.vector_type!r} - {field_repr(fieldset.U, offset=4) if not from_fieldset_repr else ""} - {field_repr(fieldset.V, offset=4) if not from_fieldset_repr else ""} - {field_repr(fieldset.W, offset=4) if not from_fieldset_repr and fieldset.W else ""}""" + {field_repr(fieldset.U, level=1) if not from_fieldset_repr else ""} + {field_repr(fieldset.V, level=1) if not from_fieldset_repr else ""} + {field_repr(fieldset.W, level=1) if not from_fieldset_repr and fieldset.W else ""}""" return out @@ -70,7 +72,66 @@ def xgrid_repr(grid: Any) -> str: return textwrap.dedent(out).strip() -def _format_list_items_multiline(items: list[str], level: int = 1) -> str: +def particleset_repr(pset: ParticleSet) -> str: + """Return a pretty repr for ParticleSet""" + if len(pset) < 10: + particles = [repr(p) for p in pset] + else: + particles = [repr(pset[i]) for i in range(7)] + ["..."] + + out = f"""<{type(pset).__name__}> + Number of particles: {len(pset)} + Particles: +{_format_list_items_multiline(particles, level=2, with_brackets=False)} + Pclass: +{textwrap.indent(repr(pset._ptype), 8 * " ")} +""" + return textwrap.dedent(out).strip() + + +def particlesetview_repr(pview: Any) -> str: + """Return a pretty repr for ParticleSetView""" + time_string = "not_yet_set" if pview.time is None or np.isnan(pview.time) else f"{pview.time:f}" + out = f"P[{pview.trajectory}]: time={time_string}, z={pview.z:f}, lat={pview.lat:f}, lon={pview.lon:f}" + vars = [v.name for v in pview._ptype.variables if v.to_write is True and v.name not in ["lon", "lat", "z", "time"]] + for var in vars: + out += f", {var}={getattr(pview, var):f}" + + return textwrap.dedent(out).strip() + + +def particleclass_repr(pclass: Any) -> str: + vars = [repr(v) for v in pclass.variables] + out = f""" +{_format_list_items_multiline(vars, level=1, with_brackets=False)} +""" + return textwrap.dedent(out).strip() + + +def variable_repr(var: Any) -> str: + return f"Variable(name={var._name!r}, dtype={var.dtype!r}, initial={var.initial!r}, to_write={var.to_write!r}, attrs={var.attrs!r})" + + +def timeinterval_repr(ti: Any) -> str: + return f"TimeInterval(left={ti.left!r}, right={ti.right!r})" + + +def particlefile_repr(pfile: Any) -> str: + out = f"""{type(pfile).__name__} + outputdt : {pfile.outputdt!r} + chunks : {pfile.chunks!r} + create_new_zarrfile: {pfile.create_new_zarrfile!r} +""" + return textwrap.dedent(out).strip() + + +def default_repr(obj: Any): + if is_builtin_object(obj): + return repr(obj) + return object.__repr__(obj) + + +def _format_list_items_multiline(items: list[str], level: int = 1, with_brackets: bool = True) -> str: """Given a list of strings, formats them across multiple lines. Uses indentation levels of 4 spaces provided by ``level``. @@ -88,35 +149,15 @@ def _format_list_items_multiline(items: list[str], level: int = 1) -> str: if len(items) == 0: return "[]" - assert level >= 1, "Indentation level >=1 supported" + assert level >= 0, "Indentation level >=0 supported" indentation_str = level * 4 * " " indentation_str_end = (level - 1) * 4 * " " items_str = ",\n".join([textwrap.indent(i, indentation_str) for i in items]) - return f"[\n{items_str}\n{indentation_str_end}]" - - -def particleset_repr(pset: ParticleSet) -> str: - """Return a pretty repr for ParticleSet""" - if len(pset) < 10: - particles = [repr(p) for p in pset] + if with_brackets: + return f"[\n{items_str}\n{indentation_str_end}]" else: - particles = [repr(pset[i]) for i in range(7)] + ["..."] - - out = f"""<{type(pset).__name__}> - fieldset : -{textwrap.indent(repr(pset.fieldset), " " * 8)} - ptype : {pset._ptype} - # particles: {len(pset)} - particles : {_format_list_items_multiline(particles, level=2)} -""" - return textwrap.dedent(out).strip() - - -def default_repr(obj: Any): - if is_builtin_object(obj): - return repr(obj) - return object.__repr__(obj) + return f"{items_str}" def is_builtin_object(obj): From 8f74642556bc16018498b2908c60501a2cf79b9e Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 7 Jan 2026 15:25:00 +0100 Subject: [PATCH 4/6] Updating ParticleFile repr And also adding support for dictionaries in the _format_list_items_multiline function --- src/parcels/_core/particleset.py | 11 ++------- src/parcels/_reprs.py | 39 ++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/parcels/_core/particleset.py b/src/parcels/_core/particleset.py index da583a32c6..72ae049cc3 100644 --- a/src/parcels/_core/particleset.py +++ b/src/parcels/_core/particleset.py @@ -7,7 +7,6 @@ import numpy as np import xarray as xr from tqdm import tqdm -from zarr.storage import DirectoryStore from parcels._core.converters import _convert_to_flat_array from parcels._core.kernel import Kernel @@ -21,7 +20,7 @@ ) from parcels._core.warnings import ParticleSetWarning from parcels._logger import logger -from parcels._reprs import particleset_repr +from parcels._reprs import _format_zarr_output_location, particleset_repr __all__ = ["ParticleSet"] @@ -446,7 +445,7 @@ def execute( # Set up pbar if output_file: - logger.info(f"Output files are stored in {_format_output_location(output_file.store)}") + logger.info(f"Output files are stored in {_format_zarr_output_location(output_file.store)}") if verbose_progress: pbar = tqdm(total=end_time - start_time, file=sys.stdout) @@ -591,9 +590,3 @@ def _get_start_time(first_release_time, time_interval, sign_dt, runtime): start_time = first_release_time if not np.isnan(first_release_time) else fieldset_start return start_time - - -def _format_output_location(zarr_obj): - if isinstance(zarr_obj, DirectoryStore): - return zarr_obj.path - return repr(zarr_obj) diff --git a/src/parcels/_reprs.py b/src/parcels/_reprs.py index 5d8ea2a33f..7783173aec 100644 --- a/src/parcels/_reprs.py +++ b/src/parcels/_reprs.py @@ -7,6 +7,7 @@ import numpy as np import xarray as xr +from zarr.storage import DirectoryStore if TYPE_CHECKING: from parcels import Field, FieldSet, ParticleSet @@ -77,7 +78,7 @@ def particleset_repr(pset: ParticleSet) -> str: if len(pset) < 10: particles = [repr(p) for p in pset] else: - particles = [repr(pset[i]) for i in range(7)] + ["..."] + particles = [repr(pset[i]) for i in range(7)] + ["..."] + [repr(pset[-1])] out = f"""<{type(pset).__name__}> Number of particles: {len(pset)} @@ -101,6 +102,7 @@ def particlesetview_repr(pview: Any) -> str: def particleclass_repr(pclass: Any) -> str: + """Return a pretty repr for ParticleClass""" vars = [repr(v) for v in pclass.variables] out = f""" {_format_list_items_multiline(vars, level=1, with_brackets=False)} @@ -109,18 +111,24 @@ def particleclass_repr(pclass: Any) -> str: def variable_repr(var: Any) -> str: + """Return a pretty repr for Variable""" return f"Variable(name={var._name!r}, dtype={var.dtype!r}, initial={var.initial!r}, to_write={var.to_write!r}, attrs={var.attrs!r})" def timeinterval_repr(ti: Any) -> str: + """Return a pretty repr for TimeInterval""" return f"TimeInterval(left={ti.left!r}, right={ti.right!r})" def particlefile_repr(pfile: Any) -> str: - out = f"""{type(pfile).__name__} - outputdt : {pfile.outputdt!r} - chunks : {pfile.chunks!r} - create_new_zarrfile: {pfile.create_new_zarrfile!r} + """Return a pretty repr for ParticleFile""" + out = f"""<{type(pfile).__name__}> + store : {_format_zarr_output_location(pfile.store)} + outputdt : {pfile.outputdt!r} + chunks : {pfile.chunks!r} + create_new_zarrfile : {pfile.create_new_zarrfile!r} + metadata : +{_format_list_items_multiline(pfile.metadata, level=2, with_brackets=False)} """ return textwrap.dedent(out).strip() @@ -131,8 +139,8 @@ def default_repr(obj: Any): return object.__repr__(obj) -def _format_list_items_multiline(items: list[str], level: int = 1, with_brackets: bool = True) -> str: - """Given a list of strings, formats them across multiple lines. +def _format_list_items_multiline(items: list[str] | dict, level: int = 1, with_brackets: bool = True) -> str: + """Given a list of strings or a dict, formats them across multiple lines. Uses indentation levels of 4 spaces provided by ``level``. @@ -149,15 +157,26 @@ def _format_list_items_multiline(items: list[str], level: int = 1, with_brackets if len(items) == 0: return "[]" - assert level >= 0, "Indentation level >=0 supported" + assert level >= 1, "Indentation level >=1 supported" indentation_str = level * 4 * " " indentation_str_end = (level - 1) * 4 * " " - items_str = ",\n".join([textwrap.indent(i, indentation_str) for i in items]) + if isinstance(items, dict): + entries = [f"{k!r}: {v!r}" for k, v in items.items()] + else: + entries = [i if isinstance(i, str) else repr(i) for i in items] + if with_brackets: + items_str = ",\n".join([textwrap.indent(e, indentation_str) for e in entries]) return f"[\n{items_str}\n{indentation_str_end}]" else: - return f"{items_str}" + return "\n".join([textwrap.indent(e, indentation_str) for e in entries]) + + +def _format_zarr_output_location(zarr_obj): + if isinstance(zarr_obj, DirectoryStore): + return zarr_obj.path + return repr(zarr_obj) def is_builtin_object(obj): From f9cb72a6b3dab28de6c9f8e2dfece56158d3a6f3 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 7 Jan 2026 15:30:23 +0100 Subject: [PATCH 5/6] Removing quotes from dict entry lines --- src/parcels/_reprs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parcels/_reprs.py b/src/parcels/_reprs.py index 7783173aec..07f58b1532 100644 --- a/src/parcels/_reprs.py +++ b/src/parcels/_reprs.py @@ -162,7 +162,7 @@ def _format_list_items_multiline(items: list[str] | dict, level: int = 1, with_b indentation_str_end = (level - 1) * 4 * " " if isinstance(items, dict): - entries = [f"{k!r}: {v!r}" for k, v in items.items()] + entries = [f"{k!s}: {v!s}" for k, v in items.items()] else: entries = [i if isinstance(i, str) else repr(i) for i in items] From d5048d93efebc20ec6640b58c4972fb85fe30c93 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 7 Jan 2026 15:43:03 +0100 Subject: [PATCH 6/6] Fixing ptype for ParticleSetView And also updating one repr test --- src/parcels/_core/particlesetview.py | 6 +++--- tests/test_particle.py | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/parcels/_core/particlesetview.py b/src/parcels/_core/particlesetview.py index 426d1d8d6f..b43860460a 100644 --- a/src/parcels/_core/particlesetview.py +++ b/src/parcels/_core/particlesetview.py @@ -56,7 +56,7 @@ def __getitem__(self, index): raise ValueError( f"Boolean index has incompatible length {arr.size} for selection of size {int(np.sum(base))}" ) - return ParticleSetView(self._data, new_index) + return ParticleSetView(self._data, new_index, self._ptype) # Integer array/list, slice or single integer relative to the local view # (boolean masks were handled above). Normalize and map to global @@ -71,12 +71,12 @@ def __getitem__(self, index): base_arr = np.asarray(base) sel = base_arr[idx] new_index[sel] = True - return ParticleSetView(self._data, new_index) + return ParticleSetView(self._data, new_index, self._ptype) # Fallback: try to assign directly (preserves previous behaviour for other index types) try: new_index[base] = index - return ParticleSetView(self._data, new_index) + return ParticleSetView(self._data, new_index, self._ptype) except Exception as e: raise TypeError(f"Unsupported index type for ParticleSetView.__getitem__: {type(index)!r}") from e diff --git a/tests/test_particle.py b/tests/test_particle.py index 5daa2c8a39..4fc02ee9ca 100644 --- a/tests/test_particle.py +++ b/tests/test_particle.py @@ -80,11 +80,9 @@ def test_particleclass_invalid_vars(): Variable("varc", dtype=np.float32, to_write=True), ] ), - """ParticleClass(variables=[ - Variable(name='vara', dtype=dtype('float32'), initial=0, to_write=True, attrs={}), - Variable(name='varb', dtype=dtype('float32'), initial=0, to_write=False, attrs={}), - Variable(name='varc', dtype=dtype('float32'), initial=0, to_write=True, attrs={}) -])""", + """Variable(name='vara', dtype=dtype('float32'), initial=0, to_write=True, attrs={}) +Variable(name='varb', dtype=dtype('float32'), initial=0, to_write=False, attrs={}) +Variable(name='varc', dtype=dtype('float32'), initial=0, to_write=True, attrs={})""", ), ], )