From 97d313765389fde03ba40251b6808a2a88aabde6 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 20 Aug 2025 17:08:36 +0100 Subject: [PATCH 1/7] Fix typing in test_core --- pyproject.toml | 2 +- tests/test_store/test_core.py | 50 ++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5a1d60485d..d57ededa8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -352,6 +352,7 @@ module = [ "tests.test_store.test_fsspec", "tests.test_store.test_memory", "tests.test_codecs.test_codecs", + "tests.test_store.test_core", ] strict = false @@ -360,7 +361,6 @@ strict = false [[tool.mypy.overrides]] module = [ "tests.test_metadata.*", - "tests.test_store.test_core", "tests.test_store.test_logging", "tests.test_store.test_object", "tests.test_store.test_stateful", diff --git a/tests/test_store/test_core.py b/tests/test_store/test_core.py index a3850de90f..2dfdaa55de 100644 --- a/tests/test_store/test_core.py +++ b/tests/test_store/test_core.py @@ -1,5 +1,7 @@ import tempfile +from collections.abc import Callable, Generator from pathlib import Path +from typing import Any, Literal import pytest from _pytest.compat import LEGACY_PATH @@ -21,7 +23,9 @@ @pytest.fixture( params=["none", "temp_dir_str", "temp_dir_path", "store_path", "memory_store", "dict"] ) -def store_like(request): +def store_like( + request: pytest.FixtureRequest, +) -> Generator[None | str | Path | StorePath | MemoryStore | dict[Any, Any], None, None]: if request.param == "none": yield None elif request.param == "temp_dir_str": @@ -42,7 +46,7 @@ def store_like(request): @pytest.mark.parametrize("write_group", [True, False]) @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_contains_group( - local_store, path: str, write_group: bool, zarr_format: ZarrFormat + local_store: LocalStore, path: str, write_group: bool, zarr_format: ZarrFormat ) -> None: """ Test that the contains_group method correctly reports the existence of a group. @@ -58,7 +62,7 @@ async def test_contains_group( @pytest.mark.parametrize("write_array", [True, False]) @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_contains_array( - local_store, path: str, write_array: bool, zarr_format: ZarrFormat + local_store: LocalStore, path: str, write_array: bool, zarr_format: ZarrFormat ) -> None: """ Test that the contains array method correctly reports the existence of an array. @@ -71,13 +75,15 @@ async def test_contains_array( @pytest.mark.parametrize("func", [contains_array, contains_group]) -async def test_contains_invalid_format_raises(local_store, func: callable) -> None: +async def test_contains_invalid_format_raises( + local_store: LocalStore, func: Callable[[Any], Any] +) -> None: """ Test contains_group and contains_array raise errors for invalid zarr_formats """ store_path = StorePath(local_store) with pytest.raises(ValueError): - assert await func(store_path, zarr_format="3.0") + assert await func(store_path, zarr_format="3.0") # type: ignore[call-arg] @pytest.mark.parametrize("path", [None, "", "bar"]) @@ -113,29 +119,37 @@ async def test_make_store_path_local( @pytest.mark.parametrize("path", [None, "", "bar"]) @pytest.mark.parametrize("mode", ["r", "w"]) async def test_make_store_path_store_path( - tmpdir: LEGACY_PATH, path: str, mode: AccessModeLiteral + tmp_path: Path, path: str, mode: AccessModeLiteral ) -> None: """ Test invoking make_store_path when the input is another store_path. In particular we want to ensure that a new path is handled correctly. """ ro = mode == "r" - store_like = await StorePath.open(LocalStore(str(tmpdir), read_only=ro), path="root", mode=mode) + store_like = await StorePath.open( + LocalStore(str(tmp_path), read_only=ro), path="root", mode=mode + ) store_path = await make_store_path(store_like, path=path, mode=mode) assert isinstance(store_path.store, LocalStore) - assert Path(store_path.store.root) == Path(tmpdir) + assert Path(store_path.store.root) == tmp_path path_normalized = normalize_path(path) assert store_path.path == (store_like / path_normalized).path assert store_path.read_only == ro @pytest.mark.parametrize("modes", [(True, "w"), (False, "x")]) -async def test_store_path_invalid_mode_raises(tmpdir: LEGACY_PATH, modes: tuple) -> None: +async def test_store_path_invalid_mode_raises( + tmp_path: Path, modes: tuple[bool, Literal["w", "x"]] +) -> None: """ Test that ValueErrors are raise for invalid mode. """ with pytest.raises(ValueError): - await StorePath.open(LocalStore(str(tmpdir), read_only=modes[0]), path=None, mode=modes[1]) + await StorePath.open( + LocalStore(str(tmp_path), read_only=modes[0]), + path="", + mode=modes[1], # type:ignore[arg-type] + ) async def test_make_store_path_invalid() -> None: @@ -143,10 +157,10 @@ async def test_make_store_path_invalid() -> None: Test that invalid types raise TypeError """ with pytest.raises(TypeError): - await make_store_path(1) # type: ignore[arg-type] + await make_store_path(1) -async def test_make_store_path_fsspec(monkeypatch) -> None: +async def test_make_store_path_fsspec() -> None: pytest.importorskip("fsspec") pytest.importorskip("requests") pytest.importorskip("aiohttp") @@ -161,7 +175,7 @@ async def test_make_store_path_storage_options_raises(store_like: StoreLike) -> async def test_unsupported() -> None: with pytest.raises(TypeError, match="Unsupported type for store_like: 'int'"): - await make_store_path(1) # type: ignore[arg-type] + await make_store_path(1) @pytest.mark.parametrize( @@ -184,12 +198,12 @@ def test_normalize_path_upath() -> None: assert normalize_path(upath.UPath("foo/bar")) == "foo/bar" -def test_normalize_path_none(): +def test_normalize_path_none() -> None: assert normalize_path(None) == "" @pytest.mark.parametrize("path", [".", ".."]) -def test_normalize_path_invalid(path: str): +def test_normalize_path_invalid(path: str) -> None: with pytest.raises(ValueError): normalize_path(path) @@ -230,7 +244,7 @@ def test_invalid(paths: tuple[str, str]) -> None: _normalize_paths(paths) -def test_normalize_path_keys(): +def test_normalize_path_keys() -> None: """ Test that ``_normalize_path_keys`` just applies the normalize_path function to each key of its input @@ -272,10 +286,10 @@ def test_different_open_mode(tmp_path: LEGACY_PATH) -> None: # Test with a store that doesn't implement .with_read_only() zarr_path = tmp_path / "foo.zarr" - store = ZipStore(zarr_path, mode="w") + zip_store = ZipStore(zarr_path, mode="w") zarr.create((100,), store=store, zarr_format=2, path="a") with pytest.raises( ValueError, match="Store is not read-only but mode is 'r'. Unable to create a read-only copy of the store. Please use a read-only store or a storage class that implements .with_read_only().", ): - zarr.open_array(store=store, path="a", zarr_format=2, mode="r") + zarr.open_array(store=zip_store, path="a", zarr_format=2, mode="r") From 3e69353265a54068a79c4c104b1e5c8d4e6c003e Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 20 Aug 2025 17:19:19 +0100 Subject: [PATCH 2/7] Fix typing in test_logging --- pyproject.toml | 2 +- tests/test_store/test_logging.py | 49 ++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d57ededa8c..05a05ee576 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -353,6 +353,7 @@ module = [ "tests.test_store.test_memory", "tests.test_codecs.test_codecs", "tests.test_store.test_core", + "tests.test_store.test_logging", ] strict = false @@ -361,7 +362,6 @@ strict = false [[tool.mypy.overrides]] module = [ "tests.test_metadata.*", - "tests.test_store.test_logging", "tests.test_store.test_object", "tests.test_store.test_stateful", "tests.test_store.test_wrapper", diff --git a/tests/test_store/test_logging.py b/tests/test_store/test_logging.py index 1a89dca874..cc6f72331e 100644 --- a/tests/test_store/test_logging.py +++ b/tests/test_store/test_logging.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypedDict import pytest @@ -11,52 +11,59 @@ from zarr.testing.store import StoreTests if TYPE_CHECKING: - from _pytest.compat import LEGACY_PATH + from pathlib import Path from zarr.abc.store import Store -class TestLoggingStore(StoreTests[LoggingStore, cpu.Buffer]): - store_cls = LoggingStore +class StoreKwargs(TypedDict): + store: LocalStore + log_level: str + + +class TestLoggingStore(StoreTests[LoggingStore[LocalStore], cpu.Buffer]): + store_cls = LoggingStore[LocalStore] buffer_cls = cpu.Buffer - async def get(self, store: LoggingStore, key: str) -> Buffer: + async def get(self, store: LoggingStore[LocalStore], key: str) -> Buffer: return self.buffer_cls.from_bytes((store._store.root / key).read_bytes()) - async def set(self, store: LoggingStore, key: str, value: Buffer) -> None: + async def set(self, store: LoggingStore[LocalStore], key: str, value: Buffer) -> None: parent = (store._store.root / key).parent if not parent.exists(): parent.mkdir(parents=True) (store._store.root / key).write_bytes(value.to_bytes()) @pytest.fixture - def store_kwargs(self, tmpdir: LEGACY_PATH) -> dict[str, str]: - return {"store": LocalStore(str(tmpdir)), "log_level": "DEBUG"} + def store_kwargs(self, tmp_path: Path) -> StoreKwargs: + return {"store": LocalStore(str(tmp_path)), "log_level": "DEBUG"} @pytest.fixture - def open_kwargs(self, tmpdir) -> dict[str, str]: - return {"store_cls": LocalStore, "root": str(tmpdir), "log_level": "DEBUG"} + def open_kwargs(self, tmp_path: Path) -> dict[str, type[LocalStore] | str]: + return {"store_cls": LocalStore, "root": str(tmp_path), "log_level": "DEBUG"} @pytest.fixture - def store(self, store_kwargs: str | dict[str, Buffer] | None) -> LoggingStore: + def store(self, store_kwargs: StoreKwargs) -> LoggingStore[LocalStore]: return self.store_cls(**store_kwargs) - def test_store_supports_writes(self, store: LoggingStore) -> None: + def test_store_supports_writes(self, store: LoggingStore[LocalStore]) -> None: assert store.supports_writes - def test_store_supports_partial_writes(self, store: LoggingStore) -> None: + def test_store_supports_partial_writes(self, store: LoggingStore[LocalStore]) -> None: assert store.supports_partial_writes - def test_store_supports_listing(self, store: LoggingStore) -> None: + def test_store_supports_listing(self, store: LoggingStore[LocalStore]) -> None: assert store.supports_listing - def test_store_repr(self, store: LoggingStore) -> None: + def test_store_repr(self, store: LoggingStore[LocalStore]) -> None: assert f"{store!r}" == f"LoggingStore(LocalStore, 'file://{store._store.root.as_posix()}')" - def test_store_str(self, store: LoggingStore) -> None: + def test_store_str(self, store: LoggingStore[LocalStore]) -> None: assert str(store) == f"logging-file://{store._store.root.as_posix()}" - async def test_default_handler(self, local_store, capsys) -> None: + async def test_default_handler( + self, local_store: LocalStore, capsys: pytest.CaptureFixture[str] + ) -> None: # Store and then remove existing handlers to enter default handler code path handlers = logging.getLogger().handlers[:] for h in handlers: @@ -64,7 +71,7 @@ async def test_default_handler(self, local_store, capsys) -> None: # Test logs are sent to stdout wrapped = LoggingStore(store=local_store) buffer = default_buffer_prototype().buffer - res = await wrapped.set("foo/bar/c/0", buffer.from_bytes(b"\x01\x02\x03\x04")) + res = await wrapped.set("foo/bar/c/0", buffer.from_bytes(b"\x01\x02\x03\x04")) # type: ignore[func-returns-value] assert res is None captured = capsys.readouterr() assert len(captured) == 2 @@ -74,7 +81,7 @@ async def test_default_handler(self, local_store, capsys) -> None: for h in handlers: logging.getLogger().addHandler(h) - def test_is_open_setter_raises(self, store: LoggingStore) -> None: + def test_is_open_setter_raises(self, store: LoggingStore[LocalStore]) -> None: "Test that a user cannot change `_is_open` without opening the underlying store." with pytest.raises( NotImplementedError, match="LoggingStore must be opened via the `_open` method" @@ -83,12 +90,12 @@ def test_is_open_setter_raises(self, store: LoggingStore) -> None: @pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) -async def test_logging_store(store: Store, caplog) -> None: +async def test_logging_store(store: Store, caplog: pytest.LogCaptureFixture) -> None: wrapped = LoggingStore(store=store, log_level="DEBUG") buffer = default_buffer_prototype().buffer caplog.clear() - res = await wrapped.set("foo/bar/c/0", buffer.from_bytes(b"\x01\x02\x03\x04")) + res = await wrapped.set("foo/bar/c/0", buffer.from_bytes(b"\x01\x02\x03\x04")) # type: ignore[func-returns-value] assert res is None assert len(caplog.record_tuples) == 2 for tup in caplog.record_tuples: From 301c92f93a01671604616d245b038c562a63a742 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 20 Aug 2025 17:26:00 +0100 Subject: [PATCH 3/7] Fix typing in test_object --- pyproject.toml | 2 +- tests/test_store/test_object.py | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 05a05ee576..c7aa8a330a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -354,6 +354,7 @@ module = [ "tests.test_codecs.test_codecs", "tests.test_store.test_core", "tests.test_store.test_logging", + "tests.test_store.test_object", ] strict = false @@ -362,7 +363,6 @@ strict = false [[tool.mypy.overrides]] module = [ "tests.test_metadata.*", - "tests.test_store.test_object", "tests.test_store.test_stateful", "tests.test_store.test_wrapper", "tests.test_group", diff --git a/tests/test_store/test_object.py b/tests/test_store/test_object.py index d8b89e56b7..9f5f3aef93 100644 --- a/tests/test_store/test_object.py +++ b/tests/test_store/test_object.py @@ -1,5 +1,6 @@ # ruff: noqa: E402 -from typing import Any +from pathlib import Path +from typing import TypedDict import pytest @@ -16,17 +17,22 @@ from zarr.testing.store import StoreTests +class StoreKwargs(TypedDict): + store: LocalStore + read_only: bool + + class TestObjectStore(StoreTests[ObjectStore, cpu.Buffer]): store_cls = ObjectStore buffer_cls = cpu.Buffer @pytest.fixture - def store_kwargs(self, tmpdir) -> dict[str, Any]: - store = LocalStore(prefix=tmpdir) + def store_kwargs(self, tmp_path: Path) -> StoreKwargs: + store = LocalStore(prefix=tmp_path) return {"store": store, "read_only": False} @pytest.fixture - def store(self, store_kwargs: dict[str, str | bool]) -> ObjectStore: + def store(self, store_kwargs: StoreKwargs) -> ObjectStore: return self.store_cls(**store_kwargs) async def get(self, store: ObjectStore, key: str) -> Buffer: @@ -48,10 +54,8 @@ def test_store_repr(self, store: ObjectStore) -> None: def test_store_supports_writes(self, store: ObjectStore) -> None: assert store.supports_writes - async def test_store_supports_partial_writes(self, store: ObjectStore) -> None: + def test_store_supports_partial_writes(self, store: ObjectStore) -> None: assert not store.supports_partial_writes - with pytest.raises(NotImplementedError): - await store.set_partial_values([("foo", 0, b"\x01\x02\x03\x04")]) def test_store_supports_listing(self, store: ObjectStore) -> None: assert store.supports_listing @@ -64,6 +68,7 @@ def test_store_equal(self, store: ObjectStore) -> None: new_memory_store = ObjectStore(MemoryStore()) assert store != new_memory_store # Test equality against a read only store + assert isinstance(store.store, LocalStore) new_local_store = ObjectStore(LocalStore(prefix=store.store.prefix), read_only=True) assert store != new_local_store # Test two memory stores cannot be equal @@ -73,7 +78,7 @@ def test_store_equal(self, store: ObjectStore) -> None: def test_store_init_raises(self) -> None: """Test __init__ raises appropriate error for improper store type""" with pytest.raises(TypeError): - ObjectStore("path/to/store") + ObjectStore("path/to/store") # type: ignore[arg-type] async def test_store_getsize(self, store: ObjectStore) -> None: buf = cpu.Buffer.from_bytes(b"\x01\x02\x03\x04") @@ -92,10 +97,10 @@ async def test_store_getsize_prefix(self, store: ObjectStore) -> None: @pytest.mark.slow_hypothesis -def test_zarr_hierarchy(): +def test_zarr_hierarchy() -> None: sync_store = ObjectStore(MemoryStore()) def mk_test_instance_sync() -> ZarrHierarchyStateMachine: return ZarrHierarchyStateMachine(sync_store) - run_state_machine_as_test(mk_test_instance_sync) + run_state_machine_as_test(mk_test_instance_sync) # type: ignore[no-untyped-call] From a77a92668edd4fd09e9512ca094be1c92acb64ce Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 20 Aug 2025 17:28:10 +0100 Subject: [PATCH 4/7] Fix typing in test_stateful --- pyproject.toml | 2 +- tests/test_store/test_stateful.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c7aa8a330a..36c2c422b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -355,6 +355,7 @@ module = [ "tests.test_store.test_core", "tests.test_store.test_logging", "tests.test_store.test_object", + "tests.test_store.test_stateful", ] strict = false @@ -363,7 +364,6 @@ strict = false [[tool.mypy.overrides]] module = [ "tests.test_metadata.*", - "tests.test_store.test_stateful", "tests.test_store.test_wrapper", "tests.test_group", "tests.test_indexing", diff --git a/tests/test_store/test_stateful.py b/tests/test_store/test_stateful.py index c0997c3df3..6ea89d91d6 100644 --- a/tests/test_store/test_stateful.py +++ b/tests/test_store/test_stateful.py @@ -16,18 +16,18 @@ @pytest.mark.filterwarnings("ignore::zarr.core.dtype.common.UnstableSpecificationWarning") -def test_zarr_hierarchy(sync_store: Store): +def test_zarr_hierarchy(sync_store: Store) -> None: def mk_test_instance_sync() -> ZarrHierarchyStateMachine: return ZarrHierarchyStateMachine(sync_store) if isinstance(sync_store, ZipStore): pytest.skip(reason="ZipStore does not support delete") - run_state_machine_as_test(mk_test_instance_sync) + run_state_machine_as_test(mk_test_instance_sync) # type: ignore[no-untyped-call] def test_zarr_store(sync_store: Store) -> None: - def mk_test_instance_sync() -> None: + def mk_test_instance_sync() -> ZarrStoreStateMachine: return ZarrStoreStateMachine(sync_store) if isinstance(sync_store, ZipStore): @@ -38,4 +38,4 @@ def mk_test_instance_sync() -> None: # It assumes that `set` and `delete` are the only two operations that modify state. # But LocalStore, directories can hang around even after a key is delete-d. pytest.skip(reason="Test isn't suitable for LocalStore.") - run_state_machine_as_test(mk_test_instance_sync) + run_state_machine_as_test(mk_test_instance_sync) # type: ignore[no-untyped-call] From d95d4c954a1f0745050938c1b73d2f23bd93286d Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 20 Aug 2025 17:40:23 +0100 Subject: [PATCH 5/7] Fix typing in test_wrapper --- pyproject.toml | 2 +- src/zarr/storage/_wrapper.py | 3 +- tests/test_store/test_wrapper.py | 62 +++++++++++++++++++------------- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 36c2c422b6..a315e9774d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -356,6 +356,7 @@ module = [ "tests.test_store.test_logging", "tests.test_store.test_object", "tests.test_store.test_stateful", + "tests.test_store.test_wrapper", ] strict = false @@ -364,7 +365,6 @@ strict = false [[tool.mypy.overrides]] module = [ "tests.test_metadata.*", - "tests.test_store.test_wrapper", "tests.test_group", "tests.test_indexing", "tests.test_properties", diff --git a/src/zarr/storage/_wrapper.py b/src/zarr/storage/_wrapper.py index f21d378191..23963c8cd6 100644 --- a/src/zarr/storage/_wrapper.py +++ b/src/zarr/storage/_wrapper.py @@ -7,8 +7,9 @@ from types import TracebackType from typing import Any, Self + from zarr.abc.buffer import Buffer from zarr.abc.store import ByteRequest - from zarr.core.buffer import Buffer, BufferPrototype + from zarr.core.buffer import BufferPrototype from zarr.core.common import BytesLike from zarr.abc.store import Store diff --git a/tests/test_store/test_wrapper.py b/tests/test_store/test_wrapper.py index c6edd4f4dd..b0f5bcd9a7 100644 --- a/tests/test_store/test_wrapper.py +++ b/tests/test_store/test_wrapper.py @@ -1,72 +1,82 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, TypedDict import pytest -from zarr.core.buffer.cpu import Buffer, buffer_prototype +from zarr.abc.store import ByteRequest, Store +from zarr.core.buffer import Buffer +from zarr.core.buffer.cpu import buffer_prototype from zarr.storage import LocalStore, WrapperStore from zarr.testing.store import StoreTests if TYPE_CHECKING: - from _pytest.compat import LEGACY_PATH + from pathlib import Path - from zarr.abc.store import Store from zarr.core.buffer.core import BufferPrototype +class StoreKwargs(TypedDict): + store: LocalStore + + +class OpenKwargs(TypedDict): + store_cls: type[LocalStore] + root: str + + # TODO: fix this warning @pytest.mark.filterwarnings( "ignore:coroutine 'ClientCreatorContext.__aexit__' was never awaited:RuntimeWarning" ) -class TestWrapperStore(StoreTests[WrapperStore, Buffer]): - store_cls = WrapperStore +class TestWrapperStore(StoreTests[WrapperStore[LocalStore], Buffer]): + store_cls = WrapperStore[LocalStore] buffer_cls = Buffer - async def get(self, store: WrapperStore, key: str) -> Buffer: + async def get(self, store: WrapperStore[LocalStore], key: str) -> Buffer: return self.buffer_cls.from_bytes((store._store.root / key).read_bytes()) - async def set(self, store: WrapperStore, key: str, value: Buffer) -> None: + async def set(self, store: WrapperStore[LocalStore], key: str, value: Buffer) -> None: parent = (store._store.root / key).parent if not parent.exists(): parent.mkdir(parents=True) (store._store.root / key).write_bytes(value.to_bytes()) @pytest.fixture - def store_kwargs(self, tmpdir: LEGACY_PATH) -> dict[str, str]: - return {"store": LocalStore(str(tmpdir))} + def store_kwargs(self, tmp_path: Path) -> StoreKwargs: + return {"store": LocalStore(str(tmp_path))} @pytest.fixture - def open_kwargs(self, tmpdir) -> dict[str, str]: - return {"store_cls": LocalStore, "root": str(tmpdir)} + def open_kwargs(self, tmp_path: Path) -> OpenKwargs: + return {"store_cls": LocalStore, "root": str(tmp_path)} - def test_store_supports_writes(self, store: WrapperStore) -> None: + def test_store_supports_writes(self, store: WrapperStore[LocalStore]) -> None: assert store.supports_writes - def test_store_supports_partial_writes(self, store: WrapperStore) -> None: + def test_store_supports_partial_writes(self, store: WrapperStore[LocalStore]) -> None: assert store.supports_partial_writes - def test_store_supports_listing(self, store: WrapperStore) -> None: + def test_store_supports_listing(self, store: WrapperStore[LocalStore]) -> None: assert store.supports_listing - def test_store_repr(self, store: WrapperStore) -> None: + def test_store_repr(self, store: WrapperStore[LocalStore]) -> None: assert f"{store!r}" == f"WrapperStore(LocalStore, 'file://{store._store.root.as_posix()}')" - def test_store_str(self, store: WrapperStore) -> None: + def test_store_str(self, store: WrapperStore[LocalStore]) -> None: assert str(store) == f"wrapping-file://{store._store.root.as_posix()}" - def test_check_writeable(self, store: WrapperStore) -> None: + def test_check_writeable(self, store: WrapperStore[LocalStore]) -> None: """ Test _check_writeable() runs without errors. """ store._check_writable() - def test_close(self, store: WrapperStore) -> None: + def test_close(self, store: WrapperStore[LocalStore]) -> None: "Test store can be closed" store.close() assert not store._is_open - def test_is_open_setter_raises(self, store: WrapperStore) -> None: + def test_is_open_setter_raises(self, store: WrapperStore[LocalStore]) -> None: """ Test that a user cannot change `_is_open` without opening the underlying store. """ @@ -83,7 +93,7 @@ def test_is_open_setter_raises(self, store: WrapperStore) -> None: @pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=True) async def test_wrapped_set(store: Store, capsys: pytest.CaptureFixture[str]) -> None: # define a class that prints when it sets - class NoisySetter(WrapperStore): + class NoisySetter(WrapperStore[Store]): async def set(self, key: str, value: Buffer) -> None: print(f"setting {key}") await super().set(key, value) @@ -101,15 +111,17 @@ async def set(self, key: str, value: Buffer) -> None: @pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=True) async def test_wrapped_get(store: Store, capsys: pytest.CaptureFixture[str]) -> None: # define a class that prints when it sets - class NoisyGetter(WrapperStore): - def get(self, key: str, prototype: BufferPrototype) -> None: + class NoisyGetter(WrapperStore[Any]): + async def get( + self, key: str, prototype: BufferPrototype, byte_range: ByteRequest | None = None + ) -> None: print(f"getting {key}") - return super().get(key, prototype=prototype) + await super().get(key, prototype=prototype, byte_range=byte_range) key = "foo" value = Buffer.from_bytes(b"bar") store_wrapped = NoisyGetter(store) await store_wrapped.set(key, value) - assert await store_wrapped.get(key, buffer_prototype) == value + await store_wrapped.get(key, buffer_prototype) captured = capsys.readouterr() assert f"getting {key}" in captured.out From 79aaa1ec7b39406b9ed0a059a0c28efdbe4c9281 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 20 Aug 2025 17:54:36 +0100 Subject: [PATCH 6/7] Fix wrapper store tests --- tests/test_store/test_wrapper.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_store/test_wrapper.py b/tests/test_store/test_wrapper.py index b0f5bcd9a7..504f4efa33 100644 --- a/tests/test_store/test_wrapper.py +++ b/tests/test_store/test_wrapper.py @@ -6,6 +6,7 @@ from zarr.abc.store import ByteRequest, Store from zarr.core.buffer import Buffer +from zarr.core.buffer.cpu import Buffer as CPUBuffer from zarr.core.buffer.cpu import buffer_prototype from zarr.storage import LocalStore, WrapperStore from zarr.testing.store import StoreTests @@ -29,9 +30,9 @@ class OpenKwargs(TypedDict): @pytest.mark.filterwarnings( "ignore:coroutine 'ClientCreatorContext.__aexit__' was never awaited:RuntimeWarning" ) -class TestWrapperStore(StoreTests[WrapperStore[LocalStore], Buffer]): - store_cls = WrapperStore[LocalStore] - buffer_cls = Buffer +class TestWrapperStore(StoreTests[WrapperStore[Any], Buffer]): + store_cls = WrapperStore + buffer_cls = CPUBuffer async def get(self, store: WrapperStore[LocalStore], key: str) -> Buffer: return self.buffer_cls.from_bytes((store._store.root / key).read_bytes()) @@ -99,7 +100,7 @@ async def set(self, key: str, value: Buffer) -> None: await super().set(key, value) key = "foo" - value = Buffer.from_bytes(b"bar") + value = CPUBuffer.from_bytes(b"bar") store_wrapped = NoisySetter(store) await store_wrapped.set(key, value) captured = capsys.readouterr() @@ -119,7 +120,7 @@ async def get( await super().get(key, prototype=prototype, byte_range=byte_range) key = "foo" - value = Buffer.from_bytes(b"bar") + value = CPUBuffer.from_bytes(b"bar") store_wrapped = NoisyGetter(store) await store_wrapped.set(key, value) await store_wrapped.get(key, buffer_prototype) From 48916507340284de1f8ceae89ffe5f00b01af680 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sun, 24 Aug 2025 10:30:00 +0100 Subject: [PATCH 7/7] Fix store in test --- tests/test_store/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_store/test_core.py b/tests/test_store/test_core.py index 2dfdaa55de..6589c68e09 100644 --- a/tests/test_store/test_core.py +++ b/tests/test_store/test_core.py @@ -287,7 +287,7 @@ def test_different_open_mode(tmp_path: LEGACY_PATH) -> None: # Test with a store that doesn't implement .with_read_only() zarr_path = tmp_path / "foo.zarr" zip_store = ZipStore(zarr_path, mode="w") - zarr.create((100,), store=store, zarr_format=2, path="a") + zarr.create((100,), store=zip_store, zarr_format=2, path="a") with pytest.raises( ValueError, match="Store is not read-only but mode is 'r'. Unable to create a read-only copy of the store. Please use a read-only store or a storage class that implements .with_read_only().",