Skip to content

Add with_read_only() convenience method to store #3138

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 13 commits into from
Jun 19, 2025
Merged
16 changes: 16 additions & 0 deletions src/zarr/abc/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@
await store._open()
return store

def with_read_only(self, read_only: bool = False) -> Store:
"""
Return a new store of the same type pointing to the same location with the specified read_only state.
The returned Store is not automatically opened.

Parameters
----------
read_only
If True, the store will be created in read-only mode. Defaults to False.

Returns
-------
A new store of the same type with the new read only attribute.
"""
raise NotImplementedError("with_read_only is not implemented for this store type.")

Check warning on line 100 in src/zarr/abc/store.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/abc/store.py#L100

Added line #L100 was not covered by tests

def __enter__(self) -> Self:
"""Enter a context manager that will close the store upon exiting."""
return self
Expand Down
7 changes: 7 additions & 0 deletions src/zarr/storage/_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
store_dict = {}
self._store_dict = store_dict

def with_read_only(self, read_only: bool = False) -> MemoryStore:
# docstring inherited
return type(self)(

Check warning on line 59 in src/zarr/storage/_memory.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/storage/_memory.py#L59

Added line #L59 was not covered by tests
store_dict=self._store_dict,
read_only=read_only,
)

async def clear(self) -> None:
# docstring inherited
self._store_dict.clear()
Expand Down
8 changes: 8 additions & 0 deletions src/zarr/testing/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@
):
await store.delete("foo")

async def test_with_read_only_store(self, open_kwargs: dict[str, Any]) -> None:
kwargs = {**open_kwargs, "read_only": True}
store = await self.store_cls.open(**kwargs)
with pytest.raises(

Check warning on line 155 in src/zarr/testing/store.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/testing/store.py#L153-L155

Added lines #L153 - L155 were not covered by tests
NotImplementedError, match="with_read_only is not implemented for this store type."
):
store.with_read_only(read_only=False)

Check warning on line 158 in src/zarr/testing/store.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/testing/store.py#L158

Added line #L158 was not covered by tests

@pytest.mark.parametrize("key", ["c/0", "foo/c/0.0", "foo/0/0"])
@pytest.mark.parametrize(
("data", "byte_range"),
Expand Down
40 changes: 40 additions & 0 deletions tests/test_store/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,46 @@ async def test_deterministic_size(
np.testing.assert_array_equal(a[:3], 1)
np.testing.assert_array_equal(a[3:], 0)

async def test_with_read_only_store(self, open_kwargs: dict[str, Any]) -> None:
kwargs = {**open_kwargs, "read_only": True}
store = await self.store_cls.open(**kwargs)
assert store.read_only

# Test that you cannot write to a read-only store
with pytest.raises(
ValueError, match="store was opened in read-only mode and does not support writing"
):
await store.set("foo", self.buffer_cls.from_bytes(b"bar"))

# Test that you can write to a copy that is not read-only
writer = store.with_read_only(read_only=False)
assert not writer._is_open
assert not writer.read_only
await writer.set("foo", self.buffer_cls.from_bytes(b"bar"))
await writer.delete("foo")

# Test that you cannot write to the original store
assert store.read_only
with pytest.raises(
ValueError, match="store was opened in read-only mode and does not support writing"
):
await store.set("foo", self.buffer_cls.from_bytes(b"bar"))
with pytest.raises(
ValueError, match="store was opened in read-only mode and does not support writing"
):
await store.delete("foo")
# Test that you cannot write to a read-only store copy
reader = store.with_read_only(read_only=True)
assert reader.read_only
with pytest.raises(
ValueError, match="store was opened in read-only mode and does not support writing"
):
await reader.set("foo", self.buffer_cls.from_bytes(b"bar"))
with pytest.raises(
ValueError, match="store was opened in read-only mode and does not support writing"
):
await reader.delete("foo")


# TODO: fix this warning
@pytest.mark.filterwarnings("ignore:Unclosed client session:ResourceWarning")
Expand Down
Loading