Skip to content

Commit dbdf63f

Browse files
committed
update import path to match Zarr v2.12 and v2.13 experimental API
remove path='xarray' default for zarr v3 path=None should work as of Zarr v2.13
1 parent d590b76 commit dbdf63f

File tree

3 files changed

+72
-31
lines changed

3 files changed

+72
-31
lines changed

xarray/backends/zarr.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,7 @@ def open_group(
371371

372372
if zarr_version is None:
373373
# default to 2 if store doesn't specify it's version (e.g. a path)
374-
zarr_version = getattr(store, '_store_version', 2)
375-
376-
if zarr_version > 2 and group is None:
377-
# v3 stores require a group name: use 'xarray' as a default one.
378-
group = 'xarray'
374+
zarr_version = getattr(store, "_store_version", 2)
379375

380376
open_kwargs = dict(
381377
mode=mode,
@@ -463,7 +459,7 @@ def open_store_variable(self, name, zarr_array):
463459
# TODO: how to properly handle 'filters' for v3 stores
464460
# currently these use a hack to store 'filters' within attributes
465461
# need to drop this here for V3 store tests to succeed
466-
attributes.pop('filters', None)
462+
attributes.pop("filters", None)
467463

468464
encoding = {
469465
"chunks": zarr_array.chunks,
@@ -592,11 +588,11 @@ def store(
592588
self.set_variables(
593589
variables_encoded, check_encoding_set, writer, unlimited_dims=unlimited_dims
594590
)
595-
zarr_version = getattr(self.zarr_group.store, '_store_version', 3)
591+
zarr_version = getattr(self.zarr_group.store, "_store_version", 3)
596592
consolidate_kwargs = {}
597593
if zarr_version > 2:
598-
# zarr v3 spec requires providing a path
599-
consolidate_kwargs['path'] = self.zarr_group.path
594+
# If the group has a path, it needs to also be passed to consolidate_metadata
595+
consolidate_kwargs["path"] = self.zarr_group.path
600596
if self._consolidate_on_close:
601597
zarr.consolidate_metadata(self.zarr_group.store, **consolidate_kwargs)
602598

xarray/core/dataset.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1954,6 +1954,7 @@ def to_zarr(
19541954
region: Mapping[str, slice] | None = None,
19551955
safe_chunks: bool = True,
19561956
storage_options: dict[str, str] | None = None,
1957+
zarr_version : int | None = None,
19571958
) -> ZarrStore | Delayed:
19581959
"""Write dataset contents to a zarr group.
19591960

xarray/tests/test_backends.py

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,17 @@
104104
have_zarr_kvstore = False
105105
try:
106106
from zarr.storage import KVStore
107+
107108
have_zarr_kvstore = True
108109
except ImportError:
109110
KVStore = None
110111

111112
have_zarr_v3 = False
112113
try:
113-
from zarr.storage_v3 import DirectoryStoreV3, KVStoreV3
114+
# as of Zarr v2.13 these imports require environment variable
115+
# ZARR_V3_EXPERIMENTAL_API=1
116+
from zarr import DirectoryStoreV3, KVStoreV3
117+
114118
have_zarr_v3 = True
115119
except ImportError:
116120
KVStoreV3 = None
@@ -1705,14 +1709,18 @@ def create_zarr_target(self):
17051709
@contextlib.contextmanager
17061710
def create_store(self):
17071711
with self.create_zarr_target() as store_target:
1708-
yield backends.ZarrStore.open_group(store_target, mode="w", **self.version_kwargs)
1712+
yield backends.ZarrStore.open_group(
1713+
store_target, mode="w", **self.version_kwargs
1714+
)
17091715

17101716
def save(self, dataset, store_target, **kwargs):
17111717
return dataset.to_zarr(store=store_target, **kwargs, **self.version_kwargs)
17121718

17131719
@contextlib.contextmanager
17141720
def open(self, store_target, **kwargs):
1715-
with xr.open_dataset(store_target, engine="zarr", **kwargs, **self.version_kwargs) as ds:
1721+
with xr.open_dataset(
1722+
store_target, engine="zarr", **kwargs, **self.version_kwargs
1723+
) as ds:
17161724
yield ds
17171725

17181726
@contextlib.contextmanager
@@ -2040,9 +2048,13 @@ def test_write_persistence_modes(self, group):
20402048
ds, ds_to_append, _ = create_append_test_data()
20412049
with self.create_zarr_target() as store_target:
20422050
ds.to_zarr(store_target, mode="w", group=group, **self.version_kwargs)
2043-
ds_to_append.to_zarr(store_target, append_dim="time", group=group, **self.version_kwargs)
2051+
ds_to_append.to_zarr(
2052+
store_target, append_dim="time", group=group, **self.version_kwargs
2053+
)
20442054
original = xr.concat([ds, ds_to_append], dim="time")
2045-
actual = xr.open_dataset(store_target, group=group, engine="zarr", **self.version_kwargs)
2055+
actual = xr.open_dataset(
2056+
store_target, group=group, engine="zarr", **self.version_kwargs
2057+
)
20462058
assert_identical(original, actual)
20472059

20482060
def test_compressor_encoding(self):
@@ -2104,13 +2116,19 @@ def test_append_with_invalid_dim_raises(self):
21042116
with pytest.raises(
21052117
ValueError, match="does not match any existing dataset dimensions"
21062118
):
2107-
ds_to_append.to_zarr(store_target, append_dim="notvalid", **self.version_kwargs)
2119+
ds_to_append.to_zarr(
2120+
store_target, append_dim="notvalid", **self.version_kwargs
2121+
)
21082122

21092123
def test_append_with_no_dims_raises(self):
21102124
with self.create_zarr_target() as store_target:
2111-
Dataset({"foo": ("x", [1])}).to_zarr(store_target, mode="w", **self.version_kwargs)
2125+
Dataset({"foo": ("x", [1])}).to_zarr(
2126+
store_target, mode="w", **self.version_kwargs
2127+
)
21122128
with pytest.raises(ValueError, match="different dimension names"):
2113-
Dataset({"foo": ("y", [2])}).to_zarr(store_target, mode="a", **self.version_kwargs)
2129+
Dataset({"foo": ("y", [2])}).to_zarr(
2130+
store_target, mode="a", **self.version_kwargs
2131+
)
21142132

21152133
def test_append_with_append_dim_not_set_raises(self):
21162134
ds, ds_to_append, _ = create_append_test_data()
@@ -2124,7 +2142,9 @@ def test_append_with_mode_not_a_raises(self):
21242142
with self.create_zarr_target() as store_target:
21252143
ds.to_zarr(store_target, mode="w", **self.version_kwargs)
21262144
with pytest.raises(ValueError, match="cannot set append_dim unless"):
2127-
ds_to_append.to_zarr(store_target, mode="w", append_dim="time", **self.version_kwargs)
2145+
ds_to_append.to_zarr(
2146+
store_target, mode="w", append_dim="time", **self.version_kwargs
2147+
)
21282148

21292149
def test_append_with_existing_encoding_raises(self):
21302150
ds, ds_to_append, _ = create_append_test_data()
@@ -2161,11 +2181,15 @@ def test_check_encoding_is_consistent_after_append(self):
21612181
encoding = {"da": {"compressor": compressor}}
21622182
ds.to_zarr(store_target, mode="w", encoding=encoding, **self.version_kwargs)
21632183
ds_to_append.to_zarr(store_target, append_dim="time", **self.version_kwargs)
2164-
actual_ds = xr.open_dataset(store_target, engine="zarr", **self.version_kwargs)
2184+
actual_ds = xr.open_dataset(
2185+
store_target, engine="zarr", **self.version_kwargs
2186+
)
21652187
actual_encoding = actual_ds["da"].encoding["compressor"]
21662188
assert actual_encoding.get_config() == compressor.get_config()
21672189
assert_identical(
2168-
xr.open_dataset(store_target, engine="zarr", **self.version_kwargs).compute(),
2190+
xr.open_dataset(
2191+
store_target, engine="zarr", **self.version_kwargs
2192+
).compute(),
21692193
xr.concat([ds, ds_to_append], dim="time"),
21702194
)
21712195

@@ -2182,7 +2206,8 @@ def test_append_with_new_variable(self):
21822206
combined = xr.concat([ds, ds_to_append], dim="time")
21832207
combined["new_var"] = ds_with_new_var["new_var"]
21842208
assert_identical(
2185-
combined, xr.open_dataset(store_target, engine="zarr", **self.version_kwargs)
2209+
combined,
2210+
xr.open_dataset(store_target, engine="zarr", **self.version_kwargs),
21862211
)
21872212

21882213
@requires_dask
@@ -2289,9 +2314,14 @@ def test_write_region(self, consolidated, compute, use_dask):
22892314
for i in range(0, 10, 2):
22902315
region = {"x": slice(i, i + 2)}
22912316
nonzeros.isel(region).to_zarr(
2292-
store, region=region, consolidated=consolidated, **self.version_kwargs,
2317+
store,
2318+
region=region,
2319+
consolidated=consolidated,
2320+
**self.version_kwargs,
22932321
)
2294-
with xr.open_zarr(store, consolidated=consolidated, **self.version_kwargs) as actual:
2322+
with xr.open_zarr(
2323+
store, consolidated=consolidated, **self.version_kwargs
2324+
) as actual:
22952325
assert_identical(actual, nonzeros)
22962326

22972327
@pytest.mark.parametrize("mode", [None, "r+", "a"])
@@ -2301,7 +2331,9 @@ def test_write_region_mode(self, mode):
23012331
with self.create_zarr_target() as store:
23022332
zeros.to_zarr(store, **self.version_kwargs)
23032333
for region in [{"x": slice(5)}, {"x": slice(5, 10)}]:
2304-
nonzeros.isel(region).to_zarr(store, region=region, mode=mode, **self.version_kwargs)
2334+
nonzeros.isel(region).to_zarr(
2335+
store, region=region, mode=mode, **self.version_kwargs
2336+
)
23052337
with xr.open_zarr(store, **self.version_kwargs) as actual:
23062338
assert_identical(actual, nonzeros)
23072339

@@ -2343,7 +2375,9 @@ def test_write_preexisting_override_metadata(self):
23432375
with self.create_zarr_target() as store:
23442376
original.to_zarr(store, compute=False, **self.version_kwargs)
23452377
# with region, the default mode becomes r+
2346-
both_modified.to_zarr(store, region={"x": slice(None)}, **self.version_kwargs)
2378+
both_modified.to_zarr(
2379+
store, region={"x": slice(None)}, **self.version_kwargs
2380+
)
23472381
with self.open(store) as actual:
23482382
assert_identical(actual, only_new_data)
23492383

@@ -2371,7 +2405,9 @@ def setup_and_verify_store(expected=data):
23712405
"cannot set region unless mode='a', mode='r+' or mode=None"
23722406
),
23732407
):
2374-
data.to_zarr(store, region={"x": slice(None)}, mode="w", **self.version_kwargs)
2408+
data.to_zarr(
2409+
store, region={"x": slice(None)}, mode="w", **self.version_kwargs
2410+
)
23752411

23762412
with setup_and_verify_store() as store:
23772413
with pytest.raises(TypeError, match=r"must be a dict"):
@@ -2383,7 +2419,9 @@ def setup_and_verify_store(expected=data):
23832419

23842420
with setup_and_verify_store() as store:
23852421
with pytest.raises(ValueError, match=r"step on all slices"):
2386-
data2.to_zarr(store, region={"x": slice(None, None, 2)}, **self.version_kwargs)
2422+
data2.to_zarr(
2423+
store, region={"x": slice(None, None, 2)}, **self.version_kwargs
2424+
)
23872425

23882426
with setup_and_verify_store() as store:
23892427
with pytest.raises(
@@ -2397,13 +2435,20 @@ def setup_and_verify_store(expected=data):
23972435
ValueError,
23982436
match=r"all variables in the dataset to write must have at least one dimension in common",
23992437
):
2400-
data2.assign(v=2).to_zarr(store, region={"x": slice(2)}, **self.version_kwargs)
2438+
data2.assign(v=2).to_zarr(
2439+
store, region={"x": slice(2)}, **self.version_kwargs
2440+
)
24012441

24022442
with setup_and_verify_store() as store:
24032443
with pytest.raises(
24042444
ValueError, match=r"cannot list the same dimension in both"
24052445
):
2406-
data.to_zarr(store, region={"x": slice(None)}, append_dim="x", **self.version_kwargs)
2446+
data.to_zarr(
2447+
store,
2448+
region={"x": slice(None)},
2449+
append_dim="x",
2450+
**self.version_kwargs,
2451+
)
24072452

24082453
with setup_and_verify_store() as store:
24092454
with pytest.raises(
@@ -2517,7 +2562,6 @@ def create_store(self):
25172562
yield group
25182563

25192564

2520-
25212565
class ZarrBaseV3(ZarrBase):
25222566
def test_roundtrip_coordinates_with_space(self):
25232567
original = Dataset(coords={"x": 0, "y z": 1})
@@ -2548,7 +2592,7 @@ def create_zarr_target(self):
25482592
class TestZarrDirectoryStoreV3FromPath(TestZarrDirectoryStoreV3):
25492593
# Must specify zarr_version=3 to get a v3 store because create_zarr_target
25502594
# is a string path.
2551-
version_kwargs = {'zarr_version': 3}
2595+
version_kwargs = {"zarr_version": 3}
25522596

25532597
@contextlib.contextmanager
25542598
def create_zarr_target(self):

0 commit comments

Comments
 (0)