Skip to content

Error on invalid store mode #3068

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/3068.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Trying to open an array with ``mode='r'`` when the store is not read-only now raises an error.
11 changes: 7 additions & 4 deletions src/zarr/storage/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import json
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Any, Literal, Self

from zarr.abc.store import ByteRequest, Store
from zarr.core.buffer import Buffer, default_buffer_prototype
Expand Down Expand Up @@ -48,9 +48,7 @@
return self.store.read_only

@classmethod
async def open(
cls, store: Store, path: str, mode: AccessModeLiteral | None = None
) -> StorePath:
async def open(cls, store: Store, path: str, mode: AccessModeLiteral | None = None) -> Self:
"""
Open StorePath based on the provided mode.

Expand All @@ -67,6 +65,9 @@
------
FileExistsError
If the mode is 'w-' and the store path already exists.
ValueError
If the mode is not "r" and the store is read-only, or
if the mode is "r" and the store is not read-only.
"""

await store._ensure_open()
Expand All @@ -78,6 +79,8 @@

if store.read_only and mode != "r":
raise ValueError(f"Store is read-only but mode is '{mode}'")
if not store.read_only and mode == "r":
raise ValueError(f"Store is not read-only but mode is '{mode}'")

Check warning on line 83 in src/zarr/storage/_common.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/storage/_common.py#L83

Added line #L83 was not covered by tests

match mode:
case "w-":
Expand Down
4 changes: 2 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_v2_and_v3_exist_at_same_path(store: Store) -> None:
zarr.create_array(store, shape=(10,), dtype="uint8", zarr_format=2)
msg = f"Both zarr.json (Zarr format 3) and .zarray (Zarr format 2) metadata objects exist at {store}. Zarr v3 will be used."
with pytest.warns(UserWarning, match=re.escape(msg)):
zarr.open(store=store, mode="r")
zarr.open(store=store)


@pytest.mark.parametrize("store", ["memory"], indirect=True)
Expand Down Expand Up @@ -1285,7 +1285,7 @@ def test_no_overwrite_open(tmp_path: Path, open_func: Callable, mode: str) -> No
existing_fpath = add_empty_file(tmp_path)

assert existing_fpath.exists()
with contextlib.suppress(FileExistsError, FileNotFoundError):
with contextlib.suppress(FileExistsError, FileNotFoundError, ValueError):
open_func(store=store, mode=mode)
if mode == "w":
assert not existing_fpath.exists()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1473,7 +1473,7 @@ async def test_name(store: Store, zarr_format: ZarrFormat, path: str | None) ->
for parent_path in parents:
# this will raise if these groups were not created
_ = await zarr.api.asynchronous.open_group(
store=store, path=parent_path, mode="r", zarr_format=zarr_format
store=store, path=parent_path, zarr_format=zarr_format
)


Expand Down Expand Up @@ -1661,7 +1661,7 @@ def test_roundtrip_numcodecs() -> None:

BYTES_CODEC = {"name": "bytes", "configuration": {"endian": "little"}}
# Read in the array again and check compressor config
root = zarr.open_group(store, mode="r")
root = zarr.open_group(store)
metadata = root["test"].metadata.to_dict()
expected = (*filters, BYTES_CODEC, *compressors)
assert metadata["codecs"] == expected
Expand Down
8 changes: 8 additions & 0 deletions tests/test_store/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest
from _pytest.compat import LEGACY_PATH

import zarr
from zarr import Group
from zarr.core.common import AccessModeLiteral, ZarrFormat
from zarr.storage import FsspecStore, LocalStore, MemoryStore, StoreLike, StorePath
Expand Down Expand Up @@ -251,3 +252,10 @@ def test_relativize_path_invalid() -> None:
msg = f"The first component of {path} does not start with {prefix}."
with pytest.raises(ValueError, match=msg):
_relativize_path(path="a/b/c", prefix="b")


def test_invalid_open_mode() -> None:
store = MemoryStore()
zarr.create((100,), store=store, zarr_format=2, path="a")
with pytest.raises(ValueError, match="Store is not read-only but mode is 'r'"):
zarr.open_array(store=store, path="a", zarr_format=2, mode="r")