Skip to content

Commit 817617a

Browse files
authored
Merge branch 'main' into string-arguments-for-codecs
2 parents a55abad + 7584b96 commit 817617a

14 files changed

Lines changed: 175 additions & 58 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ repos:
3838
# Tests
3939
- pytest
4040
- hypothesis
41+
- s3fs
4142
- repo: https://github.com/scientific-python/cookie
4243
rev: 2025.01.22
4344
hooks:

changes/2862.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug that prevented the number of initialized chunks being counted properly.

docs/user-guide/groups.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ property. E.g.::
140140
No. bytes : 8000000 (7.6M)
141141
No. bytes stored : 1614
142142
Storage ratio : 4956.6
143-
Chunks Initialized : 0
143+
Chunks Initialized : 10
144144
>>> baz.info
145145
Type : Array
146146
Zarr format : 3

pyproject.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,11 @@ module = [
358358
"tests.package_with_entrypoint.*",
359359
"zarr.testing.stateful",
360360
"tests.test_codecs.test_transpose",
361-
"tests.test_config"
361+
"tests.test_config",
362+
"tests.test_store.test_zip",
363+
"tests.test_store.test_local",
364+
"tests.test_store.test_fsspec",
365+
"tests.test_store.test_memory",
362366
]
363367
strict = false
364368

@@ -368,7 +372,11 @@ strict = false
368372
module = [
369373
"tests.test_codecs.test_codecs",
370374
"tests.test_metadata.*",
371-
"tests.test_store.*",
375+
"tests.test_store.test_core",
376+
"tests.test_store.test_logging",
377+
"tests.test_store.test_object",
378+
"tests.test_store.test_stateful",
379+
"tests.test_store.test_wrapper",
372380
"tests.test_group",
373381
"tests.test_indexing",
374382
"tests.test_properties",

src/zarr/core/array.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
get_pipeline_class,
118118
)
119119
from zarr.storage._common import StorePath, ensure_no_existing_node, make_store_path
120+
from zarr.storage._utils import _relativize_path
120121

121122
if TYPE_CHECKING:
122123
from collections.abc import Iterator, Sequence
@@ -3737,7 +3738,12 @@ async def chunks_initialized(
37373738
store_contents = [
37383739
x async for x in array.store_path.store.list_prefix(prefix=array.store_path.path)
37393740
]
3740-
return tuple(chunk_key for chunk_key in array._iter_chunk_keys() if chunk_key in store_contents)
3741+
store_contents_relative = [
3742+
_relativize_path(path=key, prefix=array.store_path.path) for key in store_contents
3743+
]
3744+
return tuple(
3745+
chunk_key for chunk_key in array._iter_chunk_keys() if chunk_key in store_contents_relative
3746+
)
37413747

37423748

37433749
def _build_parents(

src/zarr/storage/_utils.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,62 @@ def _join_paths(paths: Iterable[str]) -> str:
7474
"""
7575
Filter out instances of '' and join the remaining strings with '/'.
7676
77-
Because the root node of a zarr hierarchy is represented by an empty string,
77+
Parameters
78+
----------
79+
paths : Iterable[str]
80+
81+
Returns
82+
-------
83+
str
84+
85+
Examples
86+
--------
87+
>>> _join_paths(["", "a", "b"])
88+
'a/b'
89+
>>> _join_paths(["a", "b", "c"])
90+
'a/b/c'
7891
"""
7992
return "/".join(filter(lambda v: v != "", paths))
8093

8194

95+
def _relativize_path(*, path: str, prefix: str) -> str:
96+
"""
97+
Make a "/"-delimited path relative to some prefix. If the prefix is '', then the path is
98+
returned as-is. Otherwise, the prefix is removed from the path as well as the separator
99+
string "/".
100+
101+
If ``prefix`` is not the empty string and ``path`` does not start with ``prefix``
102+
followed by a "/" character, then an error is raised.
103+
104+
This function assumes that the prefix does not end with "/".
105+
106+
Parameters
107+
----------
108+
path : str
109+
The path to make relative to the prefix.
110+
prefix : str
111+
The prefix to make the path relative to.
112+
113+
Returns
114+
-------
115+
str
116+
117+
Examples
118+
--------
119+
>>> _relativize_path(path="", prefix="a/b")
120+
'a/b'
121+
>>> _relativize_path(path="a/b", prefix="a/b/c")
122+
'c'
123+
"""
124+
if prefix == "":
125+
return path
126+
else:
127+
_prefix = prefix + "/"
128+
if not path.startswith(_prefix):
129+
raise ValueError(f"The first component of {path} does not start with {prefix}.")
130+
return path.removeprefix(f"{prefix}/")
131+
132+
82133
def _normalize_paths(paths: Iterable[str]) -> tuple[str, ...]:
83134
"""
84135
Normalize the input paths according to the normalization scheme used for zarr node paths.

src/zarr/testing/store.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async def get(self, store: S, key: str) -> Buffer:
5858

5959
@abstractmethod
6060
@pytest.fixture
61-
def store_kwargs(self) -> dict[str, Any]:
61+
def store_kwargs(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
6262
"""Kwargs for instantiating a store"""
6363
...
6464

src/zarr/testing/utils.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

3-
from collections.abc import Callable, Coroutine
4-
from typing import TYPE_CHECKING, Any, TypeVar, cast
3+
from typing import TYPE_CHECKING, TypeVar, cast
54

65
import pytest
76

@@ -38,13 +37,13 @@ def has_cupy() -> bool:
3837
return False
3938

4039

41-
T_Callable = TypeVar("T_Callable", bound=Callable[..., Coroutine[Any, Any, None] | None])
40+
T = TypeVar("T")
4241

4342

4443
# Decorator for GPU tests
45-
def gpu_test(func: T_Callable) -> T_Callable:
44+
def gpu_test(func: T) -> T:
4645
return cast(
47-
"T_Callable",
46+
"T",
4847
pytest.mark.gpu(
4948
pytest.mark.skipif(not has_cupy(), reason="CuPy not installed or no GPU available")(
5049
func

tests/test_array.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,13 @@ async def test_nchunks_initialized(test_cls: type[Array] | type[AsyncArray[Any]]
388388
assert observed == expected
389389

390390

391-
async def test_chunks_initialized() -> None:
391+
@pytest.mark.parametrize("path", ["", "foo"])
392+
async def test_chunks_initialized(path: str) -> None:
392393
"""
393394
Test that chunks_initialized accurately returns the keys of stored chunks.
394395
"""
395396
store = MemoryStore()
396-
arr = zarr.create_array(store, shape=(100,), chunks=(10,), dtype="i4")
397+
arr = zarr.create_array(store, name=path, shape=(100,), chunks=(10,), dtype="i4")
397398

398399
chunks_accumulated = tuple(
399400
accumulate(tuple(tuple(v.split(" ")) for v in arr._iter_chunk_keys()))

tests/test_store/test_core.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
from zarr.core.common import AccessModeLiteral, ZarrFormat
99
from zarr.storage import FsspecStore, LocalStore, MemoryStore, StoreLike, StorePath
1010
from zarr.storage._common import contains_array, contains_group, make_store_path
11-
from zarr.storage._utils import _join_paths, _normalize_path_keys, _normalize_paths, normalize_path
11+
from zarr.storage._utils import (
12+
_join_paths,
13+
_normalize_path_keys,
14+
_normalize_paths,
15+
_relativize_path,
16+
normalize_path,
17+
)
1218

1319

1420
@pytest.mark.parametrize("path", ["foo", "foo/bar"])
@@ -221,3 +227,27 @@ def test_normalize_path_keys():
221227
"""
222228
data = {"a": 10, "//b": 10}
223229
assert _normalize_path_keys(data) == {normalize_path(k): v for k, v in data.items()}
230+
231+
232+
@pytest.mark.parametrize(
233+
("path", "prefix", "expected"),
234+
[
235+
("a", "", "a"),
236+
("a/b/c", "a/b", "c"),
237+
("a/b/c", "a", "b/c"),
238+
],
239+
)
240+
def test_relativize_path_valid(path: str, prefix: str, expected: str) -> None:
241+
"""
242+
Test the normal behavior of the _relativize_path function. Prefixes should be removed from the
243+
path argument.
244+
"""
245+
assert _relativize_path(path=path, prefix=prefix) == expected
246+
247+
248+
def test_relativize_path_invalid() -> None:
249+
path = "a/b/c"
250+
prefix = "b"
251+
msg = f"The first component of {path} does not start with {prefix}."
252+
with pytest.raises(ValueError, match=msg):
253+
_relativize_path(path="a/b/c", prefix="b")

0 commit comments

Comments
 (0)