From e18e6508551fe823f63904c2d637dedcbf1977e9 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 18 Nov 2024 09:31:55 -0700 Subject: [PATCH 1/7] Compatibility with Zarr v3b2 --- xarray/backends/zarr.py | 2 +- xarray/tests/__init__.py | 1 + xarray/tests/test_backends.py | 71 +++++++++++++++++------------------ 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py index fcbf1f8c4a0..732f33b6791 100644 --- a/xarray/backends/zarr.py +++ b/xarray/backends/zarr.py @@ -443,7 +443,7 @@ def extract_zarr_variable_encoding( shape = shape if shape else variable.shape encoding = variable.encoding.copy() - safe_to_drop = {"source", "original_shape"} + safe_to_drop = {"source", "original_shape", "preferred_chunks"} valid_encodings = { "codecs", "chunks", diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py index 5ed334e61dd..925ddf7a28b 100644 --- a/xarray/tests/__init__.py +++ b/xarray/tests/__init__.py @@ -138,6 +138,7 @@ def _importorskip( has_pint, requires_pint = _importorskip("pint") has_numexpr, requires_numexpr = _importorskip("numexpr") has_flox, requires_flox = _importorskip("flox") +has_netcdf, requires_netcdf = _importorskip("netcdf") has_pandas_ge_2_2, requires_pandas_ge_2_2 = _importorskip("pandas", "2.2") has_pandas_3, requires_pandas_3 = _importorskip("pandas", "3.0.0.dev0") diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index c543333c61e..ac1e08b266a 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -78,6 +78,7 @@ requires_h5netcdf_1_4_0_or_above, requires_h5netcdf_ros3, requires_iris, + requires_netcdf, requires_netCDF4, requires_netCDF4_1_6_2_or_above, requires_netCDF4_1_7_0_or_above, @@ -634,6 +635,7 @@ def test_roundtrip_float64_data(self) -> None: with self.roundtrip(expected) as actual: assert_identical(expected, actual) + @requires_netcdf def test_roundtrip_example_1_netcdf(self) -> None: with open_example_dataset("example_1.nc") as expected: with self.roundtrip(expected) as actual: @@ -1126,13 +1128,11 @@ def _create_cf_dataset(): def test_grid_mapping_and_bounds_are_not_coordinates_in_file(self) -> None: original = self._create_cf_dataset() - with create_tmp_file() as tmp_file: - original.to_netcdf(tmp_file) - with open_dataset(tmp_file, decode_coords=False) as ds: - assert ds.coords["latitude"].attrs["bounds"] == "latitude_bnds" - assert ds.coords["longitude"].attrs["bounds"] == "longitude_bnds" - assert "coordinates" not in ds["variable"].attrs - assert "coordinates" not in ds.attrs + with self.roundtrip(original, open_kwargs={"decode_coords": False}) as ds: + assert ds.coords["latitude"].attrs["bounds"] == "latitude_bnds" + assert ds.coords["longitude"].attrs["bounds"] == "longitude_bnds" + assert "coordinates" not in ds["variable"].attrs + assert "coordinates" not in ds.attrs def test_coordinate_variables_after_dataset_roundtrip(self) -> None: original = self._create_cf_dataset() @@ -1194,36 +1194,30 @@ def equals_latlon(obj): ) with self.roundtrip(original) as actual: assert_identical(actual, original) - with create_tmp_file() as tmp_file: - original.to_netcdf(tmp_file) - with open_dataset(tmp_file, decode_coords=False) as ds: - assert equals_latlon(ds["temp"].attrs["coordinates"]) - assert equals_latlon(ds["precip"].attrs["coordinates"]) - assert "coordinates" not in ds.attrs - assert "coordinates" not in ds["lat"].attrs - assert "coordinates" not in ds["lon"].attrs + with self.roundtrip(original, open_kwargs=dict(decode_coords=False)) as ds: + assert equals_latlon(ds["temp"].attrs["coordinates"]) + assert equals_latlon(ds["precip"].attrs["coordinates"]) + assert "coordinates" not in ds.attrs + assert "coordinates" not in ds["lat"].attrs + assert "coordinates" not in ds["lon"].attrs modified = original.drop_vars(["temp", "precip"]) with self.roundtrip(modified) as actual: assert_identical(actual, modified) - with create_tmp_file() as tmp_file: - modified.to_netcdf(tmp_file) - with open_dataset(tmp_file, decode_coords=False) as ds: - assert equals_latlon(ds.attrs["coordinates"]) - assert "coordinates" not in ds["lat"].attrs - assert "coordinates" not in ds["lon"].attrs + with self.roundtrip(modified, open_kwargs=dict(decode_coords=False)) as ds: + assert equals_latlon(ds.attrs["coordinates"]) + assert "coordinates" not in ds["lat"].attrs + assert "coordinates" not in ds["lon"].attrs original["temp"].encoding["coordinates"] = "lat" with self.roundtrip(original) as actual: assert_identical(actual, original) original["precip"].encoding["coordinates"] = "lat" - with create_tmp_file() as tmp_file: - original.to_netcdf(tmp_file) - with open_dataset(tmp_file, decode_coords=True) as ds: - assert "lon" not in ds["temp"].encoding["coordinates"] - assert "lon" not in ds["precip"].encoding["coordinates"] - assert "coordinates" not in ds["lat"].encoding - assert "coordinates" not in ds["lon"].encoding + with self.roundtrip(original, open_kwargs=dict(decode_coords=True)) as ds: + assert "lon" not in ds["temp"].encoding["coordinates"] + assert "lon" not in ds["precip"].encoding["coordinates"] + assert "coordinates" not in ds["lat"].encoding + assert "coordinates" not in ds["lon"].encoding def test_roundtrip_endian(self) -> None: skip_if_zarr_format_3("zarr v3 has not implemented endian support yet") @@ -2322,7 +2316,9 @@ def test_read_non_consolidated_warning(self) -> None: assert_identical(ds, expected) def test_non_existent_store(self) -> None: - with pytest.raises(FileNotFoundError, match="No such file or directory"): + with pytest.raises( + FileNotFoundError, match="(No such file or directory|Unable to find group)" + ): xr.open_zarr(f"{uuid.uuid4()}") @pytest.mark.skipif(has_zarr_v3, reason="chunk_store not implemented in zarr v3") @@ -2552,6 +2548,7 @@ def test_chunk_encoding_with_dask(self) -> None: # don't actually check equality because the data could be corrupted pass + @requires_netcdf def test_drop_encoding(self): with open_example_dataset("example_1.nc") as ds: encodings = {v: {**ds[v].encoding} for v in ds.data_vars} @@ -3274,7 +3271,7 @@ def create_zarr_target(self): pytest.skip("Instrumented tests only work on latest Zarr.") if has_zarr_v3: - kwargs = {"mode": "a"} + kwargs = {"read_only": False} else: kwargs = {} # type: ignore[arg-type,unused-ignore] @@ -3316,10 +3313,10 @@ def test_append(self) -> None: if has_zarr_v3: # TOOD: verify these expected = { - "set": 17, - "get": 12, + "set": 5, + "get": 7, "list_dir": 3, - "list_prefix": 0, + "list_prefix": 1, } else: expected = { @@ -3341,10 +3338,10 @@ def test_append(self) -> None: # 6057128b: {'iter': 5, 'contains': 2, 'setitem': 5, 'getitem': 10, "listdir": 5, "list_prefix": 0} if has_zarr_v3: expected = { - "set": 10, + "set": 4, "get": 16, # TODO: fixme upstream (should be 8) "list_dir": 3, # TODO: fixme upstream (should be 2) - "list_prefix": 0, + "list_prefix": 1, } else: expected = { @@ -3367,7 +3364,7 @@ def test_append(self) -> None: "set": 10, "get": 16, # TODO: fixme upstream (should be 8) "list_dir": 3, # TODO: fixme upstream (should be 2) - "list_prefix": 0, + "list_prefix": 2, } else: expected = { @@ -3491,7 +3488,7 @@ class TestZarrDictStore(ZarrBase): @contextlib.contextmanager def create_zarr_target(self): if has_zarr_v3: - yield zarr.storage.MemoryStore({}, mode="a") + yield zarr.storage.MemoryStore({}, read_only=False) else: yield {} From c0af9eec53ffab177e2723c37678604238d59e30 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 18 Nov 2024 10:37:16 -0700 Subject: [PATCH 2/7] More guards with mode="w" --- xarray/backends/zarr.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py index 732f33b6791..3ee1d254175 100644 --- a/xarray/backends/zarr.py +++ b/xarray/backends/zarr.py @@ -892,15 +892,20 @@ def store( f"dataset dimensions {existing_dims}" ) - existing_variable_names = { - vn for vn in variables if _encode_variable_name(vn) in existing_keys - } - new_variable_names = set(variables) - existing_variable_names + if self._mode == "w": + # always overwrite + new_variable_names = set(variables) + else: + existing_variable_names = { + vn for vn in variables if _encode_variable_name(vn) in existing_keys + } + new_variable_names = set(variables) - existing_variable_names + variables_encoded, attributes = self.encode( {vn: variables[vn] for vn in new_variable_names}, attributes ) - if existing_variable_names: + if self._mode in ["a", "a-", "r+"] and existing_variable_names: # We make sure that values to be appended are encoded *exactly* # as the current values in the store. # To do so, we decode variables directly to access the proper encoding, @@ -917,10 +922,9 @@ def store( # Modified variables must use the same encoding as the store. vars_with_encoding = {} for vn in existing_variable_names: - if self._mode in ["a", "a-", "r+"]: - _validate_datatypes_for_zarr_append( - vn, existing_vars[vn], variables[vn] - ) + _validate_datatypes_for_zarr_append( + vn, existing_vars[vn], variables[vn] + ) vars_with_encoding[vn] = variables[vn].copy(deep=False) vars_with_encoding[vn].encoding = existing_vars[vn].encoding vars_with_encoding, _ = self.encode(vars_with_encoding, {}) @@ -1018,7 +1022,7 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No write_region = self._write_region if self._write_region is not None else {} write_region = {dim: write_region.get(dim, slice(None)) for dim in dims} - if name in existing_keys: + if self._mode != "w" and name in existing_keys: # existing variable # TODO: if mode="a", consider overriding the existing variable # metadata. This would need some case work properly with region @@ -1086,16 +1090,14 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No shape=zarr_shape, ) - if name not in existing_keys: + if self._mode == "w" or name not in existing_keys: # new variable - encoded_attrs = {} + encoded_attrs = {k: self.encode_attribute(v) for k, v in attrs.items()} # the magic for storing the hidden dimension data if is_zarr_v3_format: encoding["dimension_names"] = dims else: encoded_attrs[DIMENSION_KEY] = dims - for k2, v2 in attrs.items(): - encoded_attrs[k2] = self.encode_attribute(v2) if coding.strings.check_vlen_dtype(dtype) is str: dtype = str @@ -1117,6 +1119,7 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No shape=shape, dtype=dtype, fill_value=fill_value, + exists_ok=True if self._mode == "w" else False, **encoding, ) zarr_array = _put_attrs(zarr_array, encoded_attrs) From a990970d87c21290a5ed6c3ff7115f022a46d668 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 18 Nov 2024 10:41:03 -0700 Subject: [PATCH 3/7] refactoring --- xarray/backends/zarr.py | 170 ++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 78 deletions(-) diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py index 3ee1d254175..b8d948d6f87 100644 --- a/xarray/backends/zarr.py +++ b/xarray/backends/zarr.py @@ -38,6 +38,7 @@ if TYPE_CHECKING: from io import BufferedIOBase + from zarr import Array as ZarrArray from zarr import Group as ZarrGroup from xarray.backends.common import AbstractDataStore @@ -868,16 +869,27 @@ def store( """ import zarr - existing_keys = tuple(self.zarr_group.array_keys()) + if self._mode == "w": + # always overwrite, so we don't care about existing names, + # and consistency of encoding + new_variable_names = set(variables) + existing_keys = {} + existing_variable_names = {} + else: + existing_keys = tuple(self.zarr_group.array_keys()) + existing_variable_names = { + vn for vn in variables if _encode_variable_name(vn) in existing_keys + } + new_variable_names = set(variables) - existing_variable_names - if self._mode == "r+": - new_names = [k for k in variables if k not in existing_keys] - if new_names: - raise ValueError( - f"dataset contains non-pre-existing variables {new_names}, " - "which is not allowed in ``xarray.Dataset.to_zarr()`` with " - "``mode='r+'``. To allow writing new variables, set ``mode='a'``." - ) + if self._mode == "r+" and ( + new_names := [k for k in variables if k not in existing_keys] + ): + raise ValueError( + f"dataset contains non-pre-existing variables {new_names!r}, " + "which is not allowed in ``xarray.Dataset.to_zarr()`` with " + "``mode='r+'``. To allow writing new variables, set ``mode='a'``." + ) if self._append_dim is not None and self._append_dim not in existing_keys: # For dimensions without coordinate values, we must parse @@ -892,20 +904,11 @@ def store( f"dataset dimensions {existing_dims}" ) - if self._mode == "w": - # always overwrite - new_variable_names = set(variables) - else: - existing_variable_names = { - vn for vn in variables if _encode_variable_name(vn) in existing_keys - } - new_variable_names = set(variables) - existing_variable_names - variables_encoded, attributes = self.encode( {vn: variables[vn] for vn in new_variable_names}, attributes ) - if self._mode in ["a", "a-", "r+"] and existing_variable_names: + if existing_variable_names: # We make sure that values to be appended are encoded *exactly* # as the current values in the store. # To do so, we decode variables directly to access the proper encoding, @@ -969,6 +972,70 @@ def store( def sync(self): pass + def _open_existing_array(self, *, name) -> ZarrArray: + import zarr + + # TODO: if mode="a", consider overriding the existing variable + # metadata. This would need some case work properly with region + # and append_dim. + if self._write_empty is not None: + # Write to zarr_group.chunk_store instead of zarr_group.store + # See https://github.com/pydata/xarray/pull/8326#discussion_r1365311316 for a longer explanation + # The open_consolidated() enforces a mode of r or r+ + # (and to_zarr with region provided enforces a read mode of r+), + # and this function makes sure the resulting Group has a store of type ConsolidatedMetadataStore + # and a 'normal Store subtype for chunk_store. + # The exact type depends on if a local path was used, or a URL of some sort, + # but the point is that it's not a read-only ConsolidatedMetadataStore. + # It is safe to write chunk data to the chunk_store because no metadata would be changed by + # to_zarr with the region parameter: + # - Because the write mode is enforced to be r+, no new variables can be added to the store + # (this is also checked and enforced in xarray.backends.api.py::to_zarr()). + # - Existing variables already have their attrs included in the consolidated metadata file. + # - The size of dimensions can not be expanded, that would require a call using `append_dim` + # which is mutually exclusive with `region` + zarr_array = zarr.open( + store=( + self.zarr_group.store if _zarr_v3() else self.zarr_group.chunk_store + ), + # TODO: see if zarr should normalize these strings. + path="/".join([self.zarr_group.name.rstrip("/"), name]).lstrip("/"), + write_empty_chunks=self._write_empty, + ) + else: + zarr_array = self.zarr_group[name] + + return zarr_array + + def _create_new_array( + self, *, name, shape, dtype, fill_value, encoding, attrs + ) -> ZarrArray: + if coding.strings.check_vlen_dtype(dtype) is str: + dtype = str + + if self._write_empty is not None: + if ( + "write_empty_chunks" in encoding + and encoding["write_empty_chunks"] != self._write_empty + ): + raise ValueError( + 'Differing "write_empty_chunks" values in encoding and parameters' + f'Got {encoding["write_empty_chunks"] = } and {self._write_empty = }' + ) + else: + encoding["write_empty_chunks"] = self._write_empty + + zarr_array = self.zarr_group.create( + name, + shape=shape, + dtype=dtype, + fill_value=fill_value, + exists_ok=True if self._mode == "w" else False, + **encoding, + ) + zarr_array = _put_attrs(zarr_array, attrs) + return zarr_array + def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=None): """ This provides a centralized method to set the variables on the data @@ -987,8 +1054,6 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No dimensions. """ - import zarr - existing_keys = tuple(self.zarr_group.array_keys()) is_zarr_v3_format = _zarr_v3() and self.zarr_group.metadata.zarr_format == 3 @@ -1017,47 +1082,13 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No else: del v.encoding["_FillValue"] - zarr_array = None zarr_shape = None write_region = self._write_region if self._write_region is not None else {} write_region = {dim: write_region.get(dim, slice(None)) for dim in dims} if self._mode != "w" and name in existing_keys: # existing variable - # TODO: if mode="a", consider overriding the existing variable - # metadata. This would need some case work properly with region - # and append_dim. - if self._write_empty is not None: - # Write to zarr_group.chunk_store instead of zarr_group.store - # See https://github.com/pydata/xarray/pull/8326#discussion_r1365311316 for a longer explanation - # The open_consolidated() enforces a mode of r or r+ - # (and to_zarr with region provided enforces a read mode of r+), - # and this function makes sure the resulting Group has a store of type ConsolidatedMetadataStore - # and a 'normal Store subtype for chunk_store. - # The exact type depends on if a local path was used, or a URL of some sort, - # but the point is that it's not a read-only ConsolidatedMetadataStore. - # It is safe to write chunk data to the chunk_store because no metadata would be changed by - # to_zarr with the region parameter: - # - Because the write mode is enforced to be r+, no new variables can be added to the store - # (this is also checked and enforced in xarray.backends.api.py::to_zarr()). - # - Existing variables already have their attrs included in the consolidated metadata file. - # - The size of dimensions can not be expanded, that would require a call using `append_dim` - # which is mutually exclusive with `region` - zarr_array = zarr.open( - store=( - self.zarr_group.store - if _zarr_v3() - else self.zarr_group.chunk_store - ), - # TODO: see if zarr should normalize these strings. - path="/".join([self.zarr_group.name.rstrip("/"), name]).lstrip( - "/" - ), - write_empty_chunks=self._write_empty, - ) - else: - zarr_array = self.zarr_group[name] - + zarr_array = self._open_existing_array(name=name) if self._append_dim is not None and self._append_dim in dims: # resize existing variable append_axis = dims.index(self._append_dim) @@ -1098,31 +1129,14 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No encoding["dimension_names"] = dims else: encoded_attrs[DIMENSION_KEY] = dims - - if coding.strings.check_vlen_dtype(dtype) is str: - dtype = str - - if self._write_empty is not None: - if ( - "write_empty_chunks" in encoding - and encoding["write_empty_chunks"] != self._write_empty - ): - raise ValueError( - 'Differing "write_empty_chunks" values in encoding and parameters' - f'Got {encoding["write_empty_chunks"] = } and {self._write_empty = }' - ) - else: - encoding["write_empty_chunks"] = self._write_empty - - zarr_array = self.zarr_group.create( - name, - shape=shape, + zarr_array = self._create_new_array( + name=name, dtype=dtype, + shape=shape, fill_value=fill_value, - exists_ok=True if self._mode == "w" else False, - **encoding, + encoding=encoding, + attrs=encoded_attrs, ) - zarr_array = _put_attrs(zarr_array, encoded_attrs) writer.add(v.data, zarr_array, region) From c07ed88bd66194d719afc4300dce91298a0fa389 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 18 Nov 2024 10:54:51 -0700 Subject: [PATCH 4/7] tweak expected requestsC --- xarray/tests/test_backends.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index ac1e08b266a..53aceae84cb 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -3341,7 +3341,7 @@ def test_append(self) -> None: "set": 4, "get": 16, # TODO: fixme upstream (should be 8) "list_dir": 3, # TODO: fixme upstream (should be 2) - "list_prefix": 1, + "list_prefix": 0, } else: expected = { @@ -3361,10 +3361,10 @@ def test_append(self) -> None: if has_zarr_v3: expected = { - "set": 10, + "set": 4, "get": 16, # TODO: fixme upstream (should be 8) "list_dir": 3, # TODO: fixme upstream (should be 2) - "list_prefix": 2, + "list_prefix": 0, } else: expected = { From 4563efece2267acd7fb1076bd3c276d0452dcc41 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 18 Nov 2024 11:03:41 -0700 Subject: [PATCH 5/7] compat --- xarray/backends/zarr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py index f058a9617d2..42e143e1885 100644 --- a/xarray/backends/zarr.py +++ b/xarray/backends/zarr.py @@ -1029,7 +1029,6 @@ def _create_new_array( shape=shape, dtype=dtype, fill_value=fill_value, - exists_ok=True if self._mode == "w" else False, **encoding, ) zarr_array = _put_attrs(zarr_array, attrs) @@ -1126,8 +1125,11 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No # the magic for storing the hidden dimension data if is_zarr_v3_format: encoding["dimension_names"] = dims + encoding["exists_ok"] = (True if self._mode == "w" else False,) else: encoded_attrs[DIMENSION_KEY] = dims + encoding["overwrite"] = (True if self._mode == "w" else False,) + zarr_array = self._create_new_array( name=name, dtype=dtype, From ca30cd808afe0dfb9317d1444cf8f18721b7c6d4 Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 18 Nov 2024 11:09:11 -0700 Subject: [PATCH 6/7] more compat --- xarray/backends/zarr.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py index 42e143e1885..676e64446d5 100644 --- a/xarray/backends/zarr.py +++ b/xarray/backends/zarr.py @@ -1125,10 +1125,12 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No # the magic for storing the hidden dimension data if is_zarr_v3_format: encoding["dimension_names"] = dims - encoding["exists_ok"] = (True if self._mode == "w" else False,) else: encoded_attrs[DIMENSION_KEY] = dims - encoding["overwrite"] = (True if self._mode == "w" else False,) + + encoding["exists_ok" if _zarr_v3() else "overwrite"] = ( + True if self._mode == "w" else False + ) zarr_array = self._create_new_array( name=name, From f2e91e9f894a3ff5e27bd9e6c3a90f7fcbcfd19f Mon Sep 17 00:00:00 2001 From: Deepak Cherian Date: Mon, 18 Nov 2024 15:00:19 -0700 Subject: [PATCH 7/7] fix --- xarray/tests/test_backends.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 53aceae84cb..4fc68f176ab 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -1174,6 +1174,7 @@ def test_grid_mapping_and_bounds_are_coordinates_after_dataarray_roundtrip( assert_identical(actual, original["variable"].to_dataset()) @requires_iris + @requires_netcdf def test_coordinate_variables_after_iris_roundtrip(self) -> None: original = self._create_cf_dataset() iris_cube = original["variable"].to_iris() @@ -3394,7 +3395,7 @@ def test_region_write(self) -> None: "set": 5, "get": 10, "list_dir": 3, - "list_prefix": 0, + "list_prefix": 4, } else: expected = {