Skip to content

Commit 5ba51af

Browse files
authored
Merge branch 'main' into feat/latency-store
2 parents f97b27c + 3ab3607 commit 5ba51af

File tree

19 files changed

+976
-59
lines changed

19 files changed

+976
-59
lines changed

.github/workflows/releases.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
with:
5656
name: releases
5757
path: dist
58-
- uses: pypa/gh-action-pypi-publish@v1.11.0
58+
- uses: pypa/gh-action-pypi-publish@v1.12.2
5959
with:
6060
user: __token__
6161
password: ${{ secrets.pypi_password }}

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ default_language_version:
77
python: python3
88
repos:
99
- repo: https://github.com/astral-sh/ruff-pre-commit
10-
rev: v0.7.2
10+
rev: v0.7.3
1111
hooks:
1212
- id: ruff
1313
args: ["--fix", "--show-fixes"]

src/zarr/abc/store.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import TYPE_CHECKING, NamedTuple, Protocol, runtime_checkable
77

88
if TYPE_CHECKING:
9-
from collections.abc import AsyncGenerator, Iterable
9+
from collections.abc import AsyncGenerator, AsyncIterator, Iterable
1010
from types import TracebackType
1111
from typing import Any, Self, TypeAlias
1212

@@ -329,16 +329,19 @@ def supports_listing(self) -> bool:
329329
...
330330

331331
@abstractmethod
332-
def list(self) -> AsyncGenerator[str]:
332+
def list(self) -> AsyncIterator[str]:
333333
"""Retrieve all keys in the store.
334334
335335
Returns
336336
-------
337-
AsyncGenerator[str, None]
337+
AsyncIterator[str]
338338
"""
339+
# This method should be async, like overridden methods in child classes.
340+
# However, that's not straightforward:
341+
# https://stackoverflow.com/questions/68905848
339342

340343
@abstractmethod
341-
def list_prefix(self, prefix: str) -> AsyncGenerator[str]:
344+
def list_prefix(self, prefix: str) -> AsyncIterator[str]:
342345
"""
343346
Retrieve all keys in the store that begin with a given prefix. Keys are returned relative
344347
to the root of the store.
@@ -349,11 +352,14 @@ def list_prefix(self, prefix: str) -> AsyncGenerator[str]:
349352
350353
Returns
351354
-------
352-
AsyncGenerator[str, None]
355+
AsyncIterator[str]
353356
"""
357+
# This method should be async, like overridden methods in child classes.
358+
# However, that's not straightforward:
359+
# https://stackoverflow.com/questions/68905848
354360

355361
@abstractmethod
356-
def list_dir(self, prefix: str) -> AsyncGenerator[str]:
362+
def list_dir(self, prefix: str) -> AsyncIterator[str]:
357363
"""
358364
Retrieve all keys and prefixes with a given prefix and which do not contain the character
359365
“/” after the given prefix.
@@ -364,8 +370,11 @@ def list_dir(self, prefix: str) -> AsyncGenerator[str]:
364370
365371
Returns
366372
-------
367-
AsyncGenerator[str, None]
373+
AsyncIterator[str]
368374
"""
375+
# This method should be async, like overridden methods in child classes.
376+
# However, that's not straightforward:
377+
# https://stackoverflow.com/questions/68905848
369378

370379
async def delete_dir(self, prefix: str) -> None:
371380
"""

src/zarr/core/_info.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import dataclasses
2+
import textwrap
3+
from typing import Any, Literal
4+
5+
import numcodecs.abc
6+
import numpy as np
7+
8+
from zarr.abc.codec import Codec
9+
from zarr.core.metadata.v3 import DataType
10+
11+
12+
@dataclasses.dataclass(kw_only=True)
13+
class GroupInfo:
14+
"""
15+
Visual summary for a Group.
16+
17+
Note that this method and its properties is not part of
18+
Zarr's public API.
19+
"""
20+
21+
_name: str
22+
_type: Literal["Group"] = "Group"
23+
_zarr_format: Literal[2, 3]
24+
_read_only: bool
25+
_store_type: str
26+
_count_members: int | None = None
27+
_count_arrays: int | None = None
28+
_count_groups: int | None = None
29+
30+
def __repr__(self) -> str:
31+
template = textwrap.dedent("""\
32+
Name : {_name}
33+
Type : {_type}
34+
Zarr format : {_zarr_format}
35+
Read-only : {_read_only}
36+
Store type : {_store_type}""")
37+
38+
if self._count_members is not None:
39+
template += "\nNo. members : {_count_members}"
40+
if self._count_arrays is not None:
41+
template += "\nNo. arrays : {_count_arrays}"
42+
if self._count_groups is not None:
43+
template += "\nNo. groups : {_count_groups}"
44+
return template.format(**dataclasses.asdict(self))
45+
46+
47+
def human_readable_size(size: int) -> str:
48+
if size < 2**10:
49+
return f"{size}"
50+
elif size < 2**20:
51+
return f"{size / float(2**10):.1f}K"
52+
elif size < 2**30:
53+
return f"{size / float(2**20):.1f}M"
54+
elif size < 2**40:
55+
return f"{size / float(2**30):.1f}G"
56+
elif size < 2**50:
57+
return f"{size / float(2**40):.1f}T"
58+
else:
59+
return f"{size / float(2**50):.1f}P"
60+
61+
62+
def byte_info(size: int) -> str:
63+
if size < 2**10:
64+
return str(size)
65+
else:
66+
return f"{size} ({human_readable_size(size)})"
67+
68+
69+
@dataclasses.dataclass(kw_only=True)
70+
class ArrayInfo:
71+
"""
72+
Visual summary for an Array.
73+
74+
Note that this method and its properties is not part of
75+
Zarr's public API.
76+
"""
77+
78+
_type: Literal["Array"] = "Array"
79+
_zarr_format: Literal[2, 3]
80+
_data_type: np.dtype[Any] | DataType
81+
_shape: tuple[int, ...]
82+
_chunk_shape: tuple[int, ...] | None = None
83+
_order: Literal["C", "F"]
84+
_read_only: bool
85+
_store_type: str
86+
_compressor: numcodecs.abc.Codec | None = None
87+
_filters: tuple[numcodecs.abc.Codec, ...] | None = None
88+
_codecs: list[Codec] | None = None
89+
_count_bytes: int | None = None
90+
_count_bytes_stored: int | None = None
91+
_count_chunks_initialized: int | None = None
92+
93+
def __repr__(self) -> str:
94+
template = textwrap.dedent("""\
95+
Type : {_type}
96+
Zarr format : {_zarr_format}
97+
Data type : {_data_type}
98+
Shape : {_shape}
99+
Chunk shape : {_chunk_shape}
100+
Order : {_order}
101+
Read-only : {_read_only}
102+
Store type : {_store_type}""")
103+
104+
kwargs = dataclasses.asdict(self)
105+
if self._chunk_shape is None:
106+
# for non-regular chunk grids
107+
kwargs["chunk_shape"] = "<variable>"
108+
if self._compressor is not None:
109+
template += "\nCompressor : {_compressor}"
110+
111+
if self._filters is not None:
112+
template += "\nFilters : {_filters}"
113+
114+
if self._codecs is not None:
115+
template += "\nCodecs : {_codecs}"
116+
117+
if self._count_bytes is not None:
118+
template += "\nNo. bytes : {_count_bytes}"
119+
kwargs["_count_bytes"] = byte_info(self._count_bytes)
120+
121+
if self._count_bytes_stored is not None:
122+
template += "\nNo. bytes stored : {_count_bytes_stored}"
123+
kwargs["_count_stored"] = byte_info(self._count_bytes_stored)
124+
125+
if (
126+
self._count_bytes is not None
127+
and self._count_bytes_stored is not None
128+
and self._count_bytes_stored > 0
129+
):
130+
template += "\nStorage ratio : {_storage_ratio}"
131+
kwargs["_storage_ratio"] = f"{self._count_bytes / self._count_bytes_stored:.1f}"
132+
133+
if self._count_chunks_initialized is not None:
134+
template += "\nChunks Initialized : {_count_chunks_initialized}"
135+
return template.format(**kwargs)

0 commit comments

Comments
 (0)