Skip to content

Commit 9995939

Browse files
authored
remove clobber from normalize_store_arg to enable writes to consolidated FSStore groups (#976)
1 parent 84d8f7c commit 9995939

File tree

7 files changed

+64
-18
lines changed

7 files changed

+64
-18
lines changed

docs/release.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ Release notes
66
Unreleased
77
----------
88

9+
10+
Bug fixes
11+
~~~~~~~~~
12+
13+
* Removed `clobber` argument from `normalize_store_arg`. This enables to change
14+
data within a opened consolidated group using mode `"r+"` (i.e region write).
15+
By :user:`Tobias Kölling <d70-t>` :issue:`975`.
16+
917
.. _release_2.11.0:
1018

1119
2.11.0

zarr/convenience.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,10 @@ def open(store: StoreLike = None, mode: str = "a", **kwargs):
7777

7878
path = kwargs.get('path')
7979
# handle polymorphic store arg
80-
clobber = mode == 'w'
8180
# we pass storage options explicitly, since normalize_store_arg might construct
8281
# a store if the input is a fsspec-compatible URL
8382
_store: BaseStore = normalize_store_arg(
84-
store, clobber=clobber, storage_options=kwargs.pop("storage_options", {})
83+
store, storage_options=kwargs.pop("storage_options", {}), mode=mode
8584
)
8685
path = normalize_storage_path(path)
8786

@@ -142,7 +141,7 @@ def save_array(store: StoreLike, arr, **kwargs):
142141
143142
"""
144143
may_need_closing = _might_close(store)
145-
_store: BaseStore = normalize_store_arg(store, clobber=True)
144+
_store: BaseStore = normalize_store_arg(store, mode="w")
146145
try:
147146
_create_array(arr, store=_store, overwrite=True, **kwargs)
148147
finally:
@@ -213,7 +212,7 @@ def save_group(store: StoreLike, *args, **kwargs):
213212
raise ValueError('at least one array must be provided')
214213
# handle polymorphic store arg
215214
may_need_closing = _might_close(store)
216-
_store: BaseStore = normalize_store_arg(store, clobber=True)
215+
_store: BaseStore = normalize_store_arg(store, mode="w")
217216
try:
218217
grp = _create_group(_store, overwrite=True)
219218
for i, arr in enumerate(args):
@@ -1117,7 +1116,7 @@ def consolidate_metadata(store: StoreLike, metadata_key=".zmetadata"):
11171116
open_consolidated
11181117
11191118
"""
1120-
store = normalize_store_arg(store, clobber=True)
1119+
store = normalize_store_arg(store, mode="w")
11211120

11221121
def is_zarr_key(key):
11231122
return (key.endswith('.zarray') or key.endswith('.zgroup') or
@@ -1179,7 +1178,7 @@ def open_consolidated(store: StoreLike, metadata_key=".zmetadata", mode="r+", **
11791178
from .storage import ConsolidatedMetadataStore
11801179

11811180
# normalize parameters
1182-
store = normalize_store_arg(store, storage_options=kwargs.get("storage_options"))
1181+
store = normalize_store_arg(store, storage_options=kwargs.get("storage_options"), mode=mode)
11831182
if mode not in {'r', 'r+'}:
11841183
raise ValueError("invalid mode, expected either 'r' or 'r+'; found {!r}"
11851184
.format(mode))

zarr/creation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,11 +490,11 @@ def open_array(
490490
# a : read/write if exists, create otherwise (default)
491491

492492
# handle polymorphic store arg
493-
clobber = (mode == 'w')
494-
store = normalize_store_arg(store, clobber=clobber, storage_options=storage_options, mode=mode)
493+
store = normalize_store_arg(store, storage_options=storage_options, mode=mode)
495494
if chunk_store is not None:
496-
chunk_store = normalize_store_arg(chunk_store, clobber=clobber,
497-
storage_options=storage_options)
495+
chunk_store = normalize_store_arg(chunk_store,
496+
storage_options=storage_options,
497+
mode=mode)
498498
path = normalize_storage_path(path)
499499

500500
# API compatibility with h5py

zarr/hierarchy.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,10 +1051,10 @@ def move(self, source, dest):
10511051
self._write_op(self._move_nosync, source, dest)
10521052

10531053

1054-
def _normalize_store_arg(store, *, clobber=False, storage_options=None, mode=None):
1054+
def _normalize_store_arg(store, *, storage_options=None, mode="r"):
10551055
if store is None:
10561056
return MemoryStore()
1057-
return normalize_store_arg(store, clobber=clobber,
1057+
return normalize_store_arg(store,
10581058
storage_options=storage_options, mode=mode)
10591059

10601060

@@ -1164,13 +1164,13 @@ def open_group(store=None, mode='a', cache_attrs=True, synchronizer=None, path=N
11641164
"""
11651165

11661166
# handle polymorphic store arg
1167-
clobber = mode != "r"
11681167
store = _normalize_store_arg(
1169-
store, clobber=clobber, storage_options=storage_options, mode=mode
1168+
store, storage_options=storage_options, mode=mode
11701169
)
11711170
if chunk_store is not None:
1172-
chunk_store = _normalize_store_arg(chunk_store, clobber=clobber,
1173-
storage_options=storage_options)
1171+
chunk_store = _normalize_store_arg(chunk_store,
1172+
storage_options=storage_options,
1173+
mode=mode)
11741174
path = normalize_storage_path(path)
11751175

11761176
# ensure store is initialized

zarr/storage.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,12 @@ def contains_group(store: StoreLike, path: Path = None) -> bool:
104104
return key in store
105105

106106

107-
def normalize_store_arg(store: Any, clobber=False, storage_options=None, mode="w") -> BaseStore:
107+
def normalize_store_arg(store: Any, storage_options=None, mode="r") -> BaseStore:
108108
if store is None:
109109
return BaseStore._ensure_store(dict())
110110
elif isinstance(store, os.PathLike):
111111
store = os.fspath(store)
112112
if isinstance(store, str):
113-
mode = mode if clobber else "r"
114113
if "://" in store or "::" in store:
115114
return FSStore(store, mode=mode, **(storage_options or {}))
116115
elif storage_options:

zarr/tests/test_convenience.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ def test_consolidate_metadata():
175175
open_consolidated(store, mode='a')
176176
with pytest.raises(ValueError):
177177
open_consolidated(store, mode='w')
178+
with pytest.raises(ValueError):
179+
open_consolidated(store, mode='w-')
178180

179181
# make sure keyword arguments are passed through without error
180182
open_consolidated(store, cache_attrs=True, synchronizer=None)
@@ -224,6 +226,8 @@ def test_consolidated_with_chunk_store():
224226
open_consolidated(store, mode='a', chunk_store=chunk_store)
225227
with pytest.raises(ValueError):
226228
open_consolidated(store, mode='w', chunk_store=chunk_store)
229+
with pytest.raises(ValueError):
230+
open_consolidated(store, mode='w-', chunk_store=chunk_store)
227231

228232
# make sure keyword arguments are passed through without error
229233
open_consolidated(store, cache_attrs=True, synchronizer=None,

zarr/tests/test_storage.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,42 @@ def test_create(self):
10211021
with pytest.raises(PermissionError):
10221022
g.data[:] = 1
10231023

1024+
@pytest.mark.parametrize('mode,allowed', [("r", False), ("r+", True)])
1025+
def test_modify_consolidated(self, mode, allowed):
1026+
import zarr
1027+
url = "file://" + tempfile.mkdtemp()
1028+
1029+
# create
1030+
root = zarr.open_group(url, mode="w")
1031+
root.zeros('baz', shape=(10000, 10000), chunks=(1000, 1000), dtype='i4')
1032+
zarr.consolidate_metadata(url)
1033+
1034+
# reopen and modify
1035+
root = zarr.open_consolidated(url, mode=mode)
1036+
if allowed:
1037+
root["baz"][0, 0] = 7
1038+
1039+
root = zarr.open_consolidated(url, mode="r")
1040+
assert root["baz"][0, 0] == 7
1041+
else:
1042+
with pytest.raises(zarr.errors.ReadOnlyError):
1043+
root["baz"][0, 0] = 7
1044+
1045+
@pytest.mark.parametrize('mode', ["r", "r+"])
1046+
def test_modify_consolidated_metadata_raises(self, mode):
1047+
import zarr
1048+
url = "file://" + tempfile.mkdtemp()
1049+
1050+
# create
1051+
root = zarr.open_group(url, mode="w")
1052+
root.zeros('baz', shape=(10000, 10000), chunks=(1000, 1000), dtype='i4')
1053+
zarr.consolidate_metadata(url)
1054+
1055+
# reopen and modify
1056+
root = zarr.open_consolidated(url, mode=mode)
1057+
with pytest.raises(zarr.errors.ReadOnlyError):
1058+
root["baz"].resize(100, 100)
1059+
10241060
def test_read_only(self):
10251061
path = tempfile.mkdtemp()
10261062
atexit.register(atexit_rmtree, path)

0 commit comments

Comments
 (0)