From a6ef792f5fbe580afe4bad860dbd9ee8fe4f1e22 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 18 Oct 2024 10:41:32 -0500 Subject: [PATCH 01/17] Added group info --- src/zarr/__init__.py | 2 ++ src/zarr/_info.py | 60 ++++++++++++++++++++++++++++++++++++++++++ src/zarr/core/array.py | 14 +++++++--- src/zarr/core/group.py | 41 ++++++++++++++++++++++++++--- tests/v3/test_group.py | 39 ++++++++++++++++++++------- tests/v3/test_info.py | 29 ++++++++++++++++++++ 6 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 src/zarr/_info.py create mode 100644 tests/v3/test_info.py diff --git a/src/zarr/__init__.py b/src/zarr/__init__.py index 51116a929e..4a16309ce5 100644 --- a/src/zarr/__init__.py +++ b/src/zarr/__init__.py @@ -1,3 +1,4 @@ +from zarr._info import GroupInfo from zarr._version import version as __version__ from zarr.api.synchronous import ( array, @@ -38,6 +39,7 @@ "AsyncArray", "AsyncGroup", "Group", + "GroupInfo", "__version__", "array", "config", diff --git a/src/zarr/_info.py b/src/zarr/_info.py new file mode 100644 index 0000000000..f9a5d0055f --- /dev/null +++ b/src/zarr/_info.py @@ -0,0 +1,60 @@ +import dataclasses +import textwrap +from typing import Literal + +import zarr.abc.store + +# Group +# Name : / +# Type : zarr.hierarchy.Group +# Read-only : False +# Store type : zarr.storage.MemoryStore +# No. members : 0 +# No. arrays : 0 +# No. groups : 0 + + +# In [19]: z.info +# Out[19]: +# Type : zarr.core.Array +# Data type : int32 +# Shape : (1000000,) +# Chunk shape : (100000,) +# Order : C +# Read-only : False +# Compressor : Blosc(cname='lz4', clevel=5, shuffle=SHUFFLE, blocksize=0) +# Store type : zarr.storage.KVStore +# No. bytes : 4000000 (3.8M) +# No. bytes stored : 320 +# Storage ratio : 12500.0 +# Chunks initialized : 0/10 + + +@dataclasses.dataclass(kw_only=True) +class GroupInfo: + name: str + type: Literal["Group"] = "Group" + read_only: bool + store_type: str + count_members: int | None = None + count_arrays: int | None = None + count_groups: int | None = None + + def __repr__(self) -> str: + template = textwrap.dedent("""\ + Name : {name} + Type : {type} + Read-only : {read_only} + Store type : {store_type}""") + + if self.count_members is not None: + template += ("\nNo. members : {count_members}") + if self.count_arrays is not None: + template += ("\nNo. arrays : {count_arrays}") + if self.count_groups is not None: + template += ("\nNo. groups : {count_groups}") + return template.format( + **dataclasses.asdict(self) + ) + + # def _repr_html_(self): ... diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index da477056ee..96ee60ae7e 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1145,7 +1145,11 @@ async def update_attributes(self, new_attributes: dict[str, JSON]) -> Self: def __repr__(self) -> str: return f"" - async def info(self) -> None: + @property + def info(self) -> ...: + ... + + async def info_full(self) -> None: raise NotImplementedError @@ -2818,10 +2822,12 @@ def update_attributes(self, new_attributes: dict[str, JSON]) -> Array: def __repr__(self) -> str: return f"" + @property def info(self) -> None: - return sync( - self._async_array.info(), - ) + return self._async_array.info + + def info_full(self) -> None: + ... def nchunks_initialized( diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index d797ed7370..d004f08007 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -13,6 +13,7 @@ import numpy.typing as npt from typing_extensions import deprecated +from zarr._info import GroupInfo import zarr.api.asynchronous as async_api from zarr.abc.metadata import Metadata from zarr.abc.store import Store, set_or_delete @@ -792,8 +793,37 @@ def attrs(self) -> dict[str, Any]: return self.metadata.attributes @property - def info(self) -> None: - raise NotImplementedError + def info(self) -> GroupInfo: + if self.metadata.consolidated_metadata: + members = list(self.metadata.consolidated_metadata.flattened_metadata.values()) + else: + members = None + return self._info(members=members) + + async def info_complete(self) -> GroupInfo: + members = [x[1].metadata async for x in self.members(max_depth=None)] + return self._info(members=members) + + def _info(self, members: list[ArrayV2Metadata | ArrayV3Metadata | GroupMetadata] | None = None) -> GroupInfo: + kwargs = {} + if members is not None: + kwargs["count_members"] = len(members) + count_arrays = 0 + count_groups = 0 + for member in members: + if isinstance(member, GroupMetadata): + count_groups += 1 + else: + count_arrays += 1 + kwargs["count_arrays"] = count_arrays + kwargs["count_groups"] = count_groups + + return GroupInfo( + name=self.store_path.path, + read_only=self.store_path.store.mode.readonly, + store_type=type(self.store_path.store).__name__, + **kwargs + ) @property def store(self) -> Store: @@ -1438,8 +1468,11 @@ def attrs(self) -> Attributes: return Attributes(self) @property - def info(self) -> None: - raise NotImplementedError + def info(self) -> GroupInfo: + return self._async_group.info + + def info_complete(self) -> GroupInfo: + return self._sync(self._async_group.info_complete()) @property def store(self) -> Store: diff --git a/tests/v3/test_group.py b/tests/v3/test_group.py index 2530f64ff4..f41549e82f 100644 --- a/tests/v3/test_group.py +++ b/tests/v3/test_group.py @@ -9,6 +9,7 @@ import pytest import zarr +from zarr._info import GroupInfo import zarr.api.asynchronous import zarr.api.synchronous from zarr import Array, AsyncArray, AsyncGroup, Group @@ -18,6 +19,7 @@ from zarr.core.sync import sync from zarr.errors import ContainsArrayError, ContainsGroupError from zarr.storage import LocalStore, MemoryStore, StorePath, ZipStore +import zarr.storage from zarr.storage.common import make_store_path from .conftest import parse_store @@ -768,15 +770,6 @@ async def test_asyncgroup_attrs(store: Store, zarr_format: ZarrFormat) -> None: assert agroup.attrs == agroup.metadata.attributes == attributes -async def test_asyncgroup_info(store: Store, zarr_format: ZarrFormat) -> None: - agroup = await AsyncGroup.from_store( # noqa: F841 - store, - zarr_format=zarr_format, - ) - pytest.xfail("Info is not implemented for metadata yet") - # assert agroup.info == agroup.metadata.info - - async def test_asyncgroup_open( store: Store, zarr_format: ZarrFormat, @@ -1322,6 +1315,34 @@ def test_from_dict_extra_fields(self): assert result == expected +class TestInfo: + def test_info(self): + store = zarr.storage.MemoryStore(mode="w") + A = zarr.group(store=store, path="A") + B = A.create_group(name="B") + + B.create_array(name="x", shape=(1,)) + B.create_array(name="y", shape=(2,)) + + result = A.info + expected = GroupInfo( + name="A", + read_only=False, + store_type="MemoryStore", + ) + assert result == expected + + result = A.info_complete() + expected = GroupInfo( + name="A", + read_only=False, + store_type="MemoryStore", + count_members=3, + count_arrays=2, + count_groups=1, + ) + assert result == expected + def test_update_attrs() -> None: # regression test for https://github.com/zarr-developers/zarr-python/issues/2328 root = Group.from_store( diff --git a/tests/v3/test_info.py b/tests/v3/test_info.py new file mode 100644 index 0000000000..1bf8fd7e58 --- /dev/null +++ b/tests/v3/test_info.py @@ -0,0 +1,29 @@ +import textwrap + +from zarr._info import GroupInfo + + +def test_group_info_repr() -> None: + info = GroupInfo(name="a", store_type="MemoryStore", read_only=False) + result = repr(info) + expected = textwrap.dedent("""\ + Name : a + Type : Group + Read-only : False + Store type : MemoryStore""") + assert result == expected + + +def test_group_info_complete() -> None: + info = GroupInfo(name="a", store_type="MemoryStore", read_only=False, count_arrays=10, count_groups=4, count_members=14) + result = repr(info) + expected = textwrap.dedent("""\ + Name : a + Type : Group + Read-only : False + Store type : MemoryStore + No. members : 14 + No. arrays : 10 + No. groups : 4""") + assert result == expected + From b94bff21c11635f59b0bbd19d3702ca0bab6dec4 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 18 Oct 2024 11:27:49 -0500 Subject: [PATCH 02/17] Basic array --- src/zarr/_info.py | 111 +++++++++++++++++++++++++++++++---------- src/zarr/core/array.py | 29 ++++++++--- src/zarr/core/group.py | 9 ++-- tests/v3/test_group.py | 5 +- tests/v3/test_info.py | 100 ++++++++++++++++++++++++++++++++++--- 5 files changed, 211 insertions(+), 43 deletions(-) diff --git a/src/zarr/_info.py b/src/zarr/_info.py index f9a5d0055f..e45f6bfd51 100644 --- a/src/zarr/_info.py +++ b/src/zarr/_info.py @@ -2,8 +2,6 @@ import textwrap from typing import Literal -import zarr.abc.store - # Group # Name : / # Type : zarr.hierarchy.Group @@ -14,26 +12,11 @@ # No. groups : 0 -# In [19]: z.info -# Out[19]: -# Type : zarr.core.Array -# Data type : int32 -# Shape : (1000000,) -# Chunk shape : (100000,) -# Order : C -# Read-only : False -# Compressor : Blosc(cname='lz4', clevel=5, shuffle=SHUFFLE, blocksize=0) -# Store type : zarr.storage.KVStore -# No. bytes : 4000000 (3.8M) -# No. bytes stored : 320 -# Storage ratio : 12500.0 -# Chunks initialized : 0/10 - - @dataclasses.dataclass(kw_only=True) class GroupInfo: name: str type: Literal["Group"] = "Group" + zarr_format: Literal[2, 3] read_only: bool store_type: str count_members: int | None = None @@ -44,17 +27,95 @@ def __repr__(self) -> str: template = textwrap.dedent("""\ Name : {name} Type : {type} + Zarr format : {zarr_format} Read-only : {read_only} Store type : {store_type}""") if self.count_members is not None: - template += ("\nNo. members : {count_members}") + template += "\nNo. members : {count_members}" if self.count_arrays is not None: - template += ("\nNo. arrays : {count_arrays}") + template += "\nNo. arrays : {count_arrays}" if self.count_groups is not None: - template += ("\nNo. groups : {count_groups}") - return template.format( - **dataclasses.asdict(self) - ) + template += "\nNo. groups : {count_groups}" + return template.format(**dataclasses.asdict(self)) + + +def human_readable_size(size: int) -> str: + if size < 2**10: + return f"{size}" + elif size < 2**20: + return f"{size / float(2**10):.1f}K" + elif size < 2**30: + return f"{size / float(2**20):.1f}M" + elif size < 2**40: + return f"{size / float(2**30):.1f}G" + elif size < 2**50: + return f"{size / float(2**40):.1f}T" + else: + return f"{size / float(2**50):.1f}P" + + +def byte_info(size: int) -> str: + if size < 2**10: + return size + else: + return f"{size} ({human_readable_size(size)})" + + +@dataclasses.dataclass(kw_only=True) +class ArrayInfo: + type: Literal["Array"] = "Array" + zarr_format: Literal[2, 3] + data_type: str + shape: tuple[int,] + chunk_shape: tuple[int,] + order: Literal["C", "F"] + read_only: bool + store_type: str + compressor: str | None = None + filters: list[str] | None = None + codecs: list[str] | None = None + count_bytes: int | None = None + count_bytes_stored: int | None = None + count_chunks_initialized: int | None = None + + def __repr__(self) -> str: + template = textwrap.dedent("""\ + Type : {type} + Zarr format : {zarr_format} + Data type : {data_type} + Shape : {shape} + Chunk shape : {chunk_shape} + Order : {order} + Read-only : {read_only} + Store type : {store_type}""") + + kwargs = dataclasses.asdict(self) + if self.compressor is not None: + template += "\nCompressor : {compressor}" + + if self.filters is not None: + template += "\nFilters : {filters}" + + if self.codecs is not None: + template += "\nCodecs : {codecs}" + + if self.count_bytes is not None: + template += "\nNo. bytes : {count_bytes}" + kwargs["count_bytes"] = byte_info(self.count_bytes) + + if self.count_bytes_stored is not None: + template += "\nNo. bytes stored : {count_bytes_stored}" + kwargs["count_stored"] = byte_info(self.count_bytes_stored) + + if ( + self.count_bytes is not None + and self.count_bytes_stored is not None + and self.count_bytes_stored > 0 + ): + template += "\nStorage ratio : {storage_ratio}" + kwargs["storage_ratio"] = f"{self.count_bytes / self.count_bytes_stored:.1f}" - # def _repr_html_(self): ... + if self.count_chunks_initialized is not None: + template += "\nChunks Initialized : {count_chunks_initialized}" + return template.format(**kwargs) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 96ee60ae7e..b200d86361 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -10,6 +10,7 @@ import numpy.typing as npt from zarr._compat import _deprecate_positional_args +from zarr._info import ArrayInfo from zarr.abc.store import Store, set_or_delete from zarr.codecs import _get_default_array_bytes_codec from zarr.codecs._v2 import V2Compressor, V2Filters @@ -1146,11 +1147,27 @@ def __repr__(self) -> str: return f"" @property - def info(self) -> ...: - ... + def info(self) -> ArrayInfo: + kwargs = {} + if self.metadata.zarr_format == 2: + kwargs["compressor"] = self.metadata.compressor + kwargs["filters"] = self.metadata.filters + else: + kwargs["codecs"] = self.metadata.codecs + + return ArrayInfo( + zarr_format=self.metadata.zarr_format, + data_type=self.metadata.data_type, + shape=self.shape, + chunk_shape=self.metadata.chunk_grid.chunk_shape, + order=self.order, + read_only=self.store_path.store.mode.readonly, + store_type=type(self.store_path.store).__name__, + **kwargs + ) async def info_full(self) -> None: - raise NotImplementedError + return ArrayInfo() @dataclass(frozen=True) @@ -2823,11 +2840,11 @@ def __repr__(self) -> str: return f"" @property - def info(self) -> None: + def info(self) -> ArrayInfo: return self._async_array.info - def info_full(self) -> None: - ... + def info_complete(self) -> ArrayInfo: + return sync(self._async_array.info_complete) def nchunks_initialized( diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index d004f08007..ba616074e6 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -13,8 +13,8 @@ import numpy.typing as npt from typing_extensions import deprecated -from zarr._info import GroupInfo import zarr.api.asynchronous as async_api +from zarr._info import GroupInfo from zarr.abc.metadata import Metadata from zarr.abc.store import Store, set_or_delete from zarr.core.array import Array, AsyncArray, _build_parents @@ -804,7 +804,9 @@ async def info_complete(self) -> GroupInfo: members = [x[1].metadata async for x in self.members(max_depth=None)] return self._info(members=members) - def _info(self, members: list[ArrayV2Metadata | ArrayV3Metadata | GroupMetadata] | None = None) -> GroupInfo: + def _info( + self, members: list[ArrayV2Metadata | ArrayV3Metadata | GroupMetadata] | None = None + ) -> GroupInfo: kwargs = {} if members is not None: kwargs["count_members"] = len(members) @@ -822,7 +824,8 @@ def _info(self, members: list[ArrayV2Metadata | ArrayV3Metadata | GroupMetadata] name=self.store_path.path, read_only=self.store_path.store.mode.readonly, store_type=type(self.store_path.store).__name__, - **kwargs + zarr_format=self.metadata.zarr_format, + **kwargs, ) @property diff --git a/tests/v3/test_group.py b/tests/v3/test_group.py index f41549e82f..fd785b79cc 100644 --- a/tests/v3/test_group.py +++ b/tests/v3/test_group.py @@ -9,17 +9,17 @@ import pytest import zarr -from zarr._info import GroupInfo import zarr.api.asynchronous import zarr.api.synchronous +import zarr.storage from zarr import Array, AsyncArray, AsyncGroup, Group +from zarr._info import GroupInfo 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 from zarr.errors import ContainsArrayError, ContainsGroupError from zarr.storage import LocalStore, MemoryStore, StorePath, ZipStore -import zarr.storage from zarr.storage.common import make_store_path from .conftest import parse_store @@ -1343,6 +1343,7 @@ def test_info(self): ) assert result == expected + def test_update_attrs() -> None: # regression test for https://github.com/zarr-developers/zarr-python/issues/2328 root = Group.from_store( diff --git a/tests/v3/test_info.py b/tests/v3/test_info.py index 1bf8fd7e58..45b3b5509d 100644 --- a/tests/v3/test_info.py +++ b/tests/v3/test_info.py @@ -1,25 +1,44 @@ import textwrap +from typing import Literal -from zarr._info import GroupInfo +import pytest +from zarr.core.common import ZarrFormat +from zarr._info import ArrayInfo, GroupInfo -def test_group_info_repr() -> None: - info = GroupInfo(name="a", store_type="MemoryStore", read_only=False) + +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) result = repr(info) - expected = textwrap.dedent("""\ + expected = textwrap.dedent(f"""\ Name : a Type : Group + Zarr format : {zarr_format} Read-only : False Store type : MemoryStore""") assert result == expected -def test_group_info_complete() -> None: - info = GroupInfo(name="a", store_type="MemoryStore", read_only=False, count_arrays=10, count_groups=4, count_members=14) +@pytest.mark.parametrize("zarr_format", ZARR_FORMATS) +def test_group_info_complete(zarr_format: ZarrFormat) -> None: + info = GroupInfo( + name="a", + store_type="MemoryStore", + zarr_format=zarr_format, + read_only=False, + count_arrays=10, + count_groups=4, + count_members=14, + ) result = repr(info) - expected = textwrap.dedent("""\ + expected = textwrap.dedent(f"""\ Name : a Type : Group + Zarr format : {zarr_format} Read-only : False Store type : MemoryStore No. members : 14 @@ -27,3 +46,70 @@ def test_group_info_complete() -> None: No. groups : 4""") assert result == expected + +@pytest.mark.parametrize("zarr_format", ZARR_FORMATS) +def test_array_info(zarr_format: ZarrFormat): + info = ArrayInfo( + zarr_format=zarr_format, + data_type="int32", + shape=(100, 100), + chunk_shape=(10, 100), + order="C", + read_only=True, + store_type="MemoryStore", + codecs=["BytesCodec(endian="], + ) + result = repr(info) + assert result == textwrap.dedent(f"""\ + Type : Array + Zarr format : {zarr_format} + Data type : int32 + Shape : (100, 100) + Chunk shape : (10, 100) + Order : C + Read-only : True + Store type : MemoryStore + Codecs : ["BytesCodec(endian="]""") + + +@pytest.mark.parametrize("zarr_format", ZARR_FORMATS) +@pytest.mark.parametrize("bytes_things", [(1_000_000, "976.6K", 500_000, "5", "2.0", 5)]) +def test_array_info_complete( + zarr_format: ZarrFormat, bytes_things: tuple[int, str, int, str, str, int] +): + ( + count_bytes, + count_bytes_formatted, + count_bytes_stored, + count_bytes_stored_formatted, + storage_ratio_formatted, + count_chunks_initialized, + ) = bytes_things + info = ArrayInfo( + zarr_format=zarr_format, + data_type="int32", + shape=(100, 100), + chunk_shape=(10, 100), + order="C", + read_only=True, + store_type="MemoryStore", + codecs=["BytesCodec(endian="], + count_bytes=count_bytes, + count_bytes_stored=count_bytes_stored, + count_chunks_initialized=count_chunks_initialized, + ) + result = repr(info) + assert result == textwrap.dedent(f"""\ + Type : Array + Zarr format : {zarr_format} + Data type : int32 + Shape : (100, 100) + Chunk shape : (10, 100) + Order : C + Read-only : True + Store type : MemoryStore + Codecs : ["BytesCodec(endian="] + No. bytes : 1000000 (976.6K) + No. bytes stored : 500000 + Storage ratio : 2.0 + Chunks Initialized : 5""") From 73ea1d234247e730fffb24e3e86056cef5889ce6 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 18 Oct 2024 12:43:33 -0500 Subject: [PATCH 03/17] fixup --- src/zarr/_info.py | 9 ++++++--- src/zarr/core/array.py | 34 +++++++++++++++++++++++----------- src/zarr/core/group.py | 3 ++- tests/v3/test_info.py | 16 +++++++--------- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/zarr/_info.py b/src/zarr/_info.py index e45f6bfd51..123e0d86a8 100644 --- a/src/zarr/_info.py +++ b/src/zarr/_info.py @@ -57,7 +57,7 @@ def human_readable_size(size: int) -> str: def byte_info(size: int) -> str: if size < 2**10: - return size + return str(size) else: return f"{size} ({human_readable_size(size)})" @@ -67,8 +67,8 @@ class ArrayInfo: type: Literal["Array"] = "Array" zarr_format: Literal[2, 3] data_type: str - shape: tuple[int,] - chunk_shape: tuple[int,] + shape: tuple[int, ...] + chunk_shape: tuple[int, ...] | None = None order: Literal["C", "F"] read_only: bool store_type: str @@ -91,6 +91,9 @@ def __repr__(self) -> str: Store type : {store_type}""") kwargs = dataclasses.asdict(self) + if self.chunk_shape is None: + # for non-regular chunk grids + kwargs["chunk_shape"] = "" if self.compressor is not None: template += "\nCompressor : {compressor}" diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index b200d86361..3c1e3d19d5 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1148,27 +1148,39 @@ def __repr__(self) -> str: @property def info(self) -> ArrayInfo: - kwargs = {} + return self._info() + + async def info_complete(self) -> ArrayInfo: + # do the I/O to get the extra + extra: dict[str, int] = {} + return self._info(extra=extra) + + def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: + kwargs: dict[str, Any] = {} if self.metadata.zarr_format == 2: - kwargs["compressor"] = self.metadata.compressor - kwargs["filters"] = self.metadata.filters + assert isinstance(self.metadata, ArrayV2Metadata) + if self.metadata.compressor is not None: + kwargs["compressor"] = str(self.metadata.compressor) + if self.metadata.filters is not None: + kwargs["filters"] = str(self.metadata.filters) + kwargs["data_type"] = str(self.metadata.dtype) + kwargs["chunks"] = self.metadata.chunks else: - kwargs["codecs"] = self.metadata.codecs + kwargs["codecs"] = str(self.metadata.codecs) + kwargs["data_type"] = str(self.metadata.data_type) + # just regular? + if isinstance(self.metadata.chunk_grid, RegularChunkGrid): + kwargs["chunks"] = self.metadata.chunk_grid.chunk_shape return ArrayInfo( zarr_format=self.metadata.zarr_format, - data_type=self.metadata.data_type, shape=self.shape, - chunk_shape=self.metadata.chunk_grid.chunk_shape, order=self.order, read_only=self.store_path.store.mode.readonly, store_type=type(self.store_path.store).__name__, - **kwargs + **kwargs, ) - async def info_full(self) -> None: - return ArrayInfo() - @dataclass(frozen=True) class Array: @@ -2844,7 +2856,7 @@ def info(self) -> ArrayInfo: return self._async_array.info def info_complete(self) -> ArrayInfo: - return sync(self._async_array.info_complete) + return sync(self._async_array.info_complete()) def nchunks_initialized( diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index ba616074e6..3124b2b820 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -825,7 +825,8 @@ def _info( read_only=self.store_path.store.mode.readonly, store_type=type(self.store_path.store).__name__, zarr_format=self.metadata.zarr_format, - **kwargs, + # maybe do a typeddict + **kwargs, # type: ignore[arg-type] ) @property diff --git a/tests/v3/test_info.py b/tests/v3/test_info.py index 45b3b5509d..7868670bb8 100644 --- a/tests/v3/test_info.py +++ b/tests/v3/test_info.py @@ -1,11 +1,9 @@ import textwrap -from typing import Literal import pytest -from zarr.core.common import ZarrFormat from zarr._info import ArrayInfo, GroupInfo - +from zarr.core.common import ZarrFormat ZARR_FORMATS = [2, 3] @@ -48,7 +46,7 @@ def test_group_info_complete(zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("zarr_format", ZARR_FORMATS) -def test_array_info(zarr_format: ZarrFormat): +def test_array_info(zarr_format: ZarrFormat) -> None: info = ArrayInfo( zarr_format=zarr_format, data_type="int32", @@ -73,10 +71,10 @@ def test_array_info(zarr_format: ZarrFormat): @pytest.mark.parametrize("zarr_format", ZARR_FORMATS) -@pytest.mark.parametrize("bytes_things", [(1_000_000, "976.6K", 500_000, "5", "2.0", 5)]) +@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] -): +) -> None: ( count_bytes, count_bytes_formatted, @@ -109,7 +107,7 @@ def test_array_info_complete( Read-only : True Store type : MemoryStore Codecs : ["BytesCodec(endian="] - No. bytes : 1000000 (976.6K) - No. bytes stored : 500000 - Storage ratio : 2.0 + No. bytes : {count_bytes} ({count_bytes_formatted}) + No. bytes stored : {count_bytes_stored_formatted} + Storage ratio : {storage_ratio_formatted} Chunks Initialized : 5""") From a3b797de6887cda12bb15ae3f039cd409e7f4eb4 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 18 Oct 2024 12:56:16 -0500 Subject: [PATCH 04/17] docs, tests --- src/zarr/_info.py | 37 ++++++++++++++++++++++++++++--------- tests/v3/test_info.py | 18 +++++++++++++++++- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/zarr/_info.py b/src/zarr/_info.py index 123e0d86a8..285d94fe1f 100644 --- a/src/zarr/_info.py +++ b/src/zarr/_info.py @@ -2,18 +2,37 @@ import textwrap from typing import Literal -# Group -# Name : / -# Type : zarr.hierarchy.Group -# Read-only : False -# Store type : zarr.storage.MemoryStore -# No. members : 0 -# No. arrays : 0 -# No. groups : 0 - @dataclasses.dataclass(kw_only=True) class GroupInfo: + """ + Information about a group. + + Parameters + ---------- + name : str + The path of the group within the Store + type : "Group" + zarr_format : {2, 3} + The zarr format of the Group. + read_only : bool + Whether the Group's access mode is read only. + store_type : str + The name of the Store class containing this group. + count_members : int, optional + The number of child members below this group. This + will be set when the Group has consolidated metadata + or when using :class:`Group.info_complete`. + count_arrays : int, optional + The number of child arrays below this group. This + will be set when the Group has consolidated metadata + or when using :class:`Group.info_complete`. + count_groups : int, optional + The number of child groups below this group. This + will be set when the Group has consolidated metadata + or when using :class:`Group.info_complete`. + """ + name: str type: Literal["Group"] = "Group" zarr_format: Literal[2, 3] diff --git a/tests/v3/test_info.py b/tests/v3/test_info.py index 7868670bb8..99e442222c 100644 --- a/tests/v3/test_info.py +++ b/tests/v3/test_info.py @@ -2,7 +2,7 @@ import pytest -from zarr._info import ArrayInfo, GroupInfo +from zarr._info import ArrayInfo, GroupInfo, human_readable_size from zarr.core.common import ZarrFormat ZARR_FORMATS = [2, 3] @@ -111,3 +111,19 @@ def test_array_info_complete( No. bytes stored : {count_bytes_stored_formatted} Storage ratio : {storage_ratio_formatted} Chunks Initialized : 5""") + + +@pytest.mark.parametrize( + ("size", "expected"), + [ + (1, "1"), + (2**10, "1.0K"), + (2**20, "1.0M"), + (2**30, "1.0G"), + (2**40, "1.0T"), + (2**50, "1.0P"), + ], +) +def test_human_readable_size(size: int, expected: str) -> None: + result = human_readable_size(size) + assert result == expected From 30c4e6aac6f6e78de579f5b6b50f48856338c09e Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 18 Oct 2024 13:16:01 -0500 Subject: [PATCH 05/17] fixup --- src/zarr/_info.py | 2 +- src/zarr/core/array.py | 9 +++---- src/zarr/core/group.py | 55 ++++++++++++++++++++++++++++++++++++++++++ tests/v3/test_array.py | 32 ++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/zarr/_info.py b/src/zarr/_info.py index 285d94fe1f..ae323fc57f 100644 --- a/src/zarr/_info.py +++ b/src/zarr/_info.py @@ -93,7 +93,7 @@ class ArrayInfo: store_type: str compressor: str | None = None filters: list[str] | None = None - codecs: list[str] | None = None + codecs: str | None = None count_bytes: int | None = None count_bytes_stored: int | None = None count_chunks_initialized: int | None = None diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 3c1e3d19d5..439d35d089 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1151,9 +1151,8 @@ def info(self) -> ArrayInfo: return self._info() async def info_complete(self) -> ArrayInfo: - # do the I/O to get the extra - extra: dict[str, int] = {} - return self._info(extra=extra) + # TODO: get the size of the object from the store. + raise NotImplementedError def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: kwargs: dict[str, Any] = {} @@ -1164,13 +1163,13 @@ def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: if self.metadata.filters is not None: kwargs["filters"] = str(self.metadata.filters) kwargs["data_type"] = str(self.metadata.dtype) - kwargs["chunks"] = self.metadata.chunks + kwargs["chunk_shape"] = self.metadata.chunks else: kwargs["codecs"] = str(self.metadata.codecs) kwargs["data_type"] = str(self.metadata.data_type) # just regular? if isinstance(self.metadata.chunk_grid, RegularChunkGrid): - kwargs["chunks"] = self.metadata.chunk_grid.chunk_shape + kwargs["chunk_shape"] = self.metadata.chunk_grid.chunk_shape return ArrayInfo( zarr_format=self.metadata.zarr_format, diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index 3124b2b820..cb06ec09ea 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -794,6 +794,20 @@ def attrs(self) -> dict[str, Any]: @property def info(self) -> GroupInfo: + """ + Return the statically known information for a group. + + Returns + ------- + GroupInfo + + See Also + -------- + AsyncGroup.info_complete + All information about a group, including dynamic information + like the children members. + """ + if self.metadata.consolidated_metadata: members = list(self.metadata.consolidated_metadata.flattened_metadata.values()) else: @@ -801,6 +815,20 @@ def info(self) -> GroupInfo: return self._info(members=members) async def info_complete(self) -> GroupInfo: + """ + Return information for a group. + + If this group doesn't contain consolidated metadata then + this will need to read from the backing Store. + + Returns + ------- + GroupInfo + + See Also + -------- + AsyncGroup.info + """ members = [x[1].metadata async for x in self.members(max_depth=None)] return self._info(members=members) @@ -1473,9 +1501,36 @@ def attrs(self) -> Attributes: @property def info(self) -> GroupInfo: + """ + Return the statically known information for a group. + + Returns + ------- + GroupInfo + + See Also + -------- + Group.info_complete + All information about a group, including dynamic information + like the children members. + """ return self._async_group.info def info_complete(self) -> GroupInfo: + """ + Return information for a group. + + If this group doesn't contain consolidated metadata then + this will need to read from the backing Store. + + Returns + ------- + GroupInfo + + See Also + -------- + Group.info + """ return self._sync(self._async_group.info_complete()) @property diff --git a/tests/v3/test_array.py b/tests/v3/test_array.py index 829a04d304..e54589d029 100644 --- a/tests/v3/test_array.py +++ b/tests/v3/test_array.py @@ -7,6 +7,7 @@ import zarr.api.asynchronous from zarr import Array, AsyncArray, Group +from zarr._info import ArrayInfo from zarr.codecs import BytesCodec, VLenBytesCodec from zarr.core.array import chunks_initialized from zarr.core.buffer.cpu import NDBuffer @@ -417,3 +418,34 @@ def test_update_attrs(zarr_format: int) -> None: arr2 = zarr.open_array(store=store, zarr_format=zarr_format) assert arr2.attrs["foo"] == "bar" + + +class TestInfo: + def test_info_v2(self) -> None: + arr = zarr.create(shape=(4, 4), chunks=(2, 2), zarr_format=2) + result = arr.info + expected = ArrayInfo( + zarr_format=2, + data_type="float64", + shape=(4, 4), + chunk_shape=(2, 2), + order="C", + read_only=False, + store_type="MemoryStore", + ) + assert result == expected + + def test_info_v3(self) -> None: + arr = zarr.create(shape=(4, 4), chunks=(2, 2), zarr_format=3) + result = arr.info + expected = ArrayInfo( + zarr_format=3, + data_type="DataType.float64", + shape=(4, 4), + chunk_shape=(2, 2), + order="C", + read_only=False, + store_type="MemoryStore", + codecs="[BytesCodec(endian=)]", + ) + assert result == expected From 615c025907082bcd0f8f5e4c4182cd9894a8d833 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 20 Oct 2024 11:11:03 -0500 Subject: [PATCH 06/17] fixup --- src/zarr/__init__.py | 2 +- src/zarr/{ => core}/_info.py | 2 +- src/zarr/core/array.py | 2 +- src/zarr/core/group.py | 2 +- tests/test_array.py | 2 +- tests/test_group.py | 2 +- tests/test_info.py | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) rename src/zarr/{ => core}/_info.py (99%) diff --git a/src/zarr/__init__.py b/src/zarr/__init__.py index 4a16309ce5..910fcfc9c3 100644 --- a/src/zarr/__init__.py +++ b/src/zarr/__init__.py @@ -1,4 +1,3 @@ -from zarr._info import GroupInfo from zarr._version import version as __version__ from zarr.api.synchronous import ( array, @@ -27,6 +26,7 @@ zeros, zeros_like, ) +from zarr.core._info import GroupInfo from zarr.core.array import Array, AsyncArray from zarr.core.config import config from zarr.core.group import AsyncGroup, Group diff --git a/src/zarr/_info.py b/src/zarr/core/_info.py similarity index 99% rename from src/zarr/_info.py rename to src/zarr/core/_info.py index ae323fc57f..a77963e574 100644 --- a/src/zarr/_info.py +++ b/src/zarr/core/_info.py @@ -8,7 +8,7 @@ class GroupInfo: """ Information about a group. - Parameters + Attributes ---------- name : str The path of the group within the Store diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index b9de3c4305..5a282bcf16 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -11,10 +11,10 @@ import numpy.typing as npt from zarr._compat import _deprecate_positional_args -from zarr._info import ArrayInfo from zarr.abc.store import Store, set_or_delete from zarr.codecs import _get_default_array_bytes_codec from zarr.codecs._v2 import V2Compressor, V2Filters +from zarr.core._info import ArrayInfo from zarr.core.attributes import Attributes from zarr.core.buffer import ( BufferPrototype, diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index d19ccc889c..30b6120083 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -15,9 +15,9 @@ import zarr.api.asynchronous as async_api from zarr._compat import _deprecate_positional_args -from zarr._info import GroupInfo from zarr.abc.metadata import Metadata from zarr.abc.store import Store, set_or_delete +from zarr.core._info import GroupInfo from zarr.core.array import Array, AsyncArray, _build_parents from zarr.core.attributes import Attributes from zarr.core.buffer import default_buffer_prototype diff --git a/tests/test_array.py b/tests/test_array.py index e54589d029..a0a8352d0b 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -7,8 +7,8 @@ import zarr.api.asynchronous from zarr import Array, AsyncArray, Group -from zarr._info import ArrayInfo from zarr.codecs import BytesCodec, VLenBytesCodec +from zarr.core._info import ArrayInfo from zarr.core.array import chunks_initialized from zarr.core.buffer.cpu import NDBuffer from zarr.core.common import JSON, ZarrFormat diff --git a/tests/test_group.py b/tests/test_group.py index cc78f7af03..259a59b100 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -14,8 +14,8 @@ import zarr.api.synchronous import zarr.storage from zarr import Array, AsyncArray, AsyncGroup, Group -from zarr._info import GroupInfo from zarr.abc.store import Store +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 diff --git a/tests/test_info.py b/tests/test_info.py index 99e442222c..ed14a338d3 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -2,7 +2,7 @@ import pytest -from zarr._info import ArrayInfo, GroupInfo, human_readable_size +from zarr.core._info import ArrayInfo, GroupInfo, human_readable_size from zarr.core.common import ZarrFormat ZARR_FORMATS = [2, 3] @@ -55,7 +55,7 @@ def test_array_info(zarr_format: ZarrFormat) -> None: order="C", read_only=True, store_type="MemoryStore", - codecs=["BytesCodec(endian="], + codecs="[\"BytesCodec(endian=\"]", ) result = repr(info) assert result == textwrap.dedent(f"""\ @@ -91,7 +91,7 @@ def test_array_info_complete( order="C", read_only=True, store_type="MemoryStore", - codecs=["BytesCodec(endian="], + codecs="[\"BytesCodec(endian=\"]", count_bytes=count_bytes, count_bytes_stored=count_bytes_stored, count_chunks_initialized=count_chunks_initialized, From 125129b66b000d9c7f9f8cc3409f0d393e26823c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 20 Oct 2024 11:13:19 -0500 Subject: [PATCH 07/17] docstringsx --- src/zarr/core/array.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 5a282bcf16..4b567205fa 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1147,6 +1147,19 @@ def __repr__(self) -> str: @property def info(self) -> ArrayInfo: + """ + Return the statically known information for an array. + + Returns + ------- + ArrayInfo + + See Also + -------- + AsyncArray.info_complete + All information about a group, including dynamic information + like the number of bytes and chunks written. + """ return self._info() async def info_complete(self) -> ArrayInfo: @@ -2841,6 +2854,19 @@ def __repr__(self) -> str: @property def info(self) -> ArrayInfo: + """ + Return the statically known information for an array. + + Returns + ------- + ArrayInfo + + See Also + -------- + Array.info_complete + All information about a group, including dynamic information + like the number of bytes and chunks written. + """ return self._async_array.info def info_complete(self) -> ArrayInfo: From 5599da39acdf049cf476c204a1602c02ae9c05a0 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 24 Oct 2024 11:14:12 -0500 Subject: [PATCH 08/17] fixup --- tests/test_group.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_group.py b/tests/test_group.py index 259a59b100..08f0797254 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -1334,6 +1334,7 @@ def test_info(self): name="A", read_only=False, store_type="MemoryStore", + zarr_format=3, ) assert result == expected @@ -1342,6 +1343,7 @@ def test_info(self): name="A", read_only=False, store_type="MemoryStore", + zarr_format=3, count_members=3, count_arrays=2, count_groups=1, From d96c2020038a5339ee17af6755787c029939a073 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 24 Oct 2024 11:17:05 -0500 Subject: [PATCH 09/17] chunk info --- src/zarr/core/array.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 5dfc5d38df..184c70b9db 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1235,8 +1235,12 @@ def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: kwargs["codecs"] = str(self.metadata.codecs) kwargs["data_type"] = str(self.metadata.data_type) # just regular? - if isinstance(self.metadata.chunk_grid, RegularChunkGrid): - kwargs["chunk_shape"] = self.metadata.chunk_grid.chunk_shape + chunk_grid = self.metadata.chunk_grid + if isinstance(chunk_grid, RegularChunkGrid): + kwargs["chunk_shape"] = chunk_grid.chunk_shape + else: + msg = f"'info' is not yet implemented for chunk grids of type {type(self.metadata.chunk_grid)}" + raise NotImplementedError(msg) return ArrayInfo( zarr_format=self.metadata.zarr_format, From 911805631ac2c736c038df7095e11e1a9647c4f5 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 24 Oct 2024 11:24:17 -0500 Subject: [PATCH 10/17] fixup --- src/zarr/core/array.py | 12 ++++++++++-- tests/test_array.py | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 184c70b9db..80e5c9d8da 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1219,6 +1219,12 @@ def info(self) -> ArrayInfo: async def info_complete(self) -> ArrayInfo: # TODO: get the size of the object from the store. + extra = { + "count_chunks_initialized": self.nchunks_initialized, # this should be async? + # count_bytes_stored isn't yet implemented. + } + return self._info(extra=extra) + raise NotImplementedError def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: @@ -1239,8 +1245,9 @@ def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: if isinstance(chunk_grid, RegularChunkGrid): kwargs["chunk_shape"] = chunk_grid.chunk_shape else: - msg = f"'info' is not yet implemented for chunk grids of type {type(self.metadata.chunk_grid)}" - raise NotImplementedError(msg) + raise NotImplementedError( + "'info' is not yet implemented for chunk grids of type {type(self.metadata.chunk_grid)}" + ) return ArrayInfo( zarr_format=self.metadata.zarr_format, @@ -1248,6 +1255,7 @@ def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: order=self.order, read_only=self.store_path.store.mode.readonly, store_type=type(self.store_path.store).__name__, + count_bytes=self.dtype.itemsize * self.size, **kwargs, ) diff --git a/tests/test_array.py b/tests/test_array.py index c87c3a048a..71a15f2bcd 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -435,6 +435,7 @@ def test_info_v2(self) -> None: order="C", read_only=False, store_type="MemoryStore", + count_bytes=128, ) assert result == expected @@ -450,6 +451,7 @@ def test_info_v3(self) -> None: read_only=False, store_type="MemoryStore", codecs="[BytesCodec(endian=)]", + count_bytes=128, ) assert result == expected From 0aef24056c21ae9cb00ff986d5f7c822eae8f456 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 30 Oct 2024 09:09:24 -0500 Subject: [PATCH 11/17] wip - split to chunks_initialized --- src/zarr/core/array.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 80e5c9d8da..50a45d1319 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -2971,10 +2971,43 @@ def info(self) -> ArrayInfo: Array.info_complete All information about a group, including dynamic information like the number of bytes and chunks written. + + Examples + -------- + >>> arr = zarr.create(shape=(10,), chunks=(2,), dtype="float32") + >>> arr.info + Type : Array + Zarr format : 3 + Data type : DataType.float32 + Shape : (10,) + Chunk shape : (2,) + Order : C + Read-only : False + Store type : MemoryStore + Codecs : [BytesCodec(endian=)] + No. bytes : 40 """ return self._async_array.info def info_complete(self) -> ArrayInfo: + """ + Returns all the information about an array, including information from the Store. + + In addition to the statically known information like ``name`` and ``zarr_format``, + this includes additional information like the size of the array in bytes and + the number of chunks written. + + Note that this method will need to read metadata from the store. + + Returns + ------- + ArrayInfo + + See Also + -------- + Array.info + The statically known subset of metadata about an array. + """ return sync(self._async_array.info_complete()) From cdb16729cdd808c06d9ef14a0115c723148f6bc6 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 8 Nov 2024 10:27:38 -0600 Subject: [PATCH 12/17] fixup --- src/zarr/__init__.py | 2 - src/zarr/core/_info.py | 131 ++++++++++++++++++++++------------------- src/zarr/core/array.py | 24 ++++---- src/zarr/core/group.py | 37 ++++++------ tests/test_array.py | 34 +++++------ tests/test_info.py | 56 +++++++++--------- 6 files changed, 147 insertions(+), 137 deletions(-) diff --git a/src/zarr/__init__.py b/src/zarr/__init__.py index 910fcfc9c3..51116a929e 100644 --- a/src/zarr/__init__.py +++ b/src/zarr/__init__.py @@ -26,7 +26,6 @@ zeros, zeros_like, ) -from zarr.core._info import GroupInfo from zarr.core.array import Array, AsyncArray from zarr.core.config import config from zarr.core.group import AsyncGroup, Group @@ -39,7 +38,6 @@ "AsyncArray", "AsyncGroup", "Group", - "GroupInfo", "__version__", "array", "config", diff --git a/src/zarr/core/_info.py b/src/zarr/core/_info.py index a77963e574..d07103fee1 100644 --- a/src/zarr/core/_info.py +++ b/src/zarr/core/_info.py @@ -33,29 +33,29 @@ class GroupInfo: or when using :class:`Group.info_complete`. """ - name: str - type: Literal["Group"] = "Group" - zarr_format: Literal[2, 3] - read_only: bool - store_type: str - count_members: int | None = None - count_arrays: int | None = None - count_groups: int | None = None + _name: str + _type: Literal["Group"] = "Group" + _zarr_format: Literal[2, 3] + _read_only: bool + _store_type: str + _count_members: int | None = None + _count_arrays: int | None = None + _count_groups: int | None = None def __repr__(self) -> str: template = textwrap.dedent("""\ - Name : {name} - Type : {type} - Zarr format : {zarr_format} - Read-only : {read_only} - Store type : {store_type}""") - - if self.count_members is not None: - template += "\nNo. members : {count_members}" - if self.count_arrays is not None: - template += "\nNo. arrays : {count_arrays}" - if self.count_groups is not None: - template += "\nNo. groups : {count_groups}" + Name : {_name} + Type : {_type} + Zarr format : {_zarr_format} + Read-only : {_read_only} + Store type : {_store_type}""") + + if self._count_members is not None: + template += "\nNo. members : {_count_members}" + if self._count_arrays is not None: + template += "\nNo. arrays : {_count_arrays}" + if self._count_groups is not None: + template += "\nNo. groups : {_count_groups}" return template.format(**dataclasses.asdict(self)) @@ -83,61 +83,68 @@ def byte_info(size: int) -> str: @dataclasses.dataclass(kw_only=True) class ArrayInfo: - type: Literal["Array"] = "Array" - zarr_format: Literal[2, 3] - data_type: str - shape: tuple[int, ...] - chunk_shape: tuple[int, ...] | None = None - order: Literal["C", "F"] - read_only: bool - store_type: str - compressor: str | None = None - filters: list[str] | None = None - codecs: str | None = None - count_bytes: int | None = None - count_bytes_stored: int | None = None - count_chunks_initialized: int | None = None + """ + Render the information for an array. + + Note that this method and its properties is not part of + Zarr's public API. + """ + + _type: Literal["Array"] = "Array" + _zarr_format: Literal[2, 3] + _data_type: str + _shape: tuple[int, ...] + _chunk_shape: tuple[int, ...] | None = None + _order: Literal["C", "F"] + _read_only: bool + _store_type: str + _compressor: str | None = None + _filters: list[str] | None = None + _codecs: str | None = None + _count_bytes: int | None = None + _count_bytes_stored: int | None = None + _count_chunks_initialized: int | None = None def __repr__(self) -> str: template = textwrap.dedent("""\ - Type : {type} - Zarr format : {zarr_format} - Data type : {data_type} - Shape : {shape} - Chunk shape : {chunk_shape} - Order : {order} - Read-only : {read_only} - Store type : {store_type}""") + Type : {_type} + Zarr format : {_zarr_format} + Data type : {_data_type} + Shape : {_shape} + Chunk shape : {_chunk_shape} + Order : {_order} + Read-only : {_read_only} + Store type : {_store_type}""") kwargs = dataclasses.asdict(self) - if self.chunk_shape is None: + if self._chunk_shape is None: # for non-regular chunk grids kwargs["chunk_shape"] = "" - if self.compressor is not None: - template += "\nCompressor : {compressor}" + if self._compressor is not None: + template += "\nCompressor : {_compressor}" - if self.filters is not None: - template += "\nFilters : {filters}" + if self._filters is not None: + template += "\nFilters : {_filters}" - if self.codecs is not None: - template += "\nCodecs : {codecs}" + if self._codecs is not None: + template += "\nCodecs : {_codecs}" - if self.count_bytes is not None: - template += "\nNo. bytes : {count_bytes}" - kwargs["count_bytes"] = byte_info(self.count_bytes) + if self._count_bytes is not None: + template += "\nNo. bytes : {_count_bytes}" + kwargs["_count_bytes"] = byte_info(self._count_bytes) - if self.count_bytes_stored is not None: - template += "\nNo. bytes stored : {count_bytes_stored}" - kwargs["count_stored"] = byte_info(self.count_bytes_stored) + if self._count_bytes_stored is not None: + template += "\nNo. bytes stored : {_count_bytes_stored}" + kwargs["_count_stored"] = byte_info(self._count_bytes_stored) if ( - self.count_bytes is not None - and self.count_bytes_stored is not None - and self.count_bytes_stored > 0 + self._count_bytes is not None + and self._count_bytes_stored is not None + and self._count_bytes_stored > 0 ): - template += "\nStorage ratio : {storage_ratio}" - kwargs["storage_ratio"] = f"{self.count_bytes / self.count_bytes_stored:.1f}" + template += "\nStorage ratio : {_storage_ratio}" + kwargs["_storage_ratio"] = f"{self._count_bytes / self._count_bytes_stored:.1f}" - if self.count_chunks_initialized is not None: - template += "\nChunks Initialized : {count_chunks_initialized}" + if self._count_chunks_initialized is not None: + template += "\nChunks Initialized : {_count_chunks_initialized}" return template.format(**kwargs) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 42f6b9402a..ab753bc8aa 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1334,7 +1334,7 @@ def __repr__(self) -> str: return f"" @property - def info(self) -> ArrayInfo: + def info(self) -> Any: """ Return the statically known information for an array. @@ -1350,17 +1350,17 @@ def info(self) -> ArrayInfo: """ return self._info() - async def info_complete(self) -> ArrayInfo: + async def info_complete(self) -> Any: # TODO: get the size of the object from the store. extra = { - "count_chunks_initialized": self.nchunks_initialized, # this should be async? + "count_chunks_initialized": await self.nchunks_initialized(), # count_bytes_stored isn't yet implemented. } return self._info(extra=extra) raise NotImplementedError - def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: + def _info(self, extra: dict[str, int] | None = None) -> Any: kwargs: dict[str, Any] = {} if self.metadata.zarr_format == 2: assert isinstance(self.metadata, ArrayV2Metadata) @@ -1383,12 +1383,12 @@ def _info(self, extra: dict[str, int] | None = None) -> ArrayInfo: ) return ArrayInfo( - zarr_format=self.metadata.zarr_format, - shape=self.shape, - order=self.order, - read_only=self.store_path.store.mode.readonly, - store_type=type(self.store_path.store).__name__, - count_bytes=self.dtype.itemsize * self.size, + _zarr_format=self.metadata.zarr_format, + _shape=self.shape, + _order=self.order, + _read_only=self.store_path.store.mode.readonly, + _store_type=type(self.store_path.store).__name__, + _count_bytes=self.dtype.itemsize * self.size, **kwargs, ) @@ -3157,7 +3157,7 @@ def __repr__(self) -> str: return f"" @property - def info(self) -> ArrayInfo: + def info(self) -> Any: """ Return the statically known information for an array. @@ -3188,7 +3188,7 @@ def info(self) -> ArrayInfo: """ return self._async_array.info - def info_complete(self) -> ArrayInfo: + def info_complete(self) -> Any: """ Returns all the information about an array, including information from the Store. diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index 3c50299d49..160306049d 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -806,9 +806,12 @@ def attrs(self) -> dict[str, Any]: return self.metadata.attributes @property - def info(self) -> GroupInfo: + def info(self) -> Any: """ - Return the statically known information for a group. + Return a visual representation of the statically known information about a group. + + Note that this doesn't include dynamic information, like the number of child + Groups or Arrays. Returns ------- @@ -818,7 +821,6 @@ def info(self) -> GroupInfo: -------- AsyncGroup.info_complete All information about a group, including dynamic information - like the children members. """ if self.metadata.consolidated_metadata: @@ -827,12 +829,13 @@ def info(self) -> GroupInfo: members = None return self._info(members=members) - async def info_complete(self) -> GroupInfo: + async def info_complete(self) -> Any: """ - Return information for a group. + Return all the information for a group. - If this group doesn't contain consolidated metadata then - this will need to read from the backing Store. + This includes dynamic information like the number + of child Groups or Arrays. If this group doesn't contain consolidated + metadata then this will need to read from the backing Store. Returns ------- @@ -847,10 +850,10 @@ async def info_complete(self) -> GroupInfo: def _info( self, members: list[ArrayV2Metadata | ArrayV3Metadata | GroupMetadata] | None = None - ) -> GroupInfo: + ) -> Any: kwargs = {} if members is not None: - kwargs["count_members"] = len(members) + kwargs["_count_members"] = len(members) count_arrays = 0 count_groups = 0 for member in members: @@ -858,14 +861,14 @@ def _info( count_groups += 1 else: count_arrays += 1 - kwargs["count_arrays"] = count_arrays - kwargs["count_groups"] = count_groups + kwargs["_count_arrays"] = count_arrays + kwargs["_count_groups"] = count_groups return GroupInfo( - name=self.store_path.path, - read_only=self.store_path.store.mode.readonly, - store_type=type(self.store_path.store).__name__, - zarr_format=self.metadata.zarr_format, + _name=self.store_path.path, + _read_only=self.store_path.store.mode.readonly, + _store_type=type(self.store_path.store).__name__, + _zarr_format=self.metadata.zarr_format, # maybe do a typeddict **kwargs, # type: ignore[arg-type] ) @@ -1516,7 +1519,7 @@ def attrs(self) -> Attributes: return Attributes(self) @property - def info(self) -> GroupInfo: + def info(self) -> Any: """ Return the statically known information for a group. @@ -1532,7 +1535,7 @@ def info(self) -> GroupInfo: """ return self._async_group.info - def info_complete(self) -> GroupInfo: + def info_complete(self) -> Any: """ Return information for a group. diff --git a/tests/test_array.py b/tests/test_array.py index d3593c30a0..42ad32aa0a 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -424,14 +424,14 @@ def test_info_v2(self) -> None: arr = zarr.create(shape=(4, 4), chunks=(2, 2), zarr_format=2) result = arr.info expected = ArrayInfo( - zarr_format=2, - data_type="float64", - shape=(4, 4), - chunk_shape=(2, 2), - order="C", - read_only=False, - store_type="MemoryStore", - count_bytes=128, + _zarr_format=2, + _data_type="float64", + _shape=(4, 4), + _chunk_shape=(2, 2), + _order="C", + _read_only=False, + _store_type="MemoryStore", + _count_bytes=128, ) assert result == expected @@ -439,15 +439,15 @@ def test_info_v3(self) -> None: arr = zarr.create(shape=(4, 4), chunks=(2, 2), zarr_format=3) result = arr.info expected = ArrayInfo( - zarr_format=3, - data_type="DataType.float64", - shape=(4, 4), - chunk_shape=(2, 2), - order="C", - read_only=False, - store_type="MemoryStore", - codecs="[BytesCodec(endian=)]", - count_bytes=128, + _zarr_format=3, + _data_type="DataType.float64", + _shape=(4, 4), + _chunk_shape=(2, 2), + _order="C", + _read_only=False, + _store_type="MemoryStore", + _codecs="[BytesCodec(endian=)]", + _count_bytes=128, ) assert result == expected diff --git a/tests/test_info.py b/tests/test_info.py index ed14a338d3..d6cb39339b 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -10,7 +10,9 @@ @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) + info = GroupInfo( + _name="a", _store_type="MemoryStore", _read_only=False, _zarr_format=zarr_format + ) result = repr(info) expected = textwrap.dedent(f"""\ Name : a @@ -24,13 +26,13 @@ def test_group_info_repr(zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("zarr_format", ZARR_FORMATS) def test_group_info_complete(zarr_format: ZarrFormat) -> None: info = GroupInfo( - name="a", - store_type="MemoryStore", - zarr_format=zarr_format, - read_only=False, - count_arrays=10, - count_groups=4, - count_members=14, + _name="a", + _store_type="MemoryStore", + _zarr_format=zarr_format, + _read_only=False, + _count_arrays=10, + _count_groups=4, + _count_members=14, ) result = repr(info) expected = textwrap.dedent(f"""\ @@ -48,14 +50,14 @@ def test_group_info_complete(zarr_format: ZarrFormat) -> None: @pytest.mark.parametrize("zarr_format", ZARR_FORMATS) def test_array_info(zarr_format: ZarrFormat) -> None: info = ArrayInfo( - zarr_format=zarr_format, - data_type="int32", - shape=(100, 100), - chunk_shape=(10, 100), - order="C", - read_only=True, - store_type="MemoryStore", - codecs="[\"BytesCodec(endian=\"]", + _zarr_format=zarr_format, + _data_type="int32", + _shape=(100, 100), + _chunk_shape=(10, 100), + _order="C", + _read_only=True, + _store_type="MemoryStore", + _codecs="[\"BytesCodec(endian=\"]", ) result = repr(info) assert result == textwrap.dedent(f"""\ @@ -84,17 +86,17 @@ def test_array_info_complete( count_chunks_initialized, ) = bytes_things info = ArrayInfo( - zarr_format=zarr_format, - data_type="int32", - shape=(100, 100), - chunk_shape=(10, 100), - order="C", - read_only=True, - store_type="MemoryStore", - codecs="[\"BytesCodec(endian=\"]", - count_bytes=count_bytes, - count_bytes_stored=count_bytes_stored, - count_chunks_initialized=count_chunks_initialized, + _zarr_format=zarr_format, + _data_type="int32", + _shape=(100, 100), + _chunk_shape=(10, 100), + _order="C", + _read_only=True, + _store_type="MemoryStore", + _codecs="[\"BytesCodec(endian=\"]", + _count_bytes=count_bytes, + _count_bytes_stored=count_bytes_stored, + _count_chunks_initialized=count_chunks_initialized, ) result = repr(info) assert result == textwrap.dedent(f"""\ From 035f53aabf4ba024dee593bec0090c3d1efb4839 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 8 Nov 2024 10:39:10 -0600 Subject: [PATCH 13/17] fixup --- src/zarr/core/_info.py | 16 +++++++++++----- src/zarr/core/array.py | 14 +++++++------- tests/test_array.py | 7 ++++--- tests/test_group.py | 22 +++++++++++----------- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/zarr/core/_info.py b/src/zarr/core/_info.py index d07103fee1..310b60c58d 100644 --- a/src/zarr/core/_info.py +++ b/src/zarr/core/_info.py @@ -1,6 +1,12 @@ import dataclasses import textwrap -from typing import Literal +from typing import Any, Literal + +import numcodecs.abc +import numpy as np + +from zarr.abc.codec import Codec +from zarr.core.metadata.v3 import DataType @dataclasses.dataclass(kw_only=True) @@ -92,15 +98,15 @@ class ArrayInfo: _type: Literal["Array"] = "Array" _zarr_format: Literal[2, 3] - _data_type: str + _data_type: np.dtype[Any] | DataType _shape: tuple[int, ...] _chunk_shape: tuple[int, ...] | None = None _order: Literal["C", "F"] _read_only: bool _store_type: str - _compressor: str | None = None - _filters: list[str] | None = None - _codecs: str | None = None + _compressor: numcodecs.abc.Codec | None = None + _filters: tuple[numcodecs.abc.Codec, ...] | None = None + _codecs: tuple[Codec, ...] | None = None _count_bytes: int | None = None _count_bytes_stored: int | None = None _count_chunks_initialized: int | None = None diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index ab753bc8aa..1808a86302 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -1365,18 +1365,18 @@ def _info(self, extra: dict[str, int] | None = None) -> Any: if self.metadata.zarr_format == 2: assert isinstance(self.metadata, ArrayV2Metadata) if self.metadata.compressor is not None: - kwargs["compressor"] = str(self.metadata.compressor) + kwargs["_compressor"] = self.metadata.compressor if self.metadata.filters is not None: - kwargs["filters"] = str(self.metadata.filters) - kwargs["data_type"] = str(self.metadata.dtype) - kwargs["chunk_shape"] = self.metadata.chunks + kwargs["_filters"] = self.metadata.filters + kwargs["_data_type"] = self.metadata.dtype + kwargs["_chunk_shape"] = self.metadata.chunks else: - kwargs["codecs"] = str(self.metadata.codecs) - kwargs["data_type"] = str(self.metadata.data_type) + kwargs["_codecs"] = self.metadata.codecs + kwargs["_data_type"] = self.metadata.data_type # just regular? chunk_grid = self.metadata.chunk_grid if isinstance(chunk_grid, RegularChunkGrid): - kwargs["chunk_shape"] = chunk_grid.chunk_shape + kwargs["_chunk_shape"] = chunk_grid.chunk_shape else: raise NotImplementedError( "'info' is not yet implemented for chunk grids of type {type(self.metadata.chunk_grid)}" diff --git a/tests/test_array.py b/tests/test_array.py index 42ad32aa0a..d18137d623 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -17,6 +17,7 @@ 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 @@ -425,7 +426,7 @@ def test_info_v2(self) -> None: result = arr.info expected = ArrayInfo( _zarr_format=2, - _data_type="float64", + _data_type=np.dtype("float64"), _shape=(4, 4), _chunk_shape=(2, 2), _order="C", @@ -440,13 +441,13 @@ def test_info_v3(self) -> None: result = arr.info expected = ArrayInfo( _zarr_format=3, - _data_type="DataType.float64", + _data_type=DataType.parse("float64"), _shape=(4, 4), _chunk_shape=(2, 2), _order="C", _read_only=False, _store_type="MemoryStore", - _codecs="[BytesCodec(endian=)]", + _codecs=(BytesCodec(),), _count_bytes=128, ) assert result == expected diff --git a/tests/test_group.py b/tests/test_group.py index d537f4ef97..6e185097fd 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -1356,22 +1356,22 @@ def test_info(self): result = A.info expected = GroupInfo( - name="A", - read_only=False, - store_type="MemoryStore", - zarr_format=3, + _name="A", + _read_only=False, + _store_type="MemoryStore", + _zarr_format=3, ) assert result == expected result = A.info_complete() expected = GroupInfo( - name="A", - read_only=False, - store_type="MemoryStore", - zarr_format=3, - count_members=3, - count_arrays=2, - count_groups=1, + _name="A", + _read_only=False, + _store_type="MemoryStore", + _zarr_format=3, + _count_members=3, + _count_arrays=2, + _count_groups=1, ) assert result == expected From 447dbe55f9bf21a51b769ea53eb13e832bfd4280 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 8 Nov 2024 10:41:08 -0600 Subject: [PATCH 14/17] update docs --- src/zarr/core/_info.py | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/zarr/core/_info.py b/src/zarr/core/_info.py index 310b60c58d..b8abed6274 100644 --- a/src/zarr/core/_info.py +++ b/src/zarr/core/_info.py @@ -12,31 +12,10 @@ @dataclasses.dataclass(kw_only=True) class GroupInfo: """ - Information about a group. - - Attributes - ---------- - name : str - The path of the group within the Store - type : "Group" - zarr_format : {2, 3} - The zarr format of the Group. - read_only : bool - Whether the Group's access mode is read only. - store_type : str - The name of the Store class containing this group. - count_members : int, optional - The number of child members below this group. This - will be set when the Group has consolidated metadata - or when using :class:`Group.info_complete`. - count_arrays : int, optional - The number of child arrays below this group. This - will be set when the Group has consolidated metadata - or when using :class:`Group.info_complete`. - count_groups : int, optional - The number of child groups below this group. This - will be set when the Group has consolidated metadata - or when using :class:`Group.info_complete`. + Visual summary for a Group. + + Note that this method and its properties is not part of + Zarr's public API. """ _name: str @@ -90,7 +69,7 @@ def byte_info(size: int) -> str: @dataclasses.dataclass(kw_only=True) class ArrayInfo: """ - Render the information for an array. + Visual summary for an Array. Note that this method and its properties is not part of Zarr's public API. From 2811215d4a203cdde70fead5ae0bc41e7f2697c4 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 10 Nov 2024 14:29:17 -0600 Subject: [PATCH 15/17] fixup test --- src/zarr/core/_info.py | 2 +- tests/test_array.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zarr/core/_info.py b/src/zarr/core/_info.py index b8abed6274..4708967390 100644 --- a/src/zarr/core/_info.py +++ b/src/zarr/core/_info.py @@ -85,7 +85,7 @@ class ArrayInfo: _store_type: str _compressor: numcodecs.abc.Codec | None = None _filters: tuple[numcodecs.abc.Codec, ...] | None = None - _codecs: tuple[Codec, ...] | None = None + _codecs: list[Codec] | None = None _count_bytes: int | None = None _count_bytes_stored: int | None = None _count_chunks_initialized: int | None = None diff --git a/tests/test_array.py b/tests/test_array.py index d18137d623..5f73f2f44e 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -447,7 +447,7 @@ def test_info_v3(self) -> None: _order="C", _read_only=False, _store_type="MemoryStore", - _codecs=(BytesCodec(),), + _codecs=[BytesCodec()], _count_bytes=128, ) assert result == expected From 2d0bdd72bc1fc60f5ac7d24f0336d558485c4322 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 11 Nov 2024 08:34:58 -0600 Subject: [PATCH 16/17] lint --- tests/test_info.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_info.py b/tests/test_info.py index d6cb39339b..b9af070b28 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -1,7 +1,9 @@ import textwrap +import numpy as np import pytest +from zarr.codecs.bytes import BytesCodec from zarr.core._info import ArrayInfo, GroupInfo, human_readable_size from zarr.core.common import ZarrFormat @@ -51,13 +53,13 @@ def test_group_info_complete(zarr_format: ZarrFormat) -> None: def test_array_info(zarr_format: ZarrFormat) -> None: info = ArrayInfo( _zarr_format=zarr_format, - _data_type="int32", + _data_type=np.dtype("int32"), _shape=(100, 100), _chunk_shape=(10, 100), _order="C", _read_only=True, _store_type="MemoryStore", - _codecs="[\"BytesCodec(endian=\"]", + _codecs=[BytesCodec()], ) result = repr(info) assert result == textwrap.dedent(f"""\ @@ -87,13 +89,13 @@ def test_array_info_complete( ) = bytes_things info = ArrayInfo( _zarr_format=zarr_format, - _data_type="int32", + _data_type=np.dtype("int32"), _shape=(100, 100), _chunk_shape=(10, 100), _order="C", _read_only=True, _store_type="MemoryStore", - _codecs="[\"BytesCodec(endian=\"]", + _codecs=[BytesCodec()], _count_bytes=count_bytes, _count_bytes_stored=count_bytes_stored, _count_chunks_initialized=count_chunks_initialized, From f30838cb2ce4c3afa02959014a1242a565afea32 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 11 Nov 2024 11:32:47 -0600 Subject: [PATCH 17/17] fixup test --- tests/test_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_info.py b/tests/test_info.py index b9af070b28..5d9264aa13 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -71,7 +71,7 @@ def test_array_info(zarr_format: ZarrFormat) -> None: Order : C Read-only : True Store type : MemoryStore - Codecs : ["BytesCodec(endian="]""") + Codecs : [{{'endian': }}]""") @pytest.mark.parametrize("zarr_format", ZARR_FORMATS) @@ -110,7 +110,7 @@ def test_array_info_complete( Order : C Read-only : True Store type : MemoryStore - Codecs : ["BytesCodec(endian="] + Codecs : [{{'endian': }}] No. bytes : {count_bytes} ({count_bytes_formatted}) No. bytes stored : {count_bytes_stored_formatted} Storage ratio : {storage_ratio_formatted}