From e309415c60f7d3dc153b27fee0c887319eceb04b Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 13:14:21 +0100 Subject: [PATCH 01/38] add filenotfounderror to tupe of handled errors --- src/zarr/api/asynchronous.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index cd8c3543ca..eb2727d558 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -308,6 +308,8 @@ async def open( return await open_array(store=store_path, zarr_format=zarr_format, **kwargs) except (KeyError, NodeTypeValidationError): # KeyError for a missing key + # FileNotFoundError for missing key as well. + # TODO: consolidate these exceptions # NodeTypeValidationError for failing to parse node metadata as an array when it's # actually a group return await open_group(store=store_path, zarr_format=zarr_format, **kwargs) From 004535111d594c177f5136da1031d73a05e1ef6e Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 13:15:48 +0100 Subject: [PATCH 02/38] revive remote store test fixture, and parameterize api tests with all the stores --- tests/conftest.py | 92 ++++++++++++++++------ tests/test_api.py | 108 ++++++++++++++----------- tests/test_store/test_remote.py | 135 +++++++++++++++----------------- 3 files changed, 196 insertions(+), 139 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8c66406c9b..f2303bf127 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,16 @@ from __future__ import annotations +import os import pathlib from dataclasses import dataclass, field from typing import TYPE_CHECKING +import botocore +import fsspec import numpy as np import numpy.typing as npt import pytest +from botocore.session import Session from hypothesis import HealthCheck, Verbosity, settings from zarr import AsyncGroup, config @@ -23,18 +27,30 @@ from zarr.core.common import ChunkCoords, MemoryOrder, ZarrFormat +s3fs = pytest.importorskip("s3fs") +requests = pytest.importorskip("requests") +moto_server = pytest.importorskip("moto.moto_server.threaded_moto_server") +moto = pytest.importorskip("moto") + +# ### amended from s3fs ### # +test_bucket_name = "test" +secure_bucket_name = "test-secure" +port = 5555 +endpoint_url = f"http://127.0.0.1:{port}/" + async def parse_store( store: Literal["local", "memory", "remote", "zip"], path: str ) -> LocalStore | MemoryStore | RemoteStore | ZipStore: if store == "local": - return await LocalStore.open(path, mode="w") + return await LocalStore.open(path, mode="a") if store == "memory": - return await MemoryStore.open(mode="w") + return await MemoryStore.open(mode="a") if store == "remote": - return await RemoteStore.open(url=path, mode="w") + fs = fsspec.filesystem("s3", endpoint_url=endpoint_url, anon=False, asynchronous=True) + return await RemoteStore.open(fs, mode="a") if store == "zip": - return await ZipStore.open(path + "/zarr.zip", mode="w") + return await ZipStore.open(path + "/zarr.zip", mode="a") raise AssertionError @@ -50,26 +66,6 @@ async def store_path(tmpdir: LEGACY_PATH) -> StorePath: return StorePath(store) -@pytest.fixture -async def local_store(tmpdir: LEGACY_PATH) -> LocalStore: - return await LocalStore.open(str(tmpdir), mode="w") - - -@pytest.fixture -async def remote_store(url: str) -> RemoteStore: - return await RemoteStore.open(url, mode="w") - - -@pytest.fixture -async def memory_store() -> MemoryStore: - return await MemoryStore.open(mode="w") - - -@pytest.fixture -async def zip_store(tmpdir: LEGACY_PATH) -> ZipStore: - return await ZipStore.open(str(tmpdir / "zarr.zip"), mode="w") - - @pytest.fixture async def store(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH) -> Store: param = request.param @@ -148,6 +144,54 @@ def zarr_format(request: pytest.FixtureRequest) -> ZarrFormat: raise ValueError(msg) +@pytest.fixture(scope="module") +def s3_base() -> Generator[None, None, None]: + # writable local S3 system + + # This fixture is module-scoped, meaning that we can reuse the MotoServer across all tests + server = moto_server.ThreadedMotoServer(ip_address="127.0.0.1", port=port) + server.start() + if "AWS_SECRET_ACCESS_KEY" not in os.environ: + os.environ["AWS_SECRET_ACCESS_KEY"] = "foo" + if "AWS_ACCESS_KEY_ID" not in os.environ: + os.environ["AWS_ACCESS_KEY_ID"] = "foo" + + yield + server.stop() + + +def get_boto3_client() -> botocore.client.BaseClient: + # NB: we use the sync botocore client for setup + session = Session() + return session.create_client("s3", endpoint_url=endpoint_url, region_name="us-east-1") + + +@pytest.fixture(autouse=True) +def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: + """ + Quoting Martin Durant: + pytest-asyncio creates a new event loop for each async test. + When an async-mode s3fs instance is made from async, it will be assigned to the loop from + which it is made. That means that if you use s3fs again from a subsequent test, + you will have the same identical instance, but be running on a different loop - which fails. + + For the rest: it's very convenient to clean up the state of the store between tests, + make sure we start off blank each time. + + https://github.com/zarr-developers/zarr-python/pull/1785#discussion_r1634856207 + """ + client = get_boto3_client() + client.create_bucket(Bucket=test_bucket_name, ACL="public-read") + s3fs.S3FileSystem.clear_instance_cache() + s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": endpoint_url}) + session = sync(s3.set_session()) + s3.invalidate_cache() + yield s3 + requests.post(f"{endpoint_url}/moto-api/reset") + client.close() + sync(session.close()) + + settings.register_profile( "ci", max_examples=1000, diff --git a/tests/test_api.py b/tests/test_api.py index 5b62e3a2fa..77f907366b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,4 +1,3 @@ -import pathlib import warnings from typing import Literal @@ -27,9 +26,8 @@ from zarr.storage.memory import MemoryStore -def test_create_array(memory_store: Store) -> None: - store = memory_store - +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +def test_create_array(store: Store) -> None: # create array z = create(shape=100, store=store) assert isinstance(z, Array) @@ -48,23 +46,23 @@ def test_create_array(memory_store: Store) -> None: assert z.chunks == (40,) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("path", ["foo", "/", "/foo", "///foo/bar"]) @pytest.mark.parametrize("node_type", ["array", "group"]) def test_open_normalized_path( - memory_store: MemoryStore, path: str, node_type: Literal["array", "group"] + store: Store, path: str, node_type: Literal["array", "group"] ) -> None: node: Group | Array if node_type == "group": - node = group(store=memory_store, path=path) + node = group(store=store, path=path) elif node_type == "array": - node = create(store=memory_store, path=path, shape=(2,)) + node = create(store=store, path=path, shape=(2,)) assert node.path == normalize_path(path) -async def test_open_array(memory_store: MemoryStore) -> None: - store = memory_store - +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +async def test_open_array(store: Store) -> None: # open array, create if doesn't exist z = open(store=store, shape=100) assert isinstance(z, Array) @@ -90,9 +88,8 @@ async def test_open_array(memory_store: MemoryStore) -> None: open(store="doesnotexist", mode="r") -async def test_open_group(memory_store: MemoryStore) -> None: - store = memory_store - +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +async def test_open_group(store: Store) -> None: # open group, create if doesn't exist g = open_group(store=store) g.create_group("foo") @@ -105,26 +102,44 @@ async def test_open_group(memory_store: MemoryStore) -> None: # assert "foo" not in g # open group, read-only - store_cls = type(store) - ro_store = await store_cls.open(store_dict=store._store_dict, mode="r") + ro_store = store.with_mode("r") g = open_group(store=ro_store) assert isinstance(g, Group) # assert g.read_only +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) +async def test_open_array_or_group(zarr_format: ZarrFormat, store: Store) -> None: + # create a group and an array + grp_attrs = {"foo": "bar"} + grp_w = group(store=store, path="group", zarr_format=zarr_format, attributes=grp_attrs) + arr_w = grp_w.create_array(name="foo", shape=(1,)) + + grp_r = open(store=store, path="group", mode="r", zarr_format=zarr_format) + assert isinstance(grp_r, Group) + assert grp_r.attrs == grp_attrs + + arr_r = open(store=store, path="group/foo", mode="r", zarr_format=zarr_format) + assert isinstance(arr_r, Array) + assert arr_r.shape == arr_w.shape + + +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("zarr_format", [None, 2, 3]) -async def test_open_group_unspecified_version( - tmpdir: pathlib.Path, zarr_format: ZarrFormat -) -> None: +async def test_open_group_unspecified_version(store: Store, zarr_format: ZarrFormat) -> None: """Regression test for https://github.com/zarr-developers/zarr-python/issues/2175""" # create a group with specified zarr format (could be 2, 3, or None) _ = await zarr.api.asynchronous.open_group( - store=str(tmpdir), mode="w", zarr_format=zarr_format, attributes={"foo": "bar"} + store=store, mode="w", zarr_format=zarr_format, attributes={"foo": "bar"} ) # now open that group without specifying the format - g2 = await zarr.api.asynchronous.open_group(store=str(tmpdir), mode="r") + g2 = await zarr.api.asynchronous.open_group(store, mode="r") assert g2.attrs == {"foo": "bar"} @@ -144,13 +159,14 @@ def test_save_errors() -> None: save("data/group.zarr") -def test_open_with_mode_r(tmp_path: pathlib.Path) -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +def test_open_with_mode_r(store: Store) -> None: # 'r' means read only (must exist) with pytest.raises(FileNotFoundError): - zarr.open(store=tmp_path, mode="r") - z1 = zarr.ones(store=tmp_path, shape=(3, 3)) + zarr.open(store=store, mode="r") + z1 = zarr.ones(store=store, shape=(3, 3)) assert z1.fill_value == 1 - z2 = zarr.open(store=tmp_path, mode="r") + z2 = zarr.open(store=store, mode="r") assert isinstance(z2, Array) assert z2.fill_value == 1 assert (z2[:] == 1).all() @@ -158,52 +174,56 @@ def test_open_with_mode_r(tmp_path: pathlib.Path) -> None: z2[:] = 3 -def test_open_with_mode_r_plus(tmp_path: pathlib.Path) -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +def test_open_with_mode_r_plus(store: Store) -> None: # 'r+' means read/write (must exist) with pytest.raises(FileNotFoundError): - zarr.open(store=tmp_path, mode="r+") - zarr.ones(store=tmp_path, shape=(3, 3)) - z2 = zarr.open(store=tmp_path, mode="r+") + zarr.open(store=store, mode="r+") + zarr.ones(store=store, shape=(3, 3)) + z2 = zarr.open(store=store, mode="r+") assert isinstance(z2, Array) assert (z2[:] == 1).all() z2[:] = 3 -async def test_open_with_mode_a(tmp_path: pathlib.Path) -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +async def test_open_with_mode_a(store: Store) -> None: # Open without shape argument should default to group - g = zarr.open(store=tmp_path, mode="a") + g = zarr.open(store=store, mode="a") assert isinstance(g, Group) await g.store_path.delete() # 'a' means read/write (create if doesn't exist) - arr = zarr.open(store=tmp_path, mode="a", shape=(3, 3)) + arr = zarr.open(store=store, mode="a", shape=(3, 3)) assert isinstance(arr, Array) arr[...] = 1 - z2 = zarr.open(store=tmp_path, mode="a") + z2 = zarr.open(store=store, mode="a") assert isinstance(z2, Array) assert (z2[:] == 1).all() z2[:] = 3 -def test_open_with_mode_w(tmp_path: pathlib.Path) -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +def test_open_with_mode_w(store: Store) -> None: # 'w' means create (overwrite if exists); - arr = zarr.open(store=tmp_path, mode="w", shape=(3, 3)) + arr = zarr.open(store=store, mode="w", shape=(3, 3)) assert isinstance(arr, Array) arr[...] = 3 - z2 = zarr.open(store=tmp_path, mode="w", shape=(3, 3)) + z2 = zarr.open(store=store, mode="w", shape=(3, 3)) assert isinstance(z2, Array) assert not (z2[:] == 3).all() z2[:] = 3 -def test_open_with_mode_w_minus(tmp_path: pathlib.Path) -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +def test_open_with_mode_w_minus(store: Store) -> None: # 'w-' means create (fail if exists) - arr = zarr.open(store=tmp_path, mode="w-", shape=(3, 3)) + arr = zarr.open(store=store, mode="w-", shape=(3, 3)) assert isinstance(arr, Array) arr[...] = 1 with pytest.raises(FileExistsError): - zarr.open(store=tmp_path, mode="w-") + zarr.open(store=store, mode="w-") @pytest.mark.parametrize("order", ["C", "F", None]) @@ -238,8 +258,8 @@ def test_array_order(order: MemoryOrder | None, zarr_format: ZarrFormat) -> None # assert "LazyLoader: " in repr(loader) -def test_load_array(memory_store: Store) -> None: - store = memory_store +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +def test_load_array(store: Store) -> None: foo = np.arange(100) bar = np.arange(100, 0, -1) save(store, foo=foo, bar=bar) @@ -967,9 +987,9 @@ def test_open_group_positional_args_deprecated() -> None: open_group(store, "w") -def test_open_falls_back_to_open_group() -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +def test_open_falls_back_to_open_group(store: Store) -> None: # https://github.com/zarr-developers/zarr-python/issues/2309 - store = MemoryStore(mode="w") zarr.open_group(store, attributes={"key": "value"}) group = zarr.open(store) @@ -977,9 +997,9 @@ def test_open_falls_back_to_open_group() -> None: assert group.attrs == {"key": "value"} -async def test_open_falls_back_to_open_group_async() -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +async def test_open_falls_back_to_open_group_async(store: Store) -> None: # https://github.com/zarr-developers/zarr-python/issues/2309 - store = MemoryStore(mode="w") await zarr.api.asynchronous.open_group(store, attributes={"key": "value"}) group = await zarr.api.asynchronous.open(store=store) diff --git a/tests/test_store/test_remote.py b/tests/test_store/test_remote.py index 2ad1fd787b..037bd162e0 100644 --- a/tests/test_store/test_remote.py +++ b/tests/test_store/test_remote.py @@ -1,87 +1,80 @@ from __future__ import annotations import json -import os -from typing import TYPE_CHECKING import fsspec import pytest -from botocore.session import Session from upath import UPath import zarr.api.asynchronous from zarr.core.buffer import Buffer, cpu, default_buffer_prototype -from zarr.core.sync import _collect_aiterator, sync +from zarr.core.sync import _collect_aiterator from zarr.storage import RemoteStore from zarr.testing.store import StoreTests -if TYPE_CHECKING: - from collections.abc import Generator - - import botocore.client - - -s3fs = pytest.importorskip("s3fs") -requests = pytest.importorskip("requests") -moto_server = pytest.importorskip("moto.moto_server.threaded_moto_server") -moto = pytest.importorskip("moto") - -# ### amended from s3fs ### # -test_bucket_name = "test" -secure_bucket_name = "test-secure" -port = 5555 -endpoint_url = f"http://127.0.0.1:{port}/" - - -@pytest.fixture(scope="module") -def s3_base() -> Generator[None, None, None]: - # writable local S3 system - - # This fixture is module-scoped, meaning that we can reuse the MotoServer across all tests - server = moto_server.ThreadedMotoServer(ip_address="127.0.0.1", port=port) - server.start() - if "AWS_SECRET_ACCESS_KEY" not in os.environ: - os.environ["AWS_SECRET_ACCESS_KEY"] = "foo" - if "AWS_ACCESS_KEY_ID" not in os.environ: - os.environ["AWS_ACCESS_KEY_ID"] = "foo" - - yield - server.stop() - - -def get_boto3_client() -> botocore.client.BaseClient: - # NB: we use the sync botocore client for setup - session = Session() - return session.create_client("s3", endpoint_url=endpoint_url) - - -@pytest.fixture(autouse=True) -def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: - """ - Quoting Martin Durant: - pytest-asyncio creates a new event loop for each async test. - When an async-mode s3fs instance is made from async, it will be assigned to the loop from - which it is made. That means that if you use s3fs again from a subsequent test, - you will have the same identical instance, but be running on a different loop - which fails. - - For the rest: it's very convenient to clean up the state of the store between tests, - make sure we start off blank each time. - - https://github.com/zarr-developers/zarr-python/pull/1785#discussion_r1634856207 - """ - client = get_boto3_client() - client.create_bucket(Bucket=test_bucket_name, ACL="public-read") - s3fs.S3FileSystem.clear_instance_cache() - s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": endpoint_url}) - session = sync(s3.set_session()) - s3.invalidate_cache() - yield s3 - requests.post(f"{endpoint_url}/moto-api/reset") - client.close() - sync(session.close()) - - -# ### end from s3fs ### # +from ..conftest import endpoint_url, test_bucket_name + +# s3fs = pytest.importorskip("s3fs") +# requests = pytest.importorskip("requests") +# moto_server = pytest.importorskip("moto.moto_server.threaded_moto_server") +# moto = pytest.importorskip("moto") + +# # ### amended from s3fs ### # +# test_bucket_name = "test" +# secure_bucket_name = "test-secure" +# port = 5555 +# endpoint_url = f"http://127.0.0.1:{port}/" + + +# @pytest.fixture(scope="module") +# def s3_base() -> Generator[None, None, None]: +# # writable local S3 system + +# # This fixture is module-scoped, meaning that we can reuse the MotoServer across all tests +# server = moto_server.ThreadedMotoServer(ip_address="127.0.0.1", port=port) +# server.start() +# if "AWS_SECRET_ACCESS_KEY" not in os.environ: +# os.environ["AWS_SECRET_ACCESS_KEY"] = "foo" +# if "AWS_ACCESS_KEY_ID" not in os.environ: +# os.environ["AWS_ACCESS_KEY_ID"] = "foo" + +# yield +# server.stop() + + +# def get_boto3_client() -> botocore.client.BaseClient: +# # NB: we use the sync botocore client for setup +# session = Session() +# return session.create_client("s3", endpoint_url=endpoint_url) + + +# @pytest.fixture(autouse=True) +# def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: +# """ +# Quoting Martin Durant: +# pytest-asyncio creates a new event loop for each async test. +# When an async-mode s3fs instance is made from async, it will be assigned to the loop from +# which it is made. That means that if you use s3fs again from a subsequent test, +# you will have the same identical instance, but be running on a different loop - which fails. + +# For the rest: it's very convenient to clean up the state of the store between tests, +# make sure we start off blank each time. + +# https://github.com/zarr-developers/zarr-python/pull/1785#discussion_r1634856207 +# """ +# client = get_boto3_client() +# client.create_bucket(Bucket=test_bucket_name, ACL="public-read") +# s3fs.S3FileSystem.clear_instance_cache() +# s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": endpoint_url}) +# session = sync(s3.set_session()) +# s3.invalidate_cache() +# yield s3 +# requests.post(f"{endpoint_url}/moto-api/reset") +# client.close() +# sync(session.close()) + + +# # ### end from s3fs ### # async def test_basic() -> None: From d670b309ad52477f73a32d40ae380283cb460fa5 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 13:26:50 +0100 Subject: [PATCH 03/38] properly construct remotestore instance in test fixture --- tests/conftest.py | 12 ++++--- tests/test_store/test_remote.py | 62 --------------------------------- 2 files changed, 7 insertions(+), 67 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f2303bf127..ef5044ffe8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,8 +5,6 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING -import botocore -import fsspec import numpy as np import numpy.typing as npt import pytest @@ -23,6 +21,7 @@ from collections.abc import Generator from typing import Any, Literal + import botocore from _pytest.compat import LEGACY_PATH from zarr.core.common import ChunkCoords, MemoryOrder, ZarrFormat @@ -47,8 +46,11 @@ async def parse_store( if store == "memory": return await MemoryStore.open(mode="a") if store == "remote": - fs = fsspec.filesystem("s3", endpoint_url=endpoint_url, anon=False, asynchronous=True) - return await RemoteStore.open(fs, mode="a") + return RemoteStore.from_url( + f"s3://{test_bucket_name}/foo/spam/", + mode="a", + storage_options={"endpoint_url": endpoint_url, "anon": False}, + ) if store == "zip": return await ZipStore.open(path + "/zarr.zip", mode="a") raise AssertionError @@ -167,7 +169,7 @@ def get_boto3_client() -> botocore.client.BaseClient: @pytest.fixture(autouse=True) -def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: +def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: # type: ignore[name-defined] """ Quoting Martin Durant: pytest-asyncio creates a new event loop for each async test. diff --git a/tests/test_store/test_remote.py b/tests/test_store/test_remote.py index 037bd162e0..65c0697a19 100644 --- a/tests/test_store/test_remote.py +++ b/tests/test_store/test_remote.py @@ -14,68 +14,6 @@ from ..conftest import endpoint_url, test_bucket_name -# s3fs = pytest.importorskip("s3fs") -# requests = pytest.importorskip("requests") -# moto_server = pytest.importorskip("moto.moto_server.threaded_moto_server") -# moto = pytest.importorskip("moto") - -# # ### amended from s3fs ### # -# test_bucket_name = "test" -# secure_bucket_name = "test-secure" -# port = 5555 -# endpoint_url = f"http://127.0.0.1:{port}/" - - -# @pytest.fixture(scope="module") -# def s3_base() -> Generator[None, None, None]: -# # writable local S3 system - -# # This fixture is module-scoped, meaning that we can reuse the MotoServer across all tests -# server = moto_server.ThreadedMotoServer(ip_address="127.0.0.1", port=port) -# server.start() -# if "AWS_SECRET_ACCESS_KEY" not in os.environ: -# os.environ["AWS_SECRET_ACCESS_KEY"] = "foo" -# if "AWS_ACCESS_KEY_ID" not in os.environ: -# os.environ["AWS_ACCESS_KEY_ID"] = "foo" - -# yield -# server.stop() - - -# def get_boto3_client() -> botocore.client.BaseClient: -# # NB: we use the sync botocore client for setup -# session = Session() -# return session.create_client("s3", endpoint_url=endpoint_url) - - -# @pytest.fixture(autouse=True) -# def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: -# """ -# Quoting Martin Durant: -# pytest-asyncio creates a new event loop for each async test. -# When an async-mode s3fs instance is made from async, it will be assigned to the loop from -# which it is made. That means that if you use s3fs again from a subsequent test, -# you will have the same identical instance, but be running on a different loop - which fails. - -# For the rest: it's very convenient to clean up the state of the store between tests, -# make sure we start off blank each time. - -# https://github.com/zarr-developers/zarr-python/pull/1785#discussion_r1634856207 -# """ -# client = get_boto3_client() -# client.create_bucket(Bucket=test_bucket_name, ACL="public-read") -# s3fs.S3FileSystem.clear_instance_cache() -# s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": endpoint_url}) -# session = sync(s3.set_session()) -# s3.invalidate_cache() -# yield s3 -# requests.post(f"{endpoint_url}/moto-api/reset") -# client.close() -# sync(session.close()) - - -# # ### end from s3fs ### # - async def test_basic() -> None: store = RemoteStore.from_url( From f6d35fb7b5b0f06763b615922eb2e796bd213c2c Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 13:42:32 +0100 Subject: [PATCH 04/38] add filenotfounderror to tuple of handled errors --- src/zarr/api/asynchronous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index eb2727d558..4663d9fc73 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -306,7 +306,7 @@ async def open( try: return await open_array(store=store_path, zarr_format=zarr_format, **kwargs) - except (KeyError, NodeTypeValidationError): + except (KeyError, FileNotFoundError, NodeTypeValidationError): # KeyError for a missing key # FileNotFoundError for missing key as well. # TODO: consolidate these exceptions From 311efaf9433fa00118bb73f826999081c4012020 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 14:23:29 +0100 Subject: [PATCH 05/38] propagate mode kwarg into open_group --- src/zarr/api/asynchronous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 4663d9fc73..ca822eec50 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -312,7 +312,7 @@ async def open( # TODO: consolidate these exceptions # NodeTypeValidationError for failing to parse node metadata as an array when it's # actually a group - return await open_group(store=store_path, zarr_format=zarr_format, **kwargs) + return await open_group(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) async def open_consolidated( From 73911b710fdc9c45de72a593364b460da8a78c73 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 14:24:09 +0100 Subject: [PATCH 06/38] propagate mode kwarg into open_group --- src/zarr/api/asynchronous.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index ca822eec50..4b3237a4f8 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -702,7 +702,9 @@ async def open_group( return await AsyncGroup.open( store_path, zarr_format=zarr_format, use_consolidated=use_consolidated ) - except (KeyError, FileNotFoundError): + except (KeyError, FileNotFoundError) as e: + if mode in ("r", "r+"): + raise FileNotFoundError(f"Group '{path}' not found in store '{store_path}'") from e return await AsyncGroup.from_store( store_path, zarr_format=zarr_format or _default_zarr_version(), From 99173432f23b11b349ddd65d0a594312059b9313 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 14:25:27 +0100 Subject: [PATCH 07/38] xfail zipstore --- tests/test_api.py | 59 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 77f907366b..aeacdf42e7 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -62,33 +62,36 @@ def test_open_normalized_path( @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) -async def test_open_array(store: Store) -> None: +async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: # open array, create if doesn't exist - z = open(store=store, shape=100) + z = open(store=store, shape=100, zarr_format=zarr_format) assert isinstance(z, Array) assert z.shape == (100,) # open array, overwrite # store._store_dict = {} store = MemoryStore(mode="w") - z = open(store=store, shape=200) + z = open(store=store, shape=200, zarr_format=zarr_format) assert isinstance(z, Array) assert z.shape == (200,) # open array, read-only - store_cls = type(store) - ro_store = await store_cls.open(store_dict=store._store_dict, mode="r") - z = open(store=ro_store) + ro_store = store.with_mode("r") + z = open(store=ro_store, zarr_format=zarr_format) assert isinstance(z, Array) assert z.shape == (200,) assert z.read_only # path not found with pytest.raises(FileNotFoundError): - open(store="doesnotexist", mode="r") + open(store=store, path="doesnotexist", mode="r", zarr_format=zarr_format) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) async def test_open_group(store: Store) -> None: # open group, create if doesn't exist g = open_group(store=store) @@ -128,7 +131,11 @@ async def test_open_array_or_group(zarr_format: ZarrFormat, store: Store) -> Non assert arr_r.shape == arr_w.shape -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) @pytest.mark.parametrize("zarr_format", [None, 2, 3]) async def test_open_group_unspecified_version(store: Store, zarr_format: ZarrFormat) -> None: """Regression test for https://github.com/zarr-developers/zarr-python/issues/2175""" @@ -159,7 +166,11 @@ def test_save_errors() -> None: save("data/group.zarr") -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) def test_open_with_mode_r(store: Store) -> None: # 'r' means read only (must exist) with pytest.raises(FileNotFoundError): @@ -174,7 +185,11 @@ def test_open_with_mode_r(store: Store) -> None: z2[:] = 3 -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) def test_open_with_mode_r_plus(store: Store) -> None: # 'r+' means read/write (must exist) with pytest.raises(FileNotFoundError): @@ -186,7 +201,11 @@ def test_open_with_mode_r_plus(store: Store) -> None: z2[:] = 3 -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) async def test_open_with_mode_a(store: Store) -> None: # Open without shape argument should default to group g = zarr.open(store=store, mode="a") @@ -203,7 +222,11 @@ async def test_open_with_mode_a(store: Store) -> None: z2[:] = 3 -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) def test_open_with_mode_w(store: Store) -> None: # 'w' means create (overwrite if exists); arr = zarr.open(store=store, mode="w", shape=(3, 3)) @@ -216,7 +239,11 @@ def test_open_with_mode_w(store: Store) -> None: z2[:] = 3 -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) def test_open_with_mode_w_minus(store: Store) -> None: # 'w-' means create (fail if exists) arr = zarr.open(store=store, mode="w-", shape=(3, 3)) @@ -1012,10 +1039,10 @@ async def test_metadata_validation_error() -> None: MetadataValidationError, match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.", ): - await zarr.api.asynchronous.open_group(zarr_format="3.0") # type: ignore[arg-type] + await zarr.api.asynchronous.open_group(zarr_format="3.0") with pytest.raises( MetadataValidationError, match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.", ): - await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") # type: ignore[arg-type] + await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") From 90df740238164cc0ba860cfd6ede4d30ac45c895 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 14:26:08 +0100 Subject: [PATCH 08/38] revert removal of type ignores --- tests/test_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index aeacdf42e7..53e592b2a5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1039,10 +1039,10 @@ async def test_metadata_validation_error() -> None: MetadataValidationError, match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.", ): - await zarr.api.asynchronous.open_group(zarr_format="3.0") + await zarr.api.asynchronous.open_group(zarr_format="3.0") # type: ignore[arg-type] with pytest.raises( MetadataValidationError, match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.", ): - await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") + await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") # type: ignore[arg-type] From 3c3099adc1c197b9f86d3d9fa18f61b6125cb269 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 17:59:06 +0100 Subject: [PATCH 09/38] speculative refactor of open func --- src/zarr/api/asynchronous.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 4b3237a4f8..3a86fdd920 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -290,7 +290,12 @@ async def open( store_path = await make_store_path(store, mode=mode, path=path, storage_options=storage_options) - if "shape" not in kwargs and mode in {"a", "w", "w-"}: + # TODO: check for more array-only kwargs + expect_array = "shape" in kwargs + + if expect_array: + return await open_array(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) + else: try: metadata_dict = await get_array_metadata(store_path, zarr_format=zarr_format) # TODO: remove this cast when we fix typing for array metadata dicts @@ -300,20 +305,10 @@ async def open( is_v3_array = zarr_format == 3 and _metadata_dict.get("node_type") == "array" if is_v3_array or zarr_format == 2: return AsyncArray(store_path=store_path, metadata=_metadata_dict) - except (AssertionError, FileNotFoundError): + except (AssertionError, NodeTypeValidationError, FileNotFoundError): pass return await open_group(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) - try: - return await open_array(store=store_path, zarr_format=zarr_format, **kwargs) - except (KeyError, FileNotFoundError, NodeTypeValidationError): - # KeyError for a missing key - # FileNotFoundError for missing key as well. - # TODO: consolidate these exceptions - # NodeTypeValidationError for failing to parse node metadata as an array when it's - # actually a group - return await open_group(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) - async def open_consolidated( *args: Any, use_consolidated: Literal[True] = True, **kwargs: Any From 02194256387292afaed99cfae1dfedc3ddb0394d Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 30 Oct 2024 18:02:43 +0100 Subject: [PATCH 10/38] refactor store test fixture --- tests/conftest.py | 81 +++++++++++------- tests/test_api.py | 101 ++++++++++++++++------- tests/test_array.py | 34 ++++---- tests/test_group.py | 64 ++++++++++---- tests/test_metadata/test_consolidated.py | 54 +++++++----- tests/test_metadata/test_v2.py | 4 +- tests/test_store/test_remote.py | 24 +++--- 7 files changed, 230 insertions(+), 132 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ef5044ffe8..3dff32fb91 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,25 +34,40 @@ # ### amended from s3fs ### # test_bucket_name = "test" secure_bucket_name = "test-secure" -port = 5555 -endpoint_url = f"http://127.0.0.1:{port}/" async def parse_store( - store: Literal["local", "memory", "remote", "zip"], path: str + store: str, path: str, s3_base: str ) -> LocalStore | MemoryStore | RemoteStore | ZipStore: - if store == "local": - return await LocalStore.open(path, mode="a") - if store == "memory": - return await MemoryStore.open(mode="a") - if store == "remote": - return RemoteStore.from_url( - f"s3://{test_bucket_name}/foo/spam/", - mode="a", - storage_options={"endpoint_url": endpoint_url, "anon": False}, - ) - if store == "zip": - return await ZipStore.open(path + "/zarr.zip", mode="a") + """ + Take a string representation of a store + access mode, e.g. 'local_a', which would encode + LocalStore + access mode `a`, and convert that string representation into the appropriate store object, + which is then returned. + """ + store_parsed = store.split("_") + + if len(store_parsed) == 1: + store_type = store_parsed[0] + # the default mode for testing is a + mode = "a" + elif len(store_parsed) == 2: + store_type, mode = store_parsed + else: + raise ValueError(f"Invalid store specification: {store}") + + match store_type: + case "local": + return await LocalStore.open(path, mode=mode) + case "memory": + return await MemoryStore.open(mode=mode) + case "remote": + return RemoteStore.from_url( + f"s3://{test_bucket_name}/foo/spam/", + mode=mode, + storage_options={"endpoint_url": s3_base, "anon": False}, + ) + case "zip": + return await ZipStore.open(path + "/zarr.zip", mode=mode) raise AssertionError @@ -69,14 +84,14 @@ async def store_path(tmpdir: LEGACY_PATH) -> StorePath: @pytest.fixture -async def store(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH) -> Store: +async def store(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3_base: str) -> Store: param = request.param - return await parse_store(param, str(tmpdir)) + return await parse_store(param, str(tmpdir), s3_base) @pytest.fixture(params=["local", "memory", "zip"]) -def sync_store(request: pytest.FixtureRequest, tmp_path: LEGACY_PATH) -> Store: - result = sync(parse_store(request.param, str(tmp_path))) +def sync_store(request: pytest.FixtureRequest, tmp_path: LEGACY_PATH, s3_base: str) -> Store: + result = sync(parse_store(request.param, str(tmp_path), s3_base)) if not isinstance(result, Store): raise TypeError("Wrong store class returned by test fixture! got " + result + " instead") return result @@ -90,10 +105,12 @@ class AsyncGroupRequest: @pytest.fixture -async def async_group(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH) -> AsyncGroup: +async def async_group( + request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3_base: str +) -> AsyncGroup: param: AsyncGroupRequest = request.param - store = await parse_store(param.store, str(tmpdir)) + store = await parse_store(param.store, str(tmpdir), s3_base) return await AsyncGroup.from_store( store, attributes=param.attributes, @@ -147,29 +164,31 @@ def zarr_format(request: pytest.FixtureRequest) -> ZarrFormat: @pytest.fixture(scope="module") -def s3_base() -> Generator[None, None, None]: +def s3_base() -> Generator[str, None, None]: # writable local S3 system + from moto.server import ThreadedMotoServer - # This fixture is module-scoped, meaning that we can reuse the MotoServer across all tests - server = moto_server.ThreadedMotoServer(ip_address="127.0.0.1", port=port) - server.start() if "AWS_SECRET_ACCESS_KEY" not in os.environ: os.environ["AWS_SECRET_ACCESS_KEY"] = "foo" if "AWS_ACCESS_KEY_ID" not in os.environ: os.environ["AWS_ACCESS_KEY_ID"] = "foo" + server = ThreadedMotoServer(ip_address="127.0.0.1", port=0) + server.start() + host, port = server._server.server_address + endpoint_url = f"http://{host}:{port}" - yield + yield endpoint_url server.stop() -def get_boto3_client() -> botocore.client.BaseClient: +def get_boto3_client(endpoint_url: str) -> botocore.client.BaseClient: # NB: we use the sync botocore client for setup session = Session() return session.create_client("s3", endpoint_url=endpoint_url, region_name="us-east-1") @pytest.fixture(autouse=True) -def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: # type: ignore[name-defined] +def s3(s3_base: str) -> Generator[s3fs.S3FileSystem, None, None]: # type: ignore[name-defined] """ Quoting Martin Durant: pytest-asyncio creates a new event loop for each async test. @@ -182,14 +201,14 @@ def s3(s3_base: None) -> Generator[s3fs.S3FileSystem, None, None]: # type: igno https://github.com/zarr-developers/zarr-python/pull/1785#discussion_r1634856207 """ - client = get_boto3_client() + client = get_boto3_client(s3_base) client.create_bucket(Bucket=test_bucket_name, ACL="public-read") s3fs.S3FileSystem.clear_instance_cache() - s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": endpoint_url}) + s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": s3_base}) session = sync(s3.set_session()) s3.invalidate_cache() yield s3 - requests.post(f"{endpoint_url}/moto-api/reset") + requests.post(f"{s3_base}/moto-api/reset") client.close() sync(session.close()) diff --git a/tests/test_api.py b/tests/test_api.py index 53e592b2a5..c7c3346ffc 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -21,12 +21,14 @@ save_group, ) from zarr.core.common import MemoryOrder, ZarrFormat +from zarr.core.sync import sync from zarr.errors import MetadataValidationError from zarr.storage._utils import normalize_path from zarr.storage.memory import MemoryStore +from zarr.storage.zip import ZipStore -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) def test_create_array(store: Store) -> None: # create array z = create(shape=100, store=store) @@ -46,7 +48,7 @@ def test_create_array(store: Store) -> None: assert z.chunks == (40,) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) @pytest.mark.parametrize("path", ["foo", "/", "/foo", "///foo/bar"]) @pytest.mark.parametrize("node_type", ["array", "group"]) def test_open_normalized_path( @@ -61,35 +63,51 @@ def test_open_normalized_path( assert node.path == normalize_path(path) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: # open array, create if doesn't exist z = open(store=store, shape=100, zarr_format=zarr_format) assert isinstance(z, Array) assert z.shape == (100,) - # open array, overwrite - # store._store_dict = {} - store = MemoryStore(mode="w") - z = open(store=store, shape=200, zarr_format=zarr_format) + if isinstance(store, ZipStore): + store.close() + store_w = await ZipStore.open(store.path, mode="w") + else: + # open array, overwrite + store_w = store.with_mode("w") + + z = open(store=store_w, shape=200, zarr_format=zarr_format) assert isinstance(z, Array) assert z.shape == (200,) - # open array, read-only - ro_store = store.with_mode("r") - z = open(store=ro_store, zarr_format=zarr_format) + if isinstance(store, ZipStore): + store_w.close() + store_r = await ZipStore.open(store.path, mode="r") + else: + # open array, read-only + store_r = store.with_mode("r") + + z = open(store=store_r, zarr_format=zarr_format) assert isinstance(z, Array) assert z.shape == (200,) assert z.read_only - # path not found + +# zipstore is marked as xfail because you cannot open a zipstore in read-only mode if it doesn't exist in the first place. +@pytest.mark.parametrize( + "store", + ["local_r", "memory_r", "remote_r", pytest.param("zip_r", marks=pytest.mark.xfail)], + indirect=True, +) +def test_open_path_not_found(store: Store, zarr_format: ZarrFormat) -> None: with pytest.raises(FileNotFoundError): open(store=store, path="doesnotexist", mode="r", zarr_format=zarr_format) @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True, ) async def test_open_group(store: Store) -> None: @@ -105,15 +123,23 @@ async def test_open_group(store: Store) -> None: # assert "foo" not in g # open group, read-only - ro_store = store.with_mode("r") - g = open_group(store=ro_store) + if isinstance(store, ZipStore): + store.close() + store_r = await ZipStore.open(store.path, mode="r") + else: + # open array, read-only + store_r = store.with_mode("r") + + g = open_group(store=store_r) assert isinstance(g, Group) - # assert g.read_only + + if isinstance(store, ZipStore): + store.close() @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True, ) async def test_open_array_or_group(zarr_format: ZarrFormat, store: Store) -> None: @@ -122,18 +148,24 @@ async def test_open_array_or_group(zarr_format: ZarrFormat, store: Store) -> Non grp_w = group(store=store, path="group", zarr_format=zarr_format, attributes=grp_attrs) arr_w = grp_w.create_array(name="foo", shape=(1,)) - grp_r = open(store=store, path="group", mode="r", zarr_format=zarr_format) + if isinstance(store, ZipStore): + store.close() + store_2 = await ZipStore.open(store.path, mode="r") + else: + store_2 = store + + grp_r = open(store=store_2, path="group", mode="r", zarr_format=zarr_format) assert isinstance(grp_r, Group) assert grp_r.attrs == grp_attrs - arr_r = open(store=store, path="group/foo", mode="r", zarr_format=zarr_format) + arr_r = open(store=store_2, path="group/foo", mode="r", zarr_format=zarr_format) assert isinstance(arr_r, Array) assert arr_r.shape == arr_w.shape @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True, ) @pytest.mark.parametrize("zarr_format", [None, 2, 3]) @@ -142,11 +174,17 @@ async def test_open_group_unspecified_version(store: Store, zarr_format: ZarrFor # create a group with specified zarr format (could be 2, 3, or None) _ = await zarr.api.asynchronous.open_group( - store=store, mode="w", zarr_format=zarr_format, attributes={"foo": "bar"} + store=store, mode="a", zarr_format=zarr_format, attributes={"foo": "bar"} ) + if isinstance(store, ZipStore): + store.close() + store_2 = await ZipStore.open(store.path, mode="r") + else: + store_2 = store + # now open that group without specifying the format - g2 = await zarr.api.asynchronous.open_group(store, mode="r") + g2 = await zarr.api.asynchronous.open_group(store=store_2, mode="r") assert g2.attrs == {"foo": "bar"} @@ -168,15 +206,17 @@ def test_save_errors() -> None: @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_r(store: Store) -> None: # 'r' means read only (must exist) with pytest.raises(FileNotFoundError): zarr.open(store=store, mode="r") + z1 = zarr.ones(store=store, shape=(3, 3)) assert z1.fill_value == 1 + z2 = zarr.open(store=store, mode="r") assert isinstance(z2, Array) assert z2.fill_value == 1 @@ -187,7 +227,7 @@ def test_open_with_mode_r(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_r_plus(store: Store) -> None: @@ -203,14 +243,15 @@ def test_open_with_mode_r_plus(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], indirect=True, ) async def test_open_with_mode_a(store: Store) -> None: # Open without shape argument should default to group g = zarr.open(store=store, mode="a") assert isinstance(g, Group) - await g.store_path.delete() + # this causes an exception for remotestore if it is awaited. no clue why. + sync(store.delete("zarr.json")) # 'a' means read/write (create if doesn't exist) arr = zarr.open(store=store, mode="a", shape=(3, 3)) @@ -224,7 +265,7 @@ async def test_open_with_mode_a(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_w(store: Store) -> None: @@ -241,7 +282,7 @@ def test_open_with_mode_w(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_w_minus(store: Store) -> None: @@ -285,7 +326,7 @@ def test_array_order(order: MemoryOrder | None, zarr_format: ZarrFormat) -> None # assert "LazyLoader: " in repr(loader) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) def test_load_array(store: Store) -> None: foo = np.arange(100) bar = np.arange(100, 0, -1) @@ -1014,7 +1055,7 @@ def test_open_group_positional_args_deprecated() -> None: open_group(store, "w") -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) def test_open_falls_back_to_open_group(store: Store) -> None: # https://github.com/zarr-developers/zarr-python/issues/2309 zarr.open_group(store, attributes={"key": "value"}) @@ -1024,7 +1065,7 @@ def test_open_falls_back_to_open_group(store: Store) -> None: assert group.attrs == {"key": "value"} -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) async def test_open_falls_back_to_open_group_async(store: Store) -> None: # https://github.com/zarr-developers/zarr-python/issues/2309 await zarr.api.asynchronous.open_group(store, attributes={"key": "value"}) diff --git a/tests/test_array.py b/tests/test_array.py index 6451c7fe5e..6f98971d05 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -22,7 +22,7 @@ from zarr.storage.common import StorePath -@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a", "remote_a"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) @pytest.mark.parametrize("exists_ok", [True, False]) @pytest.mark.parametrize("extant_node", ["array", "group"]) @@ -71,7 +71,7 @@ def test_array_creation_existing_node( ) -@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a", "remote_a"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_create_creates_parents( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -111,7 +111,7 @@ async def test_create_creates_parents( assert isinstance(g, AsyncGroup) -@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a", "remote_a"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_array_name_properties_no_group( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -122,7 +122,7 @@ def test_array_name_properties_no_group( assert arr.basename is None -@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_array_name_properties_with_group( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -141,7 +141,7 @@ def test_array_name_properties_with_group( assert spam.basename == "spam" -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize("specifiy_fill_value", [True, False]) @pytest.mark.parametrize("dtype_str", ["bool", "uint8", "complex64"]) def test_array_v3_fill_value_default( @@ -171,7 +171,7 @@ def test_array_v3_fill_value_default( assert arr.fill_value.dtype == arr.dtype -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize( ("dtype_str", "fill_value"), [("bool", True), ("uint8", 99), ("float32", -99.9), ("complex64", 3 + 4j)], @@ -232,7 +232,7 @@ def test_selection_positional_args_deprecated() -> None: arr.set_block_selection((0, slice(None)), 1, None) -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) async def test_array_v3_nan_fill_value(store: MemoryStore) -> None: shape = (10,) arr = Array.create( @@ -251,7 +251,7 @@ async def test_array_v3_nan_fill_value(store: MemoryStore) -> None: assert len([a async for a in store.list_prefix("/")]) == 0 -@pytest.mark.parametrize("store", ["local"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local_a"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_serializable_async_array( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -269,7 +269,7 @@ async def test_serializable_async_array( # TODO: uncomment the parts of this test that will be impacted by the config/prototype changes in flight -@pytest.mark.parametrize("store", ["local"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local_a"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_serializable_sync_array(store: LocalStore, zarr_format: ZarrFormat) -> None: expected = Array.create( @@ -284,7 +284,7 @@ def test_serializable_sync_array(store: LocalStore, zarr_format: ZarrFormat) -> np.testing.assert_array_equal(actual[:], expected[:]) -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) def test_storage_transformers(store: MemoryStore) -> None: """ Test that providing an actual storage transformer produces a warning and otherwise passes through @@ -422,7 +422,7 @@ def test_update_attrs(zarr_format: int) -> None: assert arr2.attrs["foo"] == "bar" -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_resize_1d(store: MemoryStore, zarr_format: int) -> None: z = zarr.create( @@ -461,7 +461,7 @@ def test_resize_1d(store: MemoryStore, zarr_format: int) -> None: assert new_shape == z[:].shape -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_resize_2d(store: MemoryStore, zarr_format: int) -> None: z = zarr.create( @@ -523,7 +523,7 @@ def test_resize_2d(store: MemoryStore, zarr_format: int) -> None: assert new_shape == z[:].shape -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_1d(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105) @@ -553,7 +553,7 @@ def test_append_1d(store: MemoryStore, zarr_format: int) -> None: np.testing.assert_array_equal(f, z[:]) -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_2d(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105 * 105, dtype="i4").reshape((105, 105)) @@ -577,7 +577,7 @@ def test_append_2d(store: MemoryStore, zarr_format: int) -> None: np.testing.assert_array_equal(e, actual) -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_2d_axis(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105 * 105, dtype="i4").reshape((105, 105)) @@ -599,7 +599,7 @@ def test_append_2d_axis(store: MemoryStore, zarr_format: int) -> None: np.testing.assert_array_equal(e, z[:]) -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_bad_shape(store: MemoryStore, zarr_format: int) -> None: a = np.arange(100) @@ -612,7 +612,7 @@ def test_append_bad_shape(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("order", ["C", "F", None]) @pytest.mark.parametrize("zarr_format", [2, 3]) -@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("store", ["memory_a"], indirect=True) def test_array_create_order( order: MemoryOrder | None, zarr_format: int, store: MemoryStore ) -> None: diff --git a/tests/test_group.py b/tests/test_group.py index bcdc6ff0da..674173cd41 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -13,7 +13,6 @@ import zarr.api.asynchronous import zarr.api.synchronous from zarr import Array, AsyncArray, AsyncGroup, Group -from zarr.abc.store import Store from zarr.core.buffer import default_buffer_prototype from zarr.core.group import ConsolidatedMetadata, GroupMetadata from zarr.core.sync import sync @@ -21,22 +20,11 @@ from zarr.storage import LocalStore, MemoryStore, StorePath, ZipStore from zarr.storage.common import make_store_path -from .conftest import parse_store - if TYPE_CHECKING: - from _pytest.compat import LEGACY_PATH - + from zarr.abc.store import Store from zarr.core.common import JSON, ZarrFormat -@pytest.fixture(params=["local", "memory", "zip"]) -async def store(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH) -> Store: - result = await parse_store(request.param, str(tmpdir)) - if not isinstance(result, Store): - raise TypeError("Wrong store class returned by test fixture! got " + result + " instead") - return result - - @pytest.fixture(params=[True, False]) def exists_ok(request: pytest.FixtureRequest) -> bool: result = request.param @@ -45,6 +33,7 @@ def exists_ok(request: pytest.FixtureRequest) -> bool: return result +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_init(store: Store, zarr_format: ZarrFormat) -> None: """ Test that initializing a group from an asyncgroup works. @@ -54,6 +43,7 @@ def test_group_init(store: Store, zarr_format: ZarrFormat) -> None: assert group._async_group == agroup +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_create_creates_parents(store: Store, zarr_format: ZarrFormat) -> None: # prepare a root node, with some data set await zarr.api.asynchronous.open_group( @@ -106,6 +96,7 @@ async def test_create_creates_parents(store: Store, zarr_format: ZarrFormat) -> assert g.attrs == {} +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_name_properties(store: Store, zarr_format: ZarrFormat) -> None: """ Test basic properties of groups @@ -126,6 +117,7 @@ def test_group_name_properties(store: Store, zarr_format: ZarrFormat) -> None: assert bar.basename == "bar" +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("consolidated_metadata", [True, False]) def test_group_members(store: Store, zarr_format: ZarrFormat, consolidated_metadata: bool) -> None: """ @@ -208,6 +200,7 @@ def test_group_members(store: Store, zarr_format: ZarrFormat, consolidated_metad members_observed = group.members(max_depth=-1) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group(store: Store, zarr_format: ZarrFormat) -> None: """ Test basic Group routines. @@ -250,6 +243,7 @@ def test_group(store: Store, zarr_format: ZarrFormat) -> None: assert dict(bar3.attrs) == {"baz": "qux", "name": "bar"} +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_create(store: Store, exists_ok: bool, zarr_format: ZarrFormat) -> None: """ Test that `Group.from_store` works as expected. @@ -266,6 +260,7 @@ def test_group_create(store: Store, exists_ok: bool, zarr_format: ZarrFormat) -> _ = Group.from_store(store, exists_ok=exists_ok, zarr_format=zarr_format) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_open(store: Store, zarr_format: ZarrFormat, exists_ok: bool) -> None: """ Test the `Group.open` method. @@ -299,6 +294,7 @@ def test_group_open(store: Store, zarr_format: ZarrFormat, exists_ok: bool) -> N @pytest.mark.parametrize("consolidated", [True, False]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_getitem(store: Store, zarr_format: ZarrFormat, consolidated: bool) -> None: """ Test the `Group.__getitem__` method. @@ -355,6 +351,7 @@ def test_group_getitem(store: Store, zarr_format: ZarrFormat, consolidated: bool group["subarray/subsubarray"] +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_get_with_default(store: Store, zarr_format: ZarrFormat) -> None: group = Group.from_store(store, zarr_format=zarr_format) @@ -375,6 +372,7 @@ def test_group_get_with_default(store: Store, zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("consolidated", [True, False]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_delitem(store: Store, zarr_format: ZarrFormat, consolidated: bool) -> None: """ Test the `Group.__delitem__` method. @@ -404,6 +402,7 @@ def test_group_delitem(store: Store, zarr_format: ZarrFormat, consolidated: bool group["subarray"] +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_iter(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__iter__` method. @@ -413,6 +412,7 @@ def test_group_iter(store: Store, zarr_format: ZarrFormat) -> None: assert list(group) == [] +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_len(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__len__` method. @@ -422,6 +422,7 @@ def test_group_len(store: Store, zarr_format: ZarrFormat) -> None: assert len(group) == 0 +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_setitem(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__setitem__` method. @@ -448,6 +449,7 @@ def test_group_setitem(store: Store, zarr_format: ZarrFormat) -> None: np.testing.assert_array_equal(group[key], arr) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__contains__` method @@ -458,6 +460,7 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None: assert "foo" in group +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("consolidate", [True, False]) def test_group_child_iterators(store: Store, zarr_format: ZarrFormat, consolidate: bool): group = Group.from_store(store, zarr_format=zarr_format) @@ -563,6 +566,7 @@ def test_group_child_iterators(store: Store, zarr_format: ZarrFormat, consolidat assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: """ Test the behavior of `Group.update_attributes` @@ -575,6 +579,7 @@ def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: assert new_group.attrs == new_attrs +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_group_update_attributes_async(store: Store, zarr_format: ZarrFormat) -> None: """ Test the behavior of `Group.update_attributes_async` @@ -588,6 +593,7 @@ async def test_group_update_attributes_async(store: Store, zarr_format: ZarrForm @pytest.mark.parametrize("method", ["create_array", "array"]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_create_array( store: Store, zarr_format: ZarrFormat, @@ -622,6 +628,7 @@ def test_group_create_array( assert np.array_equal(array[:], data) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_array_creation( store: Store, zarr_format: ZarrFormat, @@ -690,7 +697,7 @@ def test_group_array_creation( assert full_like_array.store_path.store == store -@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) @pytest.mark.parametrize("exists_ok", [True, False]) @pytest.mark.parametrize("extant_node", ["array", "group"]) @@ -737,6 +744,7 @@ def test_group_creation_existing_node( ) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_create( store: Store, exists_ok: bool, @@ -779,6 +787,7 @@ async def test_asyncgroup_create( ) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_attrs(store: Store, zarr_format: ZarrFormat) -> None: attributes = {"foo": 100} agroup = await AsyncGroup.from_store(store, zarr_format=zarr_format, attributes=attributes) @@ -786,6 +795,7 @@ async def test_asyncgroup_attrs(store: Store, zarr_format: ZarrFormat) -> None: assert agroup.attrs == agroup.metadata.attributes == attributes +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_info(store: Store, zarr_format: ZarrFormat) -> None: agroup = await AsyncGroup.from_store( # noqa: F841 store, @@ -795,6 +805,7 @@ async def test_asyncgroup_info(store: Store, zarr_format: ZarrFormat) -> None: # assert agroup.info == agroup.metadata.info +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_open( store: Store, zarr_format: ZarrFormat, @@ -816,6 +827,7 @@ async def test_asyncgroup_open( assert group_w == group_r +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_open_wrong_format( store: Store, zarr_format: ZarrFormat, @@ -836,6 +848,7 @@ async def test_asyncgroup_open_wrong_format( # todo: replace the dict[str, Any] type with something a bit more specific # should this be async? +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize( "data", [ @@ -858,6 +871,7 @@ def test_asyncgroup_from_dict(store: Store, data: dict[str, Any]) -> None: # todo: replace this with a declarative API where we model a full hierarchy +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_getitem(store: Store, zarr_format: ZarrFormat) -> None: """ Create an `AsyncGroup`, then create members of that group, and ensure that we can access those @@ -880,6 +894,7 @@ async def test_asyncgroup_getitem(store: Store, zarr_format: ZarrFormat) -> None await agroup.getitem("foo") +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_delitem(store: Store, zarr_format: ZarrFormat) -> None: if not store.supports_deletes: pytest.skip("store does not support deletes") @@ -916,6 +931,7 @@ async def test_asyncgroup_delitem(store: Store, zarr_format: ZarrFormat) -> None raise AssertionError +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_create_group( store: Store, zarr_format: ZarrFormat, @@ -932,6 +948,7 @@ async def test_asyncgroup_create_group( assert subnode.metadata.zarr_format == zarr_format +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_create_array( store: Store, zarr_format: ZarrFormat, exists_ok: bool ) -> None: @@ -971,6 +988,7 @@ async def test_asyncgroup_create_array( assert subnode.metadata.zarr_format == zarr_format +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_asyncgroup_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: """ Test that the AsyncGroup.update_attributes method works correctly. @@ -985,7 +1003,7 @@ async def test_asyncgroup_update_attributes(store: Store, zarr_format: ZarrForma assert agroup_new_attributes.attrs == attributes_new -@pytest.mark.parametrize("store", ["local"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_serializable_async_group(store: LocalStore, zarr_format: ZarrFormat) -> None: expected = await AsyncGroup.from_store( @@ -996,7 +1014,7 @@ async def test_serializable_async_group(store: LocalStore, zarr_format: ZarrForm assert actual == expected -@pytest.mark.parametrize("store", ["local"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_serializable_sync_group(store: LocalStore, zarr_format: ZarrFormat) -> None: expected = Group.from_store(store=store, attributes={"foo": 999}, zarr_format=zarr_format) @@ -1006,6 +1024,7 @@ def test_serializable_sync_group(store: LocalStore, zarr_format: ZarrFormat) -> @pytest.mark.parametrize("consolidated_metadata", [True, False]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_group_members_async(store: Store, consolidated_metadata: bool) -> None: group = await AsyncGroup.from_store( store=store, @@ -1081,7 +1100,8 @@ async def test_group_members_async(store: Store, consolidated_metadata: bool) -> assert nmembers == 4 -async def test_require_group(store: LocalStore | MemoryStore, zarr_format: ZarrFormat) -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +async def test_require_group(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) # create foo group @@ -1109,7 +1129,8 @@ async def test_require_group(store: LocalStore | MemoryStore, zarr_format: ZarrF await foo_group.require_group("bar") -async def test_require_groups(store: LocalStore | MemoryStore, zarr_format: ZarrFormat) -> None: +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +async def test_require_groups(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) # create foo group _ = await root.create_group("foo", attributes={"foo": 100}) @@ -1130,6 +1151,7 @@ async def test_require_groups(store: LocalStore | MemoryStore, zarr_format: Zarr assert no_group == () +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_create_dataset(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) with pytest.warns(DeprecationWarning): @@ -1144,6 +1166,7 @@ async def test_create_dataset(store: Store, zarr_format: ZarrFormat) -> None: await root.create_dataset("bar", shape=(100,), dtype="int8") +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_require_array(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) foo1 = await root.require_array("foo", shape=(10,), dtype="i8", attributes={"foo": 101}) @@ -1170,6 +1193,7 @@ async def test_require_array(store: Store, zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("consolidate", [True, False]) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_members_name(store: Store, consolidate: bool, zarr_format: ZarrFormat): group = Group.from_store(store=store, zarr_format=zarr_format) a = group.create_group(name="a") @@ -1205,6 +1229,7 @@ def test_open_mutable_mapping_sync(): class TestConsolidated: + @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_group_getitem_consolidated(self, store: Store) -> None: root = await AsyncGroup.from_store(store=store) # Set up the test structure with @@ -1255,6 +1280,7 @@ async def test_group_getitem_consolidated(self, store: Store) -> None: rg2 = await rg1.getitem("g2") assert rg2.metadata.consolidated_metadata == ConsolidatedMetadata(metadata={}) + @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_group_delitem_consolidated(self, store: Store) -> None: if isinstance(store, ZipStore): raise pytest.skip("Not implemented") @@ -1291,6 +1317,7 @@ async def test_group_delitem_consolidated(self, store: Store) -> None: assert len(group.metadata.consolidated_metadata.metadata) == 1 assert "g0" not in group.metadata.consolidated_metadata.metadata + @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_open_consolidated_raises(self, store: Store) -> None: if isinstance(store, ZipStore): raise pytest.skip("Not implemented") @@ -1311,6 +1338,7 @@ def test_open_consolidated_raises(self, store: Store) -> None: group = zarr.open_group(store=store, use_consolidated=False) assert group.metadata.consolidated_metadata is None + @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_open_consolidated_raises_async(self, store: Store) -> None: if isinstance(store, ZipStore): raise pytest.skip("Not implemented") diff --git a/tests/test_metadata/test_consolidated.py b/tests/test_metadata/test_consolidated.py index c0218602f6..4f6ce56095 100644 --- a/tests/test_metadata/test_consolidated.py +++ b/tests/test_metadata/test_consolidated.py @@ -28,7 +28,8 @@ @pytest.fixture -async def memory_store_with_hierarchy(memory_store: Store) -> None: +async def memory_store_with_hierarchy() -> None: + memory_store = await zarr.storage.MemoryStore.open(mode="a") g = await group(store=memory_store, attributes={"foo": "bar"}) await g.create_array(name="air", shape=(1, 2, 3)) await g.create_array(name="lat", shape=(1,)) @@ -211,15 +212,20 @@ async def test_consolidated(self, memory_store_with_hierarchy: Store) -> None: "time", ] - def test_consolidated_sync(self, memory_store): - g = zarr.api.synchronous.group(store=memory_store, attributes={"foo": "bar"}) + @pytest.mark.parametrize( + "store", + ["memory_a"], + indirect=True, + ) + def test_consolidated_sync(self, store: Store): + g = zarr.api.synchronous.group(store=store, attributes={"foo": "bar"}) g.create_array(name="air", shape=(1, 2, 3)) g.create_array(name="lat", shape=(1,)) g.create_array(name="lon", shape=(2,)) g.create_array(name="time", shape=(3,)) - zarr.api.synchronous.consolidate_metadata(memory_store) - group2 = zarr.api.synchronous.Group.open(memory_store) + zarr.api.synchronous.consolidate_metadata(store) + group2 = zarr.api.synchronous.Group.open(store) array_metadata = { "attributes": {}, @@ -291,15 +297,20 @@ def test_consolidated_sync(self, memory_store): ), ) assert group2.metadata == expected - group3 = zarr.api.synchronous.open(store=memory_store) + group3 = zarr.api.synchronous.open(store=store) assert group3.metadata == expected - group4 = zarr.api.synchronous.open_consolidated(store=memory_store) + group4 = zarr.api.synchronous.open_consolidated(store=store) assert group4.metadata == expected - async def test_not_writable_raises(self, memory_store: zarr.storage.MemoryStore) -> None: - await group(store=memory_store, attributes={"foo": "bar"}) - read_store = zarr.storage.MemoryStore(store_dict=memory_store._store_dict) + @pytest.mark.parametrize( + "store", + ["memory_a"], + indirect=True, + ) + async def test_not_writable_raises(self, store: zarr.storage.MemoryStore) -> None: + await group(store=store, attributes={"foo": "bar"}) + read_store = zarr.storage.MemoryStore(store_dict=store._store_dict) with pytest.raises(ValueError, match="does not support writing"): await consolidate_metadata(read_store) @@ -481,7 +492,6 @@ def test_to_dict_empty(self): } assert result == expected - @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_open_consolidated_raises_async(self, zarr_format: ZarrFormat): store = zarr.storage.MemoryStore(mode="w") await AsyncGroup.from_store(store, zarr_format=zarr_format) @@ -532,33 +542,35 @@ async def test_consolidated_metadata_v2(self): ) assert result.metadata == expected - @pytest.mark.parametrize("zarr_format", [2, 3]) + @pytest.mark.parametrize( + "store", + ["memory_a"], + indirect=True, + ) async def test_use_consolidated_false( - self, memory_store: zarr.storage.MemoryStore, zarr_format: ZarrFormat + self, store: zarr.storage.MemoryStore, zarr_format: ZarrFormat ) -> None: with zarr.config.set(default_zarr_version=zarr_format): - g = await group(store=memory_store, attributes={"foo": "bar"}) + g = await group(store=store, attributes={"foo": "bar"}) await g.create_group(name="a") # test a stale read - await zarr.api.asynchronous.consolidate_metadata(memory_store) + await zarr.api.asynchronous.consolidate_metadata(store) await g.create_group(name="b") - stale = await zarr.api.asynchronous.open_group(store=memory_store) + stale = await zarr.api.asynchronous.open_group(store=store) assert len([x async for x in stale.members()]) == 1 assert stale.metadata.consolidated_metadata assert list(stale.metadata.consolidated_metadata.metadata) == ["a"] # bypass stale data - good = await zarr.api.asynchronous.open_group( - store=memory_store, use_consolidated=False - ) + good = await zarr.api.asynchronous.open_group(store=store, use_consolidated=False) assert len([x async for x in good.members()]) == 2 # reconsolidate - await zarr.api.asynchronous.consolidate_metadata(memory_store) + await zarr.api.asynchronous.consolidate_metadata(store) - good = await zarr.api.asynchronous.open_group(store=memory_store) + good = await zarr.api.asynchronous.open_group(store=store) assert len([x async for x in good.members()]) == 2 assert good.metadata.consolidated_metadata assert sorted(good.metadata.consolidated_metadata.metadata) == ["a", "b"] diff --git a/tests/test_metadata/test_v2.py b/tests/test_metadata/test_v2.py index 089d5c98e1..f011201b4d 100644 --- a/tests/test_metadata/test_v2.py +++ b/tests/test_metadata/test_v2.py @@ -82,9 +82,7 @@ def test_metadata_to_dict( class TestConsolidated: @pytest.fixture - async def v2_consolidated_metadata( - self, memory_store: zarr.storage.MemoryStore - ) -> zarr.storage.MemoryStore: + async def v2_consolidated_metadata(self) -> zarr.storage.MemoryStore: zmetadata = { "metadata": { ".zattrs": { diff --git a/tests/test_store/test_remote.py b/tests/test_store/test_remote.py index 65c0697a19..114e8bb1c8 100644 --- a/tests/test_store/test_remote.py +++ b/tests/test_store/test_remote.py @@ -12,14 +12,14 @@ from zarr.storage import RemoteStore from zarr.testing.store import StoreTests -from ..conftest import endpoint_url, test_bucket_name +from ..conftest import test_bucket_name -async def test_basic() -> None: +async def test_basic(s3_base) -> None: store = RemoteStore.from_url( f"s3://{test_bucket_name}/foo/spam/", mode="w", - storage_options={"endpoint_url": endpoint_url, "anon": False}, + storage_options={"endpoint_url": s3_base, "anon": False}, ) assert store.fs.asynchronous assert store.path == f"{test_bucket_name}/foo/spam" @@ -40,9 +40,9 @@ class TestRemoteStoreS3(StoreTests[RemoteStore, cpu.Buffer]): buffer_cls = cpu.Buffer @pytest.fixture - def store_kwargs(self, request) -> dict[str, str | bool]: + def store_kwargs(self, request, s3_base) -> dict[str, str | bool]: fs, path = fsspec.url_to_fs( - f"s3://{test_bucket_name}", endpoint_url=endpoint_url, anon=False, asynchronous=True + f"s3://{test_bucket_name}", endpoint_url=s3_base, anon=False, asynchronous=True ) return {"fs": fs, "path": path, "mode": "r+"} @@ -76,9 +76,9 @@ def test_store_supports_partial_writes(self, store: RemoteStore) -> None: def test_store_supports_listing(self, store: RemoteStore) -> None: assert store.supports_listing - async def test_remote_store_from_uri(self, store: RemoteStore): + async def test_remote_store_from_uri(self, store: RemoteStore, s3_base): storage_options = { - "endpoint_url": endpoint_url, + "endpoint_url": s3_base, "anon": False, } @@ -113,15 +113,15 @@ async def test_remote_store_from_uri(self, store: RemoteStore): ) assert dict(group.attrs) == {"key": "value-3"} - def test_from_upath(self) -> None: + def test_from_upath(self, s3_base) -> None: path = UPath( f"s3://{test_bucket_name}/foo/bar/", - endpoint_url=endpoint_url, + endpoint_url=s3_base, anon=False, asynchronous=True, ) result = RemoteStore.from_upath(path) - assert result.fs.endpoint_url == endpoint_url + assert result.fs.endpoint_url == s3_base assert result.fs.asynchronous assert result.path == f"{test_bucket_name}/foo/bar" @@ -133,9 +133,9 @@ def test_init_raises_if_path_has_scheme(self, store_kwargs) -> None: ): self.store_cls(**store_kwargs) - def test_init_warns_if_fs_asynchronous_is_false(self) -> None: + def test_init_warns_if_fs_asynchronous_is_false(self, s3_base) -> None: fs, path = fsspec.url_to_fs( - f"s3://{test_bucket_name}", endpoint_url=endpoint_url, anon=False, asynchronous=False + f"s3://{test_bucket_name}", endpoint_url=s3_base, anon=False, asynchronous=False ) store_kwargs = {"fs": fs, "path": path, "mode": "r+"} with pytest.warns(UserWarning, match=r".* was not created with `asynchronous=True`.*"): From bd5eb138d8394ddf8c667a5f934efacaa9c19623 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 16:25:21 +0100 Subject: [PATCH 11/38] send s3fs fixture to remotestore fixture --- tests/conftest.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3dff32fb91..7c48837f2c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,7 +37,7 @@ async def parse_store( - store: str, path: str, s3_base: str + store: str, path: str, s3: s3fs.S3FileSystem ) -> LocalStore | MemoryStore | RemoteStore | ZipStore: """ Take a string representation of a store + access mode, e.g. 'local_a', which would encode @@ -61,11 +61,7 @@ async def parse_store( case "memory": return await MemoryStore.open(mode=mode) case "remote": - return RemoteStore.from_url( - f"s3://{test_bucket_name}/foo/spam/", - mode=mode, - storage_options={"endpoint_url": s3_base, "anon": False}, - ) + return await RemoteStore.open(fs=s3, path=test_bucket_name, mode=mode) case "zip": return await ZipStore.open(path + "/zarr.zip", mode=mode) raise AssertionError @@ -84,9 +80,11 @@ async def store_path(tmpdir: LEGACY_PATH) -> StorePath: @pytest.fixture -async def store(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3_base: str) -> Store: +async def store( + request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3: s3fs.S3FileSystem +) -> Store: param = request.param - return await parse_store(param, str(tmpdir), s3_base) + return await parse_store(param, str(tmpdir), s3) @pytest.fixture(params=["local", "memory", "zip"]) @@ -204,7 +202,7 @@ def s3(s3_base: str) -> Generator[s3fs.S3FileSystem, None, None]: # type: ignor client = get_boto3_client(s3_base) client.create_bucket(Bucket=test_bucket_name, ACL="public-read") s3fs.S3FileSystem.clear_instance_cache() - s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": s3_base}) + s3 = s3fs.S3FileSystem(anon=False, client_kwargs={"endpoint_url": s3_base}, asynchronous=True) session = sync(s3.set_session()) s3.invalidate_cache() yield s3 From 1c6044bfc9164c3d10a851c8527628fdb34a6e42 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 22:15:48 +0100 Subject: [PATCH 12/38] remove unused method from group and corresponding test --- src/zarr/core/group.py | 12 ------------ tests/test_group.py | 13 ------------- 2 files changed, 25 deletions(-) diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index 46f37700eb..889a9a9b44 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -781,7 +781,6 @@ async def _save_metadata(self, ensure_parents: bool = False) -> None: ).items() ] ) - await asyncio.gather(*awaitables) @property @@ -1420,17 +1419,6 @@ def __setitem__(self, key: str, value: Any) -> None: def __repr__(self) -> str: return f"" - async def update_attributes_async(self, new_attributes: dict[str, Any]) -> Group: - new_metadata = replace(self.metadata, attributes=new_attributes) - - # Write new metadata - to_save = new_metadata.to_buffer_dict(default_buffer_prototype()) - awaitables = [set_or_delete(self.store_path / key, value) for key, value in to_save.items()] - await asyncio.gather(*awaitables) - - async_group = replace(self._async_group, metadata=new_metadata) - return replace(self, _async_group=async_group) - @property def store_path(self) -> StorePath: return self._async_group.store_path diff --git a/tests/test_group.py b/tests/test_group.py index 674173cd41..5df03d52e8 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -579,19 +579,6 @@ def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: assert new_group.attrs == new_attrs -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) -async def test_group_update_attributes_async(store: Store, zarr_format: ZarrFormat) -> None: - """ - Test the behavior of `Group.update_attributes_async` - """ - attrs = {"foo": 100} - group = Group.from_store(store, zarr_format=zarr_format, attributes=attrs) - assert group.attrs == attrs - new_attrs = {"bar": 100} - new_group = await group.update_attributes_async(new_attrs) - assert new_group.attrs == new_attrs - - @pytest.mark.parametrize("method", ["create_array", "array"]) @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_group_create_array( From 4c5f06ea7e6bfb7ae7c2799f5266924fa2c7fc42 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 22:31:49 +0100 Subject: [PATCH 13/38] explicitly depend on moto server --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bb9fa4df85..75f43b65fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -197,7 +197,7 @@ dependencies = [ 'pytest', 'pytest-cov', 'pytest-asyncio', - 'moto[s3]', + 'moto[s3, server]', ] [tool.hatch.envs.upstream.env-vars] From 420ca07ec7b649c54996f3e3a46510930da0cc6e Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 22:34:15 +0100 Subject: [PATCH 14/38] explicitly depend on moto server, also in test deps --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 75f43b65fa..ed70cbd846 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ test = [ "msgpack", "s3fs", "pytest-asyncio", - "moto[s3]", + "moto[s3, server]", "flask-cors", "flask", "requests", @@ -197,7 +197,7 @@ dependencies = [ 'pytest', 'pytest-cov', 'pytest-asyncio', - 'moto[s3, server]', + 'moto[s3', ] [tool.hatch.envs.upstream.env-vars] From bac994d4efabc2460d9070d6607a6f6d6ba7353d Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 22:39:50 +0100 Subject: [PATCH 15/38] ignore mypy errors properly --- tests/conftest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7c48837f2c..d2a92af2dd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,7 +37,9 @@ async def parse_store( - store: str, path: str, s3: s3fs.S3FileSystem + store: str, + path: str, + s3: s3fs.S3FileSystem, # type: ignore[name-defined] ) -> LocalStore | MemoryStore | RemoteStore | ZipStore: """ Take a string representation of a store + access mode, e.g. 'local_a', which would encode @@ -81,7 +83,9 @@ async def store_path(tmpdir: LEGACY_PATH) -> StorePath: @pytest.fixture async def store( - request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3: s3fs.S3FileSystem + request: pytest.FixtureRequest, + tmpdir: LEGACY_PATH, + s3: s3fs.S3FileSystem, # type: ignore[name-defined] ) -> Store: param = request.param return await parse_store(param, str(tmpdir), s3) From 426e1cb42283cfdabc20d0c60202d5d0e6e6b664 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 22:43:34 +0100 Subject: [PATCH 16/38] fixup --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ed70cbd846..bb6e3b7ff7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -197,7 +197,7 @@ dependencies = [ 'pytest', 'pytest-cov', 'pytest-asyncio', - 'moto[s3', + 'moto[s3, server]' ] [tool.hatch.envs.upstream.env-vars] From 0013d22c07d46ac690cab7256051c7c052aaa96c Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 22:46:56 +0100 Subject: [PATCH 17/38] annotate store variables as Store --- tests/test_api.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index c7c3346ffc..1b2c9704d2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -70,6 +70,8 @@ async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: assert isinstance(z, Array) assert z.shape == (100,) + store_w: Store + if isinstance(store, ZipStore): store.close() store_w = await ZipStore.open(store.path, mode="w") @@ -81,6 +83,8 @@ async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: assert isinstance(z, Array) assert z.shape == (200,) + store_r: Store + if isinstance(store, ZipStore): store_w.close() store_r = await ZipStore.open(store.path, mode="r") @@ -121,6 +125,7 @@ async def test_open_group(store: Store) -> None: # g = open_group(store=store) # assert isinstance(g, Group) # assert "foo" not in g + store_r: Store # open group, read-only if isinstance(store, ZipStore): @@ -148,6 +153,8 @@ async def test_open_array_or_group(zarr_format: ZarrFormat, store: Store) -> Non grp_w = group(store=store, path="group", zarr_format=zarr_format, attributes=grp_attrs) arr_w = grp_w.create_array(name="foo", shape=(1,)) + store_2: Store + if isinstance(store, ZipStore): store.close() store_2 = await ZipStore.open(store.path, mode="r") @@ -176,7 +183,7 @@ async def test_open_group_unspecified_version(store: Store, zarr_format: ZarrFor _ = await zarr.api.asynchronous.open_group( store=store, mode="a", zarr_format=zarr_format, attributes={"foo": "bar"} ) - + store_2: Store if isinstance(store, ZipStore): store.close() store_2 = await ZipStore.open(store.path, mode="r") From fb52a7c0a881b9c12829bcb41cec0058462da46b Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Oct 2024 23:02:46 +0100 Subject: [PATCH 18/38] add moto[server] to min_deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bb6e3b7ff7..f3e5fa6b67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -231,7 +231,7 @@ dependencies = [ 'pytest', 'pytest-cov', 'pytest-asyncio', - 'moto[s3]', + 'moto[s3, server]', ] [tool.hatch.envs.min_deps.scripts] From 9ec0d013bd4bf5a627948adcc861ec6b05367922 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 13:32:18 +0100 Subject: [PATCH 19/38] revert store fixture name convention --- tests/conftest.py | 37 +++++++++++++++---------------------- tests/test_api.py | 30 +++++++++++++++--------------- tests/test_array.py | 36 +++++++++++++++++------------------- 3 files changed, 47 insertions(+), 56 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 35b096327b..02857c3a25 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ from zarr.storage.remote import RemoteStore if TYPE_CHECKING: - from collections.abc import Generator + from collections.abc import Generator, AsyncGenerator from typing import Any, Literal import botocore @@ -42,30 +42,21 @@ async def parse_store( s3: s3fs.S3FileSystem, # type: ignore[name-defined] ) -> LocalStore | MemoryStore | RemoteStore | ZipStore: """ - Take a string representation of a store + access mode, e.g. 'local_a', which would encode - LocalStore + access mode `a`, and convert that string representation into the appropriate store object, - which is then returned. + Take a string representation of a store and convert that string representation + into the appropriate store object, which is then returned. """ - store_parsed = store.split("_") - - if len(store_parsed) == 1: - store_type = store_parsed[0] - # the default mode for testing is a - mode = "a" - elif len(store_parsed) == 2: - store_type, mode = store_parsed - else: - raise ValueError(f"Invalid store specification: {store}") - - match store_type: + + match store: case "local": - return await LocalStore.open(path, mode=mode) + return LocalStore(path, read_only=False) case "memory": - return await MemoryStore.open(mode=mode) + return MemoryStore(read_only=False) case "remote": - return await RemoteStore.open(fs=s3, path=test_bucket_name, mode=mode) + return RemoteStore(fs=s3, path=test_bucket_name, read_only=False) case "zip": - return await ZipStore.open(path + "/zarr.zip", mode=mode) + _store = await ZipStore.open(path + "/zarr.zip", read_only=False, mode='w') + return _store + raise AssertionError @@ -86,9 +77,11 @@ async def store( request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3: s3fs.S3FileSystem, # type: ignore[name-defined] -) -> Store: +) -> AsyncGenerator[Store, None, None]: param = request.param - return await parse_store(param, str(tmpdir), s3) + store_instance = await parse_store(param, str(tmpdir), s3) + yield store_instance + store_instance.close() @pytest.fixture(params=["local", "memory", "zip"]) diff --git a/tests/test_api.py b/tests/test_api.py index 65db0ebbb7..55c0913f08 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -28,7 +28,7 @@ from zarr.storage.zip import ZipStore -@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory", "remote"], indirect=True) def test_create_array(store: Store) -> None: # create array z = create(shape=100, store=store) @@ -48,7 +48,7 @@ def test_create_array(store: Store) -> None: assert z.chunks == (40,) -@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) @pytest.mark.parametrize("path", ["foo", "/", "/foo", "///foo/bar"]) @pytest.mark.parametrize("node_type", ["array", "group"]) def test_open_normalized_path( @@ -63,7 +63,7 @@ def test_open_normalized_path( assert node.path == normalize_path(path) -@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: # open array, create if doesn't exist z = open(store=store, shape=100, zarr_format=zarr_format) @@ -101,7 +101,7 @@ async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: # zipstore is marked as xfail because you cannot open a zipstore in read-only mode if it doesn't exist in the first place. @pytest.mark.parametrize( "store", - ["local_r", "memory_r", "remote_r", pytest.param("zip_r", marks=pytest.mark.xfail)], + ["local", "memory", "remote", "zip"], indirect=True, ) def test_open_path_not_found(store: Store, zarr_format: ZarrFormat) -> None: @@ -111,7 +111,7 @@ def test_open_path_not_found(store: Store, zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", "zip_a"], + ["local", "memory", "remote", "zip"], indirect=True, ) async def test_open_group(store: Store) -> None: @@ -144,7 +144,7 @@ async def test_open_group(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", "zip_a"], + ["local", "memory", "remote", "zip"], indirect=True, ) async def test_open_array_or_group(zarr_format: ZarrFormat, store: Store) -> None: @@ -172,7 +172,7 @@ async def test_open_array_or_group(zarr_format: ZarrFormat, store: Store) -> Non @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", "zip_a"], + ["local", "memory", "remote", "zip"], indirect=True, ) @pytest.mark.parametrize("zarr_format", [None, 2, 3]) @@ -244,7 +244,7 @@ def test_save_errors() -> None: @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_r(store: Store) -> None: @@ -265,7 +265,7 @@ def test_open_with_mode_r(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_r_plus(store: Store) -> None: @@ -281,7 +281,7 @@ def test_open_with_mode_r_plus(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], indirect=True, ) async def test_open_with_mode_a(store: Store) -> None: @@ -303,7 +303,7 @@ async def test_open_with_mode_a(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_w(store: Store) -> None: @@ -320,7 +320,7 @@ def test_open_with_mode_w(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local_a", "memory_a", "remote_a", pytest.param("zip_a", marks=pytest.mark.xfail)], + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], indirect=True, ) def test_open_with_mode_w_minus(store: Store) -> None: @@ -364,7 +364,7 @@ def test_array_order(order: MemoryOrder | None, zarr_format: ZarrFormat) -> None # assert "LazyLoader: " in repr(loader) -@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_load_array(store: Store) -> None: foo = np.arange(100) bar = np.arange(100, 0, -1) @@ -1093,7 +1093,7 @@ def test_open_group_positional_args_deprecated() -> None: open_group(store, "w") -@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) def test_open_falls_back_to_open_group(store: Store) -> None: # https://github.com/zarr-developers/zarr-python/issues/2309 zarr.open_group(store, attributes={"key": "value"}) @@ -1103,7 +1103,7 @@ def test_open_falls_back_to_open_group(store: Store) -> None: assert group.attrs == {"key": "value"} -@pytest.mark.parametrize("store", ["local_a", "memory_a", "remote_a", "zip_a"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) async def test_open_falls_back_to_open_group_async(store: Store) -> None: # https://github.com/zarr-developers/zarr-python/issues/2309 await zarr.api.asynchronous.open_group(store, attributes={"key": "value"}) diff --git a/tests/test_array.py b/tests/test_array.py index 0f0ca13aea..f4ef7cfaef 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -10,21 +10,19 @@ import zarr.api.asynchronous from zarr import Array, AsyncArray, Group from zarr.codecs import BytesCodec, VLenBytesCodec -from zarr.core._info import ArrayInfo from zarr.core.array import chunks_initialized from zarr.core.buffer import default_buffer_prototype from zarr.core.buffer.cpu import NDBuffer from zarr.core.common import JSON, MemoryOrder, ZarrFormat from zarr.core.group import AsyncGroup from zarr.core.indexing import ceildiv -from zarr.core.metadata.v3 import DataType from zarr.core.sync import sync from zarr.errors import ContainsArrayError, ContainsGroupError from zarr.storage import LocalStore, MemoryStore from zarr.storage.common import StorePath -@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a", "remote_a"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local", "memory", "zip", "remote"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) @pytest.mark.parametrize("exists_ok", [True, False]) @pytest.mark.parametrize("extant_node", ["array", "group"]) @@ -75,7 +73,7 @@ def test_array_creation_existing_node( ) -@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a", "remote_a"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local", "memory", "zip", "remote"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_create_creates_parents( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -115,7 +113,7 @@ async def test_create_creates_parents( assert isinstance(g, AsyncGroup) -@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a", "remote_a"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local", "memory", "zip", "remote"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_array_name_properties_no_group( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -126,7 +124,7 @@ def test_array_name_properties_no_group( assert arr.basename is None -@pytest.mark.parametrize("store", ["local_a", "memory_a", "zip_a"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_array_name_properties_with_group( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -145,7 +143,7 @@ def test_array_name_properties_with_group( assert spam.basename == "spam" -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize("specifiy_fill_value", [True, False]) @pytest.mark.parametrize("dtype_str", ["bool", "uint8", "complex64"]) def test_array_v3_fill_value_default( @@ -175,7 +173,7 @@ def test_array_v3_fill_value_default( assert arr.fill_value.dtype == arr.dtype -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize( ("dtype_str", "fill_value"), [("bool", True), ("uint8", 99), ("float32", -99.9), ("complex64", 3 + 4j)], @@ -236,7 +234,7 @@ def test_selection_positional_args_deprecated() -> None: arr.set_block_selection((0, slice(None)), 1, None) -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) async def test_array_v3_nan_fill_value(store: MemoryStore) -> None: shape = (10,) arr = Array.create( @@ -255,7 +253,7 @@ async def test_array_v3_nan_fill_value(store: MemoryStore) -> None: assert len([a async for a in store.list_prefix("/")]) == 0 -@pytest.mark.parametrize("store", ["local_a"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) async def test_serializable_async_array( store: LocalStore | MemoryStore, zarr_format: ZarrFormat @@ -273,7 +271,7 @@ async def test_serializable_async_array( # TODO: uncomment the parts of this test that will be impacted by the config/prototype changes in flight -@pytest.mark.parametrize("store", ["local_a"], indirect=["store"]) +@pytest.mark.parametrize("store", ["local"], indirect=["store"]) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_serializable_sync_array(store: LocalStore, zarr_format: ZarrFormat) -> None: expected = Array.create( @@ -288,7 +286,7 @@ def test_serializable_sync_array(store: LocalStore, zarr_format: ZarrFormat) -> np.testing.assert_array_equal(actual[:], expected[:]) -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) def test_storage_transformers(store: MemoryStore) -> None: """ Test that providing an actual storage transformer produces a warning and otherwise passes through @@ -418,7 +416,7 @@ def test_update_attrs(zarr_format: int) -> None: assert arr2.attrs["foo"] == "bar" -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_resize_1d(store: MemoryStore, zarr_format: int) -> None: z = zarr.create( @@ -457,7 +455,7 @@ def test_resize_1d(store: MemoryStore, zarr_format: int) -> None: assert new_shape == z[:].shape -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_resize_2d(store: MemoryStore, zarr_format: int) -> None: z = zarr.create( @@ -519,7 +517,7 @@ def test_resize_2d(store: MemoryStore, zarr_format: int) -> None: assert new_shape == z[:].shape -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_1d(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105) @@ -549,7 +547,7 @@ def test_append_1d(store: MemoryStore, zarr_format: int) -> None: np.testing.assert_array_equal(f, z[:]) -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_2d(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105 * 105, dtype="i4").reshape((105, 105)) @@ -573,7 +571,7 @@ def test_append_2d(store: MemoryStore, zarr_format: int) -> None: np.testing.assert_array_equal(e, actual) -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_2d_axis(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105 * 105, dtype="i4").reshape((105, 105)) @@ -595,7 +593,7 @@ def test_append_2d_axis(store: MemoryStore, zarr_format: int) -> None: np.testing.assert_array_equal(e, z[:]) -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_bad_shape(store: MemoryStore, zarr_format: int) -> None: a = np.arange(100) @@ -608,7 +606,7 @@ def test_append_bad_shape(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("order", ["C", "F", None]) @pytest.mark.parametrize("zarr_format", [2, 3]) -@pytest.mark.parametrize("store", ["memory_a"], indirect=True) +@pytest.mark.parametrize("store", ["memory"], indirect=True) def test_array_create_order( order: MemoryOrder | None, zarr_format: int, store: MemoryStore ) -> None: From 7bc982ed80272e57780c080f26280203a7f0eeae Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 13:33:32 +0100 Subject: [PATCH 20/38] remove unused import --- src/zarr/api/asynchronous.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 109ff6939d..447dd61c9e 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -8,7 +8,6 @@ import numpy as np import numpy.typing as npt -from zarr.api.synchronous import open_array from zarr.core.array import Array, AsyncArray, get_array_metadata from zarr.core.buffer import NDArrayLike from zarr.core.common import ( @@ -53,7 +52,6 @@ "ones", "ones_like", "open", - "open_array", "open_consolidated", "open_group", "open_like", From d776ead1ab4fb366e491a4bc60b6658e570c737d Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 13:34:14 +0100 Subject: [PATCH 21/38] remove mode arg for remotestore --- tests/test_store/test_remote.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_store/test_remote.py b/tests/test_store/test_remote.py index 823b1c6a9d..323772a5fd 100644 --- a/tests/test_store/test_remote.py +++ b/tests/test_store/test_remote.py @@ -18,7 +18,6 @@ async def test_basic(s3_base) -> None: store = RemoteStore.from_url( f"s3://{test_bucket_name}/foo/spam/", - mode="w", storage_options={"endpoint_url": s3_base, "anon": False}, ) assert store.fs.asynchronous From 191c012f5ca96b91e41a7074564b15869f7862c5 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 13:37:47 +0100 Subject: [PATCH 22/38] fixup --- tests/conftest.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 02857c3a25..7feb28b837 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ from zarr.storage.remote import RemoteStore if TYPE_CHECKING: - from collections.abc import Generator, AsyncGenerator + from collections.abc import AsyncGenerator, Generator from typing import Any, Literal import botocore @@ -42,7 +42,7 @@ async def parse_store( s3: s3fs.S3FileSystem, # type: ignore[name-defined] ) -> LocalStore | MemoryStore | RemoteStore | ZipStore: """ - Take a string representation of a store and convert that string representation + Take a string representation of a store and convert that string representation into the appropriate store object, which is then returned. """ @@ -54,14 +54,24 @@ async def parse_store( case "remote": return RemoteStore(fs=s3, path=test_bucket_name, read_only=False) case "zip": - _store = await ZipStore.open(path + "/zarr.zip", read_only=False, mode='w') - return _store + return await ZipStore.open(path + "/zarr.zip", read_only=False, mode="w") raise AssertionError @pytest.fixture(params=[str, pathlib.Path]) def path_type(request: pytest.FixtureRequest) -> Any: + """ + A pytest fixture that provides a parameterized path type. + + This fixture yields different types of path representations + for testing purposes. The possible types are `str` and + `pathlib.Path`. It can be used to test functions or methods + that need to handle different path type inputs. + + Returns: + The path type specified by the current parameter. + """ return request.param @@ -77,7 +87,7 @@ async def store( request: pytest.FixtureRequest, tmpdir: LEGACY_PATH, s3: s3fs.S3FileSystem, # type: ignore[name-defined] -) -> AsyncGenerator[Store, None, None]: +) -> AsyncGenerator[Store, None]: param = request.param store_instance = await parse_store(param, str(tmpdir), s3) yield store_instance From 9def17d3137ad4cd26b1db18bce50bce8325c555 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 14:00:54 +0100 Subject: [PATCH 23/38] add mutability altering funcs --- tests/conftest.py | 29 ++++++++++++++++++++++++++++ tests/test_api.py | 48 +++++++++++++---------------------------------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7feb28b837..a1eaa94ecf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,35 @@ test_bucket_name = "test" secure_bucket_name = "test-secure" +def as_mutable(store: Store) -> Store: + """ + Return a mutable version of the store + """ + if isinstance(store, LocalStore): + return LocalStore(store.root, read_only=False) + if isinstance(store, MemoryStore): + return MemoryStore(store._store_dict, read_only=False) + if isinstance(store, RemoteStore): + return RemoteStore(fs=store.fs, path=store.path, read_only=False) + if isinstance(store, ZipStore): + store.close() + return sync(ZipStore.open(path=store.path, read_only=False)) + raise ValueError(f'Unknown store type: {type(store)}') + +def as_immutable(store: Store) -> Store: + """ + Return an immutable version of the store + """ + if isinstance(store, LocalStore): + return LocalStore(store.root, read_only=True) + if isinstance(store, MemoryStore): + return MemoryStore(store._store_dict, read_only=True) + if isinstance(store, RemoteStore): + return RemoteStore(fs=store.fs, path=store.path, read_only=True) + if isinstance(store, ZipStore): + store.close() + return sync(ZipStore.open(path=store.path, read_only=True)) + raise ValueError(f'Unknown store type: {type(store)}') async def parse_store( store: str, diff --git a/tests/test_api.py b/tests/test_api.py index 55c0913f08..86774f1433 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -5,6 +5,7 @@ import pytest from numpy.testing import assert_array_equal +from tests.conftest import as_immutable import zarr import zarr.api.asynchronous import zarr.core.group @@ -70,29 +71,13 @@ async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: assert isinstance(z, Array) assert z.shape == (100,) - store_w: Store - if isinstance(store, ZipStore): - store.close() - store_w = await ZipStore.open(store.path, mode="w") - else: - # open array, overwrite - store_w = store.with_mode("w") - - z = open(store=store_w, shape=200, zarr_format=zarr_format) + z = open(store=store, shape=200, zarr_format=zarr_format, mode='w') assert isinstance(z, Array) assert z.shape == (200,) - store_r: Store - - if isinstance(store, ZipStore): - store_w.close() - store_r = await ZipStore.open(store.path, mode="r") - else: - # open array, read-only - store_r = store.with_mode("r") - - z = open(store=store_r, zarr_format=zarr_format) + store_r = as_immutable(store) + z = open(store=store_r, zarr_format=zarr_format, mode='r') assert isinstance(z, Array) assert z.shape == (200,) assert z.read_only @@ -125,15 +110,7 @@ async def test_open_group(store: Store) -> None: # g = open_group(store=store) # assert isinstance(g, Group) # assert "foo" not in g - store_r: Store - - # open group, read-only - if isinstance(store, ZipStore): - store.close() - store_r = await ZipStore.open(store.path, mode="r") - else: - # open array, read-only - store_r = store.with_mode("r") + store_r = as_immutable(store) g = open_group(store=store_r) assert isinstance(g, Group) @@ -1059,13 +1036,14 @@ def test_tree() -> None: # copy(source["foo"], dest, dry_run=True, log=True) -def test_open_positional_args_deprecated() -> None: - store = MemoryStore() +@pytest.mark.parametrize("store", ["memory"], indirect=True) +def test_open_positional_args_deprecated(store: MemoryStore) -> None: with pytest.warns(FutureWarning, match="pass"): open(store, "w", shape=(1,)) -def test_save_array_positional_args_deprecated() -> None: +@pytest.mark.parametrize("store", ["memory"], indirect=True) +def test_save_array_positional_args_deprecated(store: MemoryStore) -> None: store = MemoryStore() with warnings.catch_warnings(): warnings.filterwarnings( @@ -1081,14 +1059,14 @@ def test_save_array_positional_args_deprecated() -> None: ) -def test_group_positional_args_deprecated() -> None: - store = MemoryStore() +@pytest.mark.parametrize("store", ["memory"], indirect=True) +def test_group_positional_args_deprecated(store: MemoryStore) -> None: with pytest.warns(FutureWarning, match="pass"): group(store, True) -def test_open_group_positional_args_deprecated() -> None: - store = MemoryStore() +@pytest.mark.parametrize("store", ["memory"], indirect=True) +def test_open_group_positional_args_deprecated(store: MemoryStore) -> None: with pytest.warns(FutureWarning, match="pass"): open_group(store, "w") From d1c95b26faf480e2cf8dfb5763b30699f8676c42 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 14:41:05 +0100 Subject: [PATCH 24/38] get consolidated tests to pass --- tests/test_metadata/test_consolidated.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_metadata/test_consolidated.py b/tests/test_metadata/test_consolidated.py index dfc5df72a6..007e5e3506 100644 --- a/tests/test_metadata/test_consolidated.py +++ b/tests/test_metadata/test_consolidated.py @@ -29,7 +29,7 @@ @pytest.fixture async def memory_store_with_hierarchy() -> None: - memory_store = await zarr.storage.MemoryStore.open(mode="a") + memory_store = zarr.storage.MemoryStore() g = await group(store=memory_store, attributes={"foo": "bar"}) await g.create_array(name="air", shape=(1, 2, 3)) await g.create_array(name="lat", shape=(1,)) @@ -202,7 +202,7 @@ async def test_consolidated(self, memory_store_with_hierarchy: Store) -> None: @pytest.mark.parametrize( "store", - ["memory_a"], + ["memory"], indirect=True, ) def test_consolidated_sync(self, store: Store): @@ -287,12 +287,12 @@ def test_consolidated_sync(self, store: Store): @pytest.mark.parametrize( "store", - ["memory_a"], + ["memory"], indirect=True, ) async def test_not_writable_raises(self, store: zarr.storage.MemoryStore) -> None: await group(store=store, attributes={"foo": "bar"}) - read_store = zarr.storage.MemoryStore(store_dict=store._store_dict) + read_store = zarr.storage.MemoryStore(store_dict=store._store_dict, read_only=True) with pytest.raises(ValueError, match="does not support writing"): await consolidate_metadata(read_store) @@ -518,7 +518,7 @@ async def test_consolidated_metadata_v2(self): @pytest.mark.parametrize( "store", - ["memory_a"], + ["memory"], indirect=True, ) async def test_use_consolidated_false( From a04e63697220bb111c194534a11e7273dd61c32a Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 15:48:48 +0100 Subject: [PATCH 25/38] fix for open_array in r+ mode --- src/zarr/api/asynchronous.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 447dd61c9e..9edf895770 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -299,12 +299,8 @@ async def open( store_path = await make_store_path(store, mode=mode, path=path, storage_options=storage_options) - # TODO: check for more array-only kwargs - expect_array = "shape" in kwargs - - if expect_array: - return await open_array(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) - else: + # TODO: the mode check below seems wrong! + if "shape" not in kwargs and mode in _READ_MODES: try: metadata_dict = await get_array_metadata(store_path, zarr_format=zarr_format) # TODO: remove this cast when we fix typing for array metadata dicts @@ -314,10 +310,18 @@ async def open( is_v3_array = zarr_format == 3 and _metadata_dict.get("node_type") == "array" if is_v3_array or zarr_format == 2: return AsyncArray(store_path=store_path, metadata=_metadata_dict) - except (AssertionError, NodeTypeValidationError, FileNotFoundError): + except (AssertionError, FileNotFoundError, NodeTypeValidationError): pass return await open_group(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) + try: + return await open_array(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) + except (KeyError, NodeTypeValidationError): + # KeyError for a missing key + # NodeTypeValidationError for failing to parse node metadata as an array when it's + # actually a group + return await open_group(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs) + async def open_consolidated( *args: Any, use_consolidated: Literal[True] = True, **kwargs: Any @@ -1088,11 +1092,10 @@ async def open_array( store_path = await make_store_path(store, path=path, mode=mode, storage_options=storage_options) zarr_format = _handle_zarr_version_or_format(zarr_version=zarr_version, zarr_format=zarr_format) - try: return await AsyncArray.open(store_path, zarr_format=zarr_format) except FileNotFoundError: - if not store_path.read_only: + if not store_path.read_only and mode in _CREATE_MODES: exists_ok = _infer_exists_ok(mode) _zarr_format = zarr_format or _default_zarr_version() return await create( From e29e0724c9411a3453e72704bf66c4641fe2c656 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 17:22:02 +0100 Subject: [PATCH 26/38] fix broken zipstore delete_dir --- src/zarr/storage/common.py | 5 +---- src/zarr/storage/zip.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/zarr/storage/common.py b/src/zarr/storage/common.py index 0485a33576..1e33967414 100644 --- a/src/zarr/storage/common.py +++ b/src/zarr/storage/common.py @@ -160,10 +160,7 @@ async def delete_dir(self) -> None: """ Delete all keys with the given prefix from the store. """ - path = self.path - if not path.endswith("/"): - path += "/" - await self.store.delete_dir(path) + await self.store.delete_dir(self.path) async def set_if_not_exists(self, default: Buffer) -> None: """ diff --git a/src/zarr/storage/zip.py b/src/zarr/storage/zip.py index 755588fb7a..a186b3cf59 100644 --- a/src/zarr/storage/zip.py +++ b/src/zarr/storage/zip.py @@ -215,7 +215,7 @@ async def set_if_not_exists(self, key: str, value: Buffer) -> None: async def delete_dir(self, prefix: str) -> None: # only raise NotImplementedError if any keys are found self._check_writable() - if not prefix.endswith("/"): + if prefix != "" and not prefix.endswith("/"): prefix += "/" async for _ in self.list_prefix(prefix): raise NotImplementedError From cbf1a61f4d63cc19192347a96515960bf4f15274 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 17:30:14 +0100 Subject: [PATCH 27/38] xfail zipstore in open_array test --- tests/test_api.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 86774f1433..b62b3ebfe2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -5,10 +5,10 @@ import pytest from numpy.testing import assert_array_equal -from tests.conftest import as_immutable import zarr import zarr.api.asynchronous import zarr.core.group +from tests.conftest import as_immutable from zarr import Array, Group from zarr.abc.store import Store from zarr.api.synchronous import ( @@ -64,26 +64,30 @@ def test_open_normalized_path( assert node.path == normalize_path(path) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + indirect=True, +) async def test_open_array(store: Store, zarr_format: ZarrFormat) -> None: # open array, create if doesn't exist z = open(store=store, shape=100, zarr_format=zarr_format) assert isinstance(z, Array) assert z.shape == (100,) - - z = open(store=store, shape=200, zarr_format=zarr_format, mode='w') + # invoke open again, with a different shape and mode w. + # We expect the store to be wiped at the current path and new array to come out. + z = open(store=store, shape=200, zarr_format=zarr_format, mode="w") assert isinstance(z, Array) assert z.shape == (200,) store_r = as_immutable(store) - z = open(store=store_r, zarr_format=zarr_format, mode='r') + z = open(store=store_r, zarr_format=zarr_format, mode="r") assert isinstance(z, Array) assert z.shape == (200,) assert z.read_only -# zipstore is marked as xfail because you cannot open a zipstore in read-only mode if it doesn't exist in the first place. @pytest.mark.parametrize( "store", ["local", "memory", "remote", "zip"], @@ -112,7 +116,7 @@ async def test_open_group(store: Store) -> None: # assert "foo" not in g store_r = as_immutable(store) - g = open_group(store=store_r) + g = open_group(store=store_r, mode="r") assert isinstance(g, Group) if isinstance(store, ZipStore): @@ -1103,3 +1107,19 @@ async def test_metadata_validation_error() -> None: match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.", ): await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") # type: ignore[arg-type] + + +@pytest.mark.parametrize( + "store", + ["local", "memory", "remote", "zip"], + indirect=True, +) +def test_open_array_with_mode_r_plus(store: Store) -> None: + # 'r+' means read/write (must exist) + with pytest.raises(FileNotFoundError): + zarr.open_array(store=store, mode="r+") + zarr.ones(store=store, shape=(3, 3)) + z2 = zarr.open(store=store, mode="r+") + assert isinstance(z2, Array) + assert (z2[:] == 1).all() + z2[:] = 3 From cadfef1b35ee4cd035ac9c88a5404be929d5e2b4 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 17:30:37 +0100 Subject: [PATCH 28/38] add missing import --- tests/test_group.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_group.py b/tests/test_group.py index 8d5f25057c..6ed1fe083c 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -14,6 +14,7 @@ import zarr.api.synchronous import zarr.storage from zarr import Array, AsyncArray, AsyncGroup, Group +from zarr.core._info import GroupInfo from zarr.core.buffer import default_buffer_prototype from zarr.core.group import ConsolidatedMetadata, GroupMetadata from zarr.core.sync import sync From c0f777de38870c0a9cede8c9de11554fe1e7dfd3 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 17:56:12 +0100 Subject: [PATCH 29/38] use create instead of open_array in fill_value test --- tests/test_v2.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_v2.py b/tests/test_v2.py index b6ce4f97b0..e6b50ab2ae 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -36,6 +36,7 @@ def test_simple(store: StorePath) -> None: assert np.array_equal(data, a[:, :]) +@pytest.mark.parametrize("store", ["memory"], indirect=True) @pytest.mark.parametrize( ("dtype", "fill_value"), [ @@ -48,8 +49,8 @@ def test_simple(store: StorePath) -> None: (str, ""), ], ) -def test_implicit_fill_value(store: StorePath, dtype: str, fill_value: Any) -> None: - arr = zarr.open_array(store=store, shape=(4,), fill_value=None, zarr_format=2, dtype=dtype) +def test_implicit_fill_value(store: MemoryStore, dtype: str, fill_value: Any) -> None: + arr = zarr.create(store=store, shape=(4,), fill_value=None, zarr_format=2, dtype=dtype) assert arr.metadata.fill_value is None assert arr.metadata.to_dict()["fill_value"] is None result = arr[:] From 94e2468ede41141edcd7799ff0fadba2650da090 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 17:56:53 +0100 Subject: [PATCH 30/38] remove xfail for zipstore in test_open_with_mode_r_plus --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index b62b3ebfe2..2e0c77985b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -246,7 +246,7 @@ def test_open_with_mode_r(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local", "memory", "remote", "zip"], indirect=True, ) def test_open_with_mode_r_plus(store: Store) -> None: From 15b02114d7ed54dbede3b394621c067a96c31961 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 19:10:53 +0100 Subject: [PATCH 31/38] xfail test that fails for any warning on python 3.12 or higher with remotestore --- tests/test_group.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_group.py b/tests/test_group.py index 6ed1fe083c..54e9de28b4 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -3,6 +3,7 @@ import contextlib import operator import pickle +import sys import warnings from typing import TYPE_CHECKING, Any, Literal @@ -1190,7 +1191,22 @@ async def test_require_array(store: Store, zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("consolidate", [True, False]) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize( + "store", + [ + "local", + "memory", + pytest.param( + "remote", + marks=pytest.mark.xfail( + sys.version_info >= (3, 12), + reason="Valid warnings are raised from botocore on python 3.12+", + ), + ), + "zip", + ], + indirect=True, +) async def test_members_name(store: Store, consolidate: bool, zarr_format: ZarrFormat): group = Group.from_store(store=store, zarr_format=zarr_format) a = group.create_group(name="a") From 8d3b66c828ce73403481a5e556e503d090f82317 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 19:23:07 +0100 Subject: [PATCH 32/38] remove xfail for zip --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 2e0c77985b..312e031677 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -301,7 +301,7 @@ def test_open_with_mode_w(store: Store) -> None: @pytest.mark.parametrize( "store", - ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], + ["local", "memory", "remote", "zip"], indirect=True, ) def test_open_with_mode_w_minus(store: Store) -> None: From 5e5376fb349fc3d69bad0e0ec6769c744dd1cd7e Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 19:24:45 +0100 Subject: [PATCH 33/38] lint --- tests/conftest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a1eaa94ecf..ff2f298542 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,7 @@ test_bucket_name = "test" secure_bucket_name = "test-secure" + def as_mutable(store: Store) -> Store: """ Return a mutable version of the store @@ -48,7 +49,8 @@ def as_mutable(store: Store) -> Store: if isinstance(store, ZipStore): store.close() return sync(ZipStore.open(path=store.path, read_only=False)) - raise ValueError(f'Unknown store type: {type(store)}') + raise ValueError(f"Unknown store type: {type(store)}") + def as_immutable(store: Store) -> Store: """ @@ -63,7 +65,8 @@ def as_immutable(store: Store) -> Store: if isinstance(store, ZipStore): store.close() return sync(ZipStore.open(path=store.path, read_only=True)) - raise ValueError(f'Unknown store type: {type(store)}') + raise ValueError(f"Unknown store type: {type(store)}") + async def parse_store( store: str, From d087e9b97a16f6d118f492309cbda193bba3d480 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 19:33:16 +0100 Subject: [PATCH 34/38] fix open_array for mode r+ --- src/zarr/api/asynchronous.py | 2 +- tests/test_api.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 94ecfee4d6..3f36614cc2 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -1097,7 +1097,7 @@ async def open_array( try: return await AsyncArray.open(store_path, zarr_format=zarr_format) except FileNotFoundError: - if not store_path.read_only: + if not store_path.read_only and mode in _CREATE_MODES: exists_ok = _infer_exists_ok(mode) _zarr_format = zarr_format or _default_zarr_version() return await create( diff --git a/tests/test_api.py b/tests/test_api.py index fa3d80014d..c7fc88241f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1030,3 +1030,19 @@ async def test_metadata_validation_error() -> None: match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.", ): await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") # type: ignore[arg-type] + + +@pytest.mark.parametrize( + "store", + ["local", "memory", "zip"], + indirect=True, +) +def test_open_array_with_mode_r_plus(store: Store) -> None: + # 'r+' means read/write (must exist) + with pytest.raises(FileNotFoundError): + zarr.open_array(store=store, mode="r+") + zarr.ones(store=store, shape=(3, 3)) + z2 = zarr.open_array(store=store, mode="r+") + assert isinstance(z2, Array) + assert (z2[:] == 1).all() + z2[:] = 3 From 9f4599783c9e13b48b9886501dbcc6eabb185ae6 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 20:00:48 +0100 Subject: [PATCH 35/38] simplify group tests by removing zip and remote store cases --- tests/test_group.py | 86 +++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/tests/test_group.py b/tests/test_group.py index 54e9de28b4..bd8b6a8f98 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -36,7 +36,7 @@ def exists_ok(request: pytest.FixtureRequest) -> bool: return result -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_init(store: Store, zarr_format: ZarrFormat) -> None: """ Test that initializing a group from an asyncgroup works. @@ -46,7 +46,7 @@ def test_group_init(store: Store, zarr_format: ZarrFormat) -> None: assert group._async_group == agroup -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_create_creates_parents(store: Store, zarr_format: ZarrFormat) -> None: # prepare a root node, with some data set await zarr.api.asynchronous.open_group( @@ -99,7 +99,7 @@ async def test_create_creates_parents(store: Store, zarr_format: ZarrFormat) -> assert g.attrs == {} -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_name_properties(store: Store, zarr_format: ZarrFormat) -> None: """ Test basic properties of groups @@ -120,7 +120,7 @@ def test_group_name_properties(store: Store, zarr_format: ZarrFormat) -> None: assert bar.basename == "bar" -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) @pytest.mark.parametrize("consolidated_metadata", [True, False]) def test_group_members(store: Store, zarr_format: ZarrFormat, consolidated_metadata: bool) -> None: """ @@ -203,7 +203,7 @@ def test_group_members(store: Store, zarr_format: ZarrFormat, consolidated_metad members_observed = group.members(max_depth=-1) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group(store: Store, zarr_format: ZarrFormat) -> None: """ Test basic Group routines. @@ -246,7 +246,7 @@ def test_group(store: Store, zarr_format: ZarrFormat) -> None: assert dict(bar3.attrs) == {"baz": "qux", "name": "bar"} -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_create(store: Store, exists_ok: bool, zarr_format: ZarrFormat) -> None: """ Test that `Group.from_store` works as expected. @@ -263,7 +263,7 @@ def test_group_create(store: Store, exists_ok: bool, zarr_format: ZarrFormat) -> _ = Group.from_store(store, exists_ok=exists_ok, zarr_format=zarr_format) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_open(store: Store, zarr_format: ZarrFormat, exists_ok: bool) -> None: """ Test the `Group.open` method. @@ -301,7 +301,7 @@ def test_group_open(store: Store, zarr_format: ZarrFormat, exists_ok: bool) -> N @pytest.mark.parametrize("consolidated", [True, False]) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_getitem(store: Store, zarr_format: ZarrFormat, consolidated: bool) -> None: """ Test the `Group.__getitem__` method. @@ -358,7 +358,7 @@ def test_group_getitem(store: Store, zarr_format: ZarrFormat, consolidated: bool group["subarray/subsubarray"] -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_get_with_default(store: Store, zarr_format: ZarrFormat) -> None: group = Group.from_store(store, zarr_format=zarr_format) @@ -379,7 +379,7 @@ def test_group_get_with_default(store: Store, zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("consolidated", [True, False]) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_delitem(store: Store, zarr_format: ZarrFormat, consolidated: bool) -> None: """ Test the `Group.__delitem__` method. @@ -409,7 +409,7 @@ def test_group_delitem(store: Store, zarr_format: ZarrFormat, consolidated: bool group["subarray"] -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_iter(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__iter__` method. @@ -419,7 +419,7 @@ def test_group_iter(store: Store, zarr_format: ZarrFormat) -> None: assert list(group) == [] -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_len(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__len__` method. @@ -429,7 +429,7 @@ def test_group_len(store: Store, zarr_format: ZarrFormat) -> None: assert len(group) == 0 -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_setitem(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__setitem__` method. @@ -456,7 +456,7 @@ def test_group_setitem(store: Store, zarr_format: ZarrFormat) -> None: np.testing.assert_array_equal(group[key], arr) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__contains__` method @@ -467,7 +467,7 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None: assert "foo" in group -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) @pytest.mark.parametrize("consolidate", [True, False]) def test_group_child_iterators(store: Store, zarr_format: ZarrFormat, consolidate: bool): group = Group.from_store(store, zarr_format=zarr_format) @@ -573,7 +573,7 @@ def test_group_child_iterators(store: Store, zarr_format: ZarrFormat, consolidat assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: """ Test the behavior of `Group.update_attributes` @@ -587,7 +587,7 @@ def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("method", ["create_array", "array"]) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_create_array( store: Store, zarr_format: ZarrFormat, @@ -622,7 +622,7 @@ def test_group_create_array( assert np.array_equal(array[:], data) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_group_array_creation( store: Store, zarr_format: ZarrFormat, @@ -691,9 +691,7 @@ def test_group_array_creation( assert full_like_array.store_path.store == store -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) -@pytest.mark.parametrize("exists_ok", [True, False]) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) @pytest.mark.parametrize("extant_node", ["array", "group"]) def test_group_creation_existing_node( store: Store, @@ -740,7 +738,7 @@ def test_group_creation_existing_node( ) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_create( store: Store, exists_ok: bool, @@ -783,7 +781,7 @@ async def test_asyncgroup_create( ) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_attrs(store: Store, zarr_format: ZarrFormat) -> None: attributes = {"foo": 100} agroup = await AsyncGroup.from_store(store, zarr_format=zarr_format, attributes=attributes) @@ -791,7 +789,7 @@ async def test_asyncgroup_attrs(store: Store, zarr_format: ZarrFormat) -> None: assert agroup.attrs == agroup.metadata.attributes == attributes -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_info(store: Store, zarr_format: ZarrFormat) -> None: agroup = await AsyncGroup.from_store( # noqa: F841 store, @@ -801,7 +799,7 @@ async def test_asyncgroup_info(store: Store, zarr_format: ZarrFormat) -> None: # assert agroup.info == agroup.metadata.info -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_open( store: Store, zarr_format: ZarrFormat, @@ -823,7 +821,7 @@ async def test_asyncgroup_open( assert group_w == group_r -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_open_wrong_format( store: Store, zarr_format: ZarrFormat, @@ -844,7 +842,7 @@ async def test_asyncgroup_open_wrong_format( # todo: replace the dict[str, Any] type with something a bit more specific # should this be async? -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) @pytest.mark.parametrize( "data", [ @@ -867,7 +865,7 @@ def test_asyncgroup_from_dict(store: Store, data: dict[str, Any]) -> None: # todo: replace this with a declarative API where we model a full hierarchy -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_getitem(store: Store, zarr_format: ZarrFormat) -> None: """ Create an `AsyncGroup`, then create members of that group, and ensure that we can access those @@ -890,7 +888,7 @@ async def test_asyncgroup_getitem(store: Store, zarr_format: ZarrFormat) -> None await agroup.getitem("foo") -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_delitem(store: Store, zarr_format: ZarrFormat) -> None: if not store.supports_deletes: pytest.skip("store does not support deletes") @@ -927,7 +925,7 @@ async def test_asyncgroup_delitem(store: Store, zarr_format: ZarrFormat) -> None raise AssertionError -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_create_group( store: Store, zarr_format: ZarrFormat, @@ -944,7 +942,7 @@ async def test_asyncgroup_create_group( assert subnode.metadata.zarr_format == zarr_format -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_create_array( store: Store, zarr_format: ZarrFormat, exists_ok: bool ) -> None: @@ -984,7 +982,7 @@ async def test_asyncgroup_create_array( assert subnode.metadata.zarr_format == zarr_format -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_asyncgroup_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: """ Test that the AsyncGroup.update_attributes method works correctly. @@ -999,8 +997,7 @@ async def test_asyncgroup_update_attributes(store: Store, zarr_format: ZarrForma assert agroup_new_attributes.attrs == attributes_new -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_serializable_async_group(store: LocalStore, zarr_format: ZarrFormat) -> None: expected = await AsyncGroup.from_store( store=store, attributes={"foo": 999}, zarr_format=zarr_format @@ -1010,8 +1007,7 @@ async def test_serializable_async_group(store: LocalStore, zarr_format: ZarrForm assert actual == expected -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_serializable_sync_group(store: LocalStore, zarr_format: ZarrFormat) -> None: expected = Group.from_store(store=store, attributes={"foo": 999}, zarr_format=zarr_format) p = pickle.dumps(expected) @@ -1020,7 +1016,7 @@ def test_serializable_sync_group(store: LocalStore, zarr_format: ZarrFormat) -> @pytest.mark.parametrize("consolidated_metadata", [True, False]) -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_group_members_async(store: Store, consolidated_metadata: bool) -> None: group = await AsyncGroup.from_store( store=store, @@ -1096,7 +1092,7 @@ async def test_group_members_async(store: Store, consolidated_metadata: bool) -> assert nmembers == 4 -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_require_group(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) @@ -1127,7 +1123,7 @@ async def test_require_group(store: Store, zarr_format: ZarrFormat) -> None: await foo_group.require_group("bar") -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_require_groups(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) # create foo group @@ -1149,7 +1145,7 @@ async def test_require_groups(store: Store, zarr_format: ZarrFormat) -> None: assert no_group == () -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_create_dataset(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) with pytest.warns(DeprecationWarning): @@ -1164,7 +1160,7 @@ async def test_create_dataset(store: Store, zarr_format: ZarrFormat) -> None: await root.create_dataset("bar", shape=(100,), dtype="int8") -@pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_require_array(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) foo1 = await root.require_array("foo", shape=(10,), dtype="i8", attributes={"foo": 101}) @@ -1246,7 +1242,7 @@ def test_open_mutable_mapping_sync(): class TestConsolidated: - @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) + @pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_group_getitem_consolidated(self, store: Store) -> None: root = await AsyncGroup.from_store(store=store) # Set up the test structure with @@ -1297,7 +1293,7 @@ async def test_group_getitem_consolidated(self, store: Store) -> None: rg2 = await rg1.getitem("g2") assert rg2.metadata.consolidated_metadata == ConsolidatedMetadata(metadata={}) - @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) + @pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_group_delitem_consolidated(self, store: Store) -> None: if isinstance(store, ZipStore): raise pytest.skip("Not implemented") @@ -1334,7 +1330,7 @@ async def test_group_delitem_consolidated(self, store: Store) -> None: assert len(group.metadata.consolidated_metadata.metadata) == 1 assert "g0" not in group.metadata.consolidated_metadata.metadata - @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) + @pytest.mark.parametrize("store", ["local", "memory"], indirect=True) def test_open_consolidated_raises(self, store: Store) -> None: if isinstance(store, ZipStore): raise pytest.skip("Not implemented") @@ -1355,7 +1351,7 @@ def test_open_consolidated_raises(self, store: Store) -> None: group = zarr.open_group(store=store, use_consolidated=False) assert group.metadata.consolidated_metadata is None - @pytest.mark.parametrize("store", ["local", "memory", "remote", "zip"], indirect=True) + @pytest.mark.parametrize("store", ["local", "memory"], indirect=True) async def test_open_consolidated_raises_async(self, store: Store) -> None: if isinstance(store, ZipStore): raise pytest.skip("Not implemented") From c0d5407000e05a196a7bd408aabfb983f9e42b35 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 20:08:13 +0100 Subject: [PATCH 36/38] simplify test_array --- tests/test_array.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index f162e01ede..6cd0d3203e 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -22,8 +22,7 @@ from zarr.storage.common import StorePath -@pytest.mark.parametrize("store", ["local", "memory", "zip", "remote"], indirect=["store"]) -@pytest.mark.parametrize("zarr_format", [2, 3]) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) @pytest.mark.parametrize("exists_ok", [True, False]) @pytest.mark.parametrize("extant_node", ["array", "group"]) def test_array_creation_existing_node( @@ -73,8 +72,7 @@ def test_array_creation_existing_node( ) -@pytest.mark.parametrize("store", ["local", "memory", "zip", "remote"], indirect=["store"]) -@pytest.mark.parametrize("zarr_format", [2, 3]) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) async def test_create_creates_parents( store: LocalStore | MemoryStore, zarr_format: ZarrFormat ) -> None: @@ -113,8 +111,7 @@ async def test_create_creates_parents( assert isinstance(g, AsyncGroup) -@pytest.mark.parametrize("store", ["local", "memory", "zip", "remote"], indirect=["store"]) -@pytest.mark.parametrize("zarr_format", [2, 3]) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) def test_array_name_properties_no_group( store: LocalStore | MemoryStore, zarr_format: ZarrFormat ) -> None: @@ -124,8 +121,7 @@ def test_array_name_properties_no_group( assert arr.basename is None -@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"]) -@pytest.mark.parametrize("zarr_format", [2, 3]) +@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) def test_array_name_properties_with_group( store: LocalStore | MemoryStore, zarr_format: ZarrFormat ) -> None: @@ -254,7 +250,6 @@ async def test_array_v3_nan_fill_value(store: MemoryStore) -> None: @pytest.mark.parametrize("store", ["local"], indirect=["store"]) -@pytest.mark.parametrize("zarr_format", [2, 3]) async def test_serializable_async_array( store: LocalStore | MemoryStore, zarr_format: ZarrFormat ) -> None: @@ -272,7 +267,6 @@ async def test_serializable_async_array( @pytest.mark.parametrize("store", ["local"], indirect=["store"]) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_serializable_sync_array(store: LocalStore, zarr_format: ZarrFormat) -> None: expected = Array.create( store=store, shape=(100,), chunks=(10,), zarr_format=zarr_format, dtype="i4" @@ -428,7 +422,6 @@ def test_vlen_errors() -> None: ) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_update_attrs(zarr_format: int) -> None: # regression test for https://github.com/zarr-developers/zarr-python/issues/2328 store = MemoryStore() @@ -441,7 +434,6 @@ def test_update_attrs(zarr_format: int) -> None: @pytest.mark.parametrize("store", ["memory"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_resize_1d(store: MemoryStore, zarr_format: int) -> None: z = zarr.create( shape=105, chunks=10, dtype="i4", fill_value=0, store=store, zarr_format=zarr_format @@ -480,7 +472,6 @@ def test_resize_1d(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("store", ["memory"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_resize_2d(store: MemoryStore, zarr_format: int) -> None: z = zarr.create( shape=(105, 105), @@ -542,7 +533,6 @@ def test_resize_2d(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("store", ["memory"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_1d(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105) z = zarr.create(shape=a.shape, chunks=10, dtype=a.dtype, store=store, zarr_format=zarr_format) @@ -572,7 +562,6 @@ def test_append_1d(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("store", ["memory"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_2d(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105 * 105, dtype="i4").reshape((105, 105)) z = zarr.create( @@ -596,7 +585,6 @@ def test_append_2d(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("store", ["memory"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_2d_axis(store: MemoryStore, zarr_format: int) -> None: a = np.arange(105 * 105, dtype="i4").reshape((105, 105)) z = zarr.create( @@ -618,7 +606,6 @@ def test_append_2d_axis(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("store", ["memory"], indirect=True) -@pytest.mark.parametrize("zarr_format", [2, 3]) def test_append_bad_shape(store: MemoryStore, zarr_format: int) -> None: a = np.arange(100) z = zarr.create(shape=a.shape, chunks=10, dtype=a.dtype, store=store, zarr_format=zarr_format) @@ -629,7 +616,6 @@ def test_append_bad_shape(store: MemoryStore, zarr_format: int) -> None: @pytest.mark.parametrize("order", ["C", "F", None]) -@pytest.mark.parametrize("zarr_format", [2, 3]) @pytest.mark.parametrize("store", ["memory"], indirect=True) def test_array_create_order( order: MemoryOrder | None, zarr_format: int, store: MemoryStore From f946499f96dbbddba18bcfb56aaf5da041fa8fd3 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 20:10:28 +0100 Subject: [PATCH 37/38] use zarr_format fixture --- tests/test_info.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_info.py b/tests/test_info.py index 5d9264aa13..640ad41cb7 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -7,10 +7,7 @@ from zarr.core._info import ArrayInfo, GroupInfo, human_readable_size from zarr.core.common import ZarrFormat -ZARR_FORMATS = [2, 3] - -@pytest.mark.parametrize("zarr_format", ZARR_FORMATS) def test_group_info_repr(zarr_format: ZarrFormat) -> None: info = GroupInfo( _name="a", _store_type="MemoryStore", _read_only=False, _zarr_format=zarr_format @@ -25,7 +22,6 @@ def test_group_info_repr(zarr_format: ZarrFormat) -> None: assert result == expected -@pytest.mark.parametrize("zarr_format", ZARR_FORMATS) def test_group_info_complete(zarr_format: ZarrFormat) -> None: info = GroupInfo( _name="a", @@ -49,7 +45,6 @@ def test_group_info_complete(zarr_format: ZarrFormat) -> None: assert result == expected -@pytest.mark.parametrize("zarr_format", ZARR_FORMATS) def test_array_info(zarr_format: ZarrFormat) -> None: info = ArrayInfo( _zarr_format=zarr_format, @@ -74,7 +69,6 @@ def test_array_info(zarr_format: ZarrFormat) -> None: Codecs : [{{'endian': }}]""") -@pytest.mark.parametrize("zarr_format", ZARR_FORMATS) @pytest.mark.parametrize("bytes_things", [(1_000_000, "976.6K", 500_000, "500000", "2.0", 5)]) def test_array_info_complete( zarr_format: ZarrFormat, bytes_things: tuple[int, str, int, str, str, int] From c5b3588a3cab3e1169c8ede7b2b4805f7011b6d4 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 15 Nov 2024 20:21:38 +0100 Subject: [PATCH 38/38] add old open_with_mode_r test --- tests/test_api.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 1ebda74d17..dbe474f93a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,3 +1,4 @@ +import pathlib import warnings from typing import Literal @@ -228,7 +229,7 @@ def test_save_errors() -> None: ["local", "memory", "remote", pytest.param("zip", marks=pytest.mark.xfail)], indirect=True, ) -def test_open_with_mode_r(store: Store) -> None: +def test_open_store_with_mode_r(store: Store) -> None: # 'r' means read only (must exist) with pytest.raises(FileNotFoundError): zarr.open(store=store, mode="r") @@ -244,6 +245,20 @@ def test_open_with_mode_r(store: Store) -> None: z2[:] = 3 +def test_open_path_with_mode_r(tmp_path: pathlib.Path) -> None: + # 'r' means read only (must exist) + with pytest.raises(FileNotFoundError): + zarr.open(store=tmp_path, mode="r") + z1 = zarr.ones(store=tmp_path, shape=(3, 3)) + assert z1.fill_value == 1 + z2 = zarr.open(store=tmp_path, mode="r") + assert isinstance(z2, Array) + assert z2.fill_value == 1 + assert (z2[:] == 1).all() + with pytest.raises(ValueError): + z2[:] = 3 + + @pytest.mark.parametrize( "store", ["local", "memory", "remote", "zip"],