Skip to content

Commit ba1f71a

Browse files
authored
Fix zarr.open default for argument mode when store is read_only (#3128)
* fix `zarr.open` default for argument `mode` when store is read_only * document changes and format * assert type in test_load_zip and test_load_local * default mode=None for `zarr.synchronous.open` * update docstring * also check for read_only if store is a StorePath
1 parent 229a6c7 commit ba1f71a

File tree

4 files changed

+45
-4
lines changed

4 files changed

+45
-4
lines changed

changes/3128.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix `zarr.open` default for argument `mode` when `store` is `read_only`

src/zarr/api/asynchronous.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import numpy.typing as npt
1010
from typing_extensions import deprecated
1111

12+
from zarr.abc.store import Store
1213
from zarr.core.array import (
1314
Array,
1415
AsyncArray,
@@ -40,6 +41,7 @@
4041
from zarr.core.metadata import ArrayMetadataDict, ArrayV2Metadata, ArrayV3Metadata
4142
from zarr.core.metadata.v2 import _default_compressor, _default_filters
4243
from zarr.errors import GroupNotFoundError, NodeTypeValidationError
44+
from zarr.storage import StorePath
4345
from zarr.storage._common import make_store_path
4446

4547
if TYPE_CHECKING:
@@ -298,7 +300,7 @@ async def load(
298300
async def open(
299301
*,
300302
store: StoreLike | None = None,
301-
mode: AccessModeLiteral = "a",
303+
mode: AccessModeLiteral | None = None,
302304
zarr_version: ZarrFormat | None = None, # deprecated
303305
zarr_format: ZarrFormat | None = None,
304306
path: str | None = None,
@@ -316,6 +318,7 @@ async def open(
316318
read/write (must exist); 'a' means read/write (create if doesn't
317319
exist); 'w' means create (overwrite if exists); 'w-' means create
318320
(fail if exists).
321+
If the store is read-only, the default is 'r'; otherwise, it is 'a'.
319322
zarr_format : {2, 3, None}, optional
320323
The zarr format to use when saving.
321324
path : str or None, optional
@@ -333,7 +336,11 @@ async def open(
333336
Return type depends on what exists in the given store.
334337
"""
335338
zarr_format = _handle_zarr_version_or_format(zarr_version=zarr_version, zarr_format=zarr_format)
336-
339+
if mode is None:
340+
if isinstance(store, (Store, StorePath)) and store.read_only:
341+
mode = "r"
342+
else:
343+
mode = "a"
337344
store_path = await make_store_path(store, mode=mode, path=path, storage_options=storage_options)
338345

339346
# TODO: the mode check below seems wrong!

src/zarr/api/synchronous.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def load(
162162
def open(
163163
store: StoreLike | None = None,
164164
*,
165-
mode: AccessModeLiteral = "a",
165+
mode: AccessModeLiteral | None = None,
166166
zarr_version: ZarrFormat | None = None, # deprecated
167167
zarr_format: ZarrFormat | None = None,
168168
path: str | None = None,
@@ -180,6 +180,7 @@ def open(
180180
read/write (must exist); 'a' means read/write (create if doesn't
181181
exist); 'w' means create (overwrite if exists); 'w-' means create
182182
(fail if exists).
183+
If the store is read-only, the default is 'r'; otherwise, it is 'a'.
183184
zarr_format : {2, 3, None}, optional
184185
The zarr format to use when saving.
185186
path : str or None, optional

tests/test_api.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
)
4040
from zarr.core.buffer import NDArrayLike
4141
from zarr.errors import MetadataValidationError
42-
from zarr.storage import MemoryStore
42+
from zarr.storage import LocalStore, MemoryStore, ZipStore
4343
from zarr.storage._utils import normalize_path
4444
from zarr.testing.utils import gpu_test
4545

@@ -401,6 +401,38 @@ def test_load_array(sync_store: Store) -> None:
401401
assert_array_equal(bar, array)
402402

403403

404+
@pytest.mark.parametrize("path", ["data", None])
405+
@pytest.mark.parametrize("load_read_only", [True, False, None])
406+
def test_load_zip(tmp_path: pathlib.Path, path: str | None, load_read_only: bool | None) -> None:
407+
file = tmp_path / "test.zip"
408+
data = np.arange(100).reshape(10, 10)
409+
410+
with ZipStore(file, mode="w", read_only=False) as zs:
411+
save(zs, data, path=path)
412+
with ZipStore(file, mode="r", read_only=load_read_only) as zs:
413+
result = zarr.load(store=zs, path=path)
414+
assert isinstance(result, np.ndarray)
415+
assert np.array_equal(result, data)
416+
with ZipStore(file, read_only=load_read_only) as zs:
417+
result = zarr.load(store=zs, path=path)
418+
assert isinstance(result, np.ndarray)
419+
assert np.array_equal(result, data)
420+
421+
422+
@pytest.mark.parametrize("path", ["data", None])
423+
@pytest.mark.parametrize("load_read_only", [True, False])
424+
def test_load_local(tmp_path: pathlib.Path, path: str | None, load_read_only: bool) -> None:
425+
file = tmp_path / "test.zip"
426+
data = np.arange(100).reshape(10, 10)
427+
428+
with LocalStore(file, read_only=False) as zs:
429+
save(zs, data, path=path)
430+
with LocalStore(file, read_only=load_read_only) as zs:
431+
result = zarr.load(store=zs, path=path)
432+
assert isinstance(result, np.ndarray)
433+
assert np.array_equal(result, data)
434+
435+
404436
def test_tree() -> None:
405437
pytest.importorskip("rich")
406438
g1 = zarr.group()

0 commit comments

Comments
 (0)