Skip to content

Commit e3e1216

Browse files
committed
Merge branch 'main' of github.com:zarr-developers/zarr-python into chore/handle-numcodecs-codecs
2 parents d5e0461 + c019a5f commit e3e1216

File tree

11 files changed

+282
-85
lines changed

11 files changed

+282
-85
lines changed

changes/3367.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added `zarr.errors.ArrayNotFoundError`, which is raised when attempting to open a zarr array that does not exist, and `zarr.errors.NodeNotFoundError`, which is raised when failing to open an array or a group in a context where either an array or a group was expected.

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ docs = [
115115

116116

117117
[project.urls]
118-
"Bug Tracker" = "https://github.com/zarr-developers/zarr-python/issues"
119-
Changelog = "https://zarr.readthedocs.io/en/stable/release-notes.html"
118+
issues = "https://github.com/zarr-developers/zarr-python/issues"
119+
changelog = "https://zarr.readthedocs.io/en/stable/release-notes.html"
120120
Discussions = "https://github.com/zarr-developers/zarr-python/discussions"
121-
Documentation = "https://zarr.readthedocs.io/"
122-
Homepage = "https://github.com/zarr-developers/zarr-python"
121+
documentation = "https://zarr.readthedocs.io/"
122+
homepage = "https://github.com/zarr-developers/zarr-python"
123123

124124
[dependency-groups]
125125
dev = [
@@ -352,14 +352,14 @@ module = [
352352
"tests.test_store.test_fsspec",
353353
"tests.test_store.test_memory",
354354
"tests.test_codecs.test_codecs",
355+
"tests.test_metadata.*",
355356
]
356357
strict = false
357358

358359
# TODO: Move the next modules up to the strict = false section
359360
# and fix the errors
360361
[[tool.mypy.overrides]]
361362
module = [
362-
"tests.test_metadata.*",
363363
"tests.test_store.test_core",
364364
"tests.test_store.test_logging",
365365
"tests.test_store.test_object",

src/zarr/abc/codec.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def validate(
138138
"""
139139

140140
async def _decode_single(self, chunk_data: CodecOutput, chunk_spec: ArraySpec) -> CodecInput:
141-
raise NotImplementedError
141+
raise NotImplementedError # pragma: no cover
142142

143143
async def decode(
144144
self,
@@ -161,7 +161,7 @@ async def decode(
161161
async def _encode_single(
162162
self, chunk_data: CodecInput, chunk_spec: ArraySpec
163163
) -> CodecOutput | None:
164-
raise NotImplementedError
164+
raise NotImplementedError # pragma: no cover
165165

166166
async def encode(
167167
self,
@@ -242,7 +242,7 @@ async def _encode_partial_single(
242242
selection: SelectorTuple,
243243
chunk_spec: ArraySpec,
244244
) -> None:
245-
raise NotImplementedError
245+
raise NotImplementedError # pragma: no cover
246246

247247
async def encode_partial(
248248
self,

src/zarr/api/asynchronous.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
)
4040
from zarr.core.metadata import ArrayMetadataDict, ArrayV2Metadata, ArrayV3Metadata
4141
from zarr.errors import (
42+
ArrayNotFoundError,
4243
GroupNotFoundError,
4344
NodeTypeValidationError,
4445
ZarrDeprecationWarning,
@@ -1257,7 +1258,7 @@ async def open_array(
12571258

12581259
try:
12591260
return await AsyncArray.open(store_path, zarr_format=zarr_format)
1260-
except FileNotFoundError:
1261+
except FileNotFoundError as err:
12611262
if not store_path.read_only and mode in _CREATE_MODES:
12621263
overwrite = _infer_overwrite(mode)
12631264
_zarr_format = zarr_format or _default_zarr_format()
@@ -1267,7 +1268,7 @@ async def open_array(
12671268
overwrite=overwrite,
12681269
**kwargs,
12691270
)
1270-
raise
1271+
raise ArrayNotFoundError(store_path.store, store_path.path) from err
12711272

12721273

12731274
async def open_like(

src/zarr/core/array.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
import warnings
55
from asyncio import gather
6-
from collections.abc import Iterable
6+
from collections.abc import Iterable, Mapping
77
from dataclasses import dataclass, field, replace
88
from itertools import starmap
99
from logging import getLogger
@@ -117,7 +117,12 @@
117117
)
118118
from zarr.core.metadata.v3 import parse_node_type_array
119119
from zarr.core.sync import sync
120-
from zarr.errors import MetadataValidationError, ZarrDeprecationWarning, ZarrUserWarning
120+
from zarr.errors import (
121+
ArrayNotFoundError,
122+
MetadataValidationError,
123+
ZarrDeprecationWarning,
124+
ZarrUserWarning,
125+
)
121126
from zarr.registry import (
122127
_parse_array_array_codec,
123128
_parse_array_bytes_codec,
@@ -216,11 +221,19 @@ async def get_array_metadata(
216221
(store_path / ZATTRS_JSON).get(prototype=cpu_buffer_prototype),
217222
)
218223
if zarray_bytes is None:
219-
raise FileNotFoundError(store_path)
224+
msg = (
225+
"A Zarr V2 array metadata document was not found in store "
226+
f"{store_path.store!r} at path {store_path.path!r}."
227+
)
228+
raise ArrayNotFoundError(msg)
220229
elif zarr_format == 3:
221230
zarr_json_bytes = await (store_path / ZARR_JSON).get(prototype=cpu_buffer_prototype)
222231
if zarr_json_bytes is None:
223-
raise FileNotFoundError(store_path)
232+
msg = (
233+
"A Zarr V3 array metadata document was not found in store "
234+
f"{store_path.store!r} at path {store_path.path!r}."
235+
)
236+
raise ArrayNotFoundError(msg)
224237
elif zarr_format is None:
225238
zarr_json_bytes, zarray_bytes, zattrs_bytes = await gather(
226239
(store_path / ZARR_JSON).get(prototype=cpu_buffer_prototype),
@@ -232,7 +245,11 @@ async def get_array_metadata(
232245
msg = f"Both zarr.json (Zarr format 3) and .zarray (Zarr format 2) metadata objects exist at {store_path}. Zarr v3 will be used."
233246
warnings.warn(msg, category=ZarrUserWarning, stacklevel=1)
234247
if zarr_json_bytes is None and zarray_bytes is None:
235-
raise FileNotFoundError(store_path)
248+
msg = (
249+
f"Neither Zarr V3 nor Zarr V2 array metadata documents "
250+
f"were found in store {store_path.store!r} at path {store_path.path!r}."
251+
)
252+
raise ArrayNotFoundError(msg)
236253
# set zarr_format based on which keys were found
237254
if zarr_json_bytes is not None:
238255
zarr_format = 3
@@ -3907,7 +3924,7 @@ def _build_parents(
39073924

39083925
CompressorsLike: TypeAlias = (
39093926
Iterable[dict[str, JSON] | BytesBytesCodec | Numcodec]
3910-
| dict[str, JSON]
3927+
| Mapping[str, JSON]
39113928
| BytesBytesCodec
39123929
| Numcodec
39133930
| Literal["auto"]

src/zarr/errors.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Any
22

33
__all__ = [
4+
"ArrayNotFoundError",
45
"BaseZarrError",
56
"ContainsArrayAndGroupError",
67
"ContainsArrayError",
@@ -26,12 +27,49 @@ def __init__(self, *args: Any) -> None:
2627
super().__init__(self._msg.format(*args))
2728

2829

29-
class GroupNotFoundError(BaseZarrError, FileNotFoundError):
30+
class NodeNotFoundError(BaseZarrError, FileNotFoundError):
31+
"""
32+
Raised when a node (array or group) is not found at a certain path.
33+
"""
34+
35+
def __init__(self, *args: Any) -> None:
36+
if len(args) == 1:
37+
# Pre-formatted message
38+
super(BaseZarrError, self).__init__(args[0])
39+
else:
40+
# Store and path arguments - format them
41+
_msg = "No node found in store {!r} at path {!r}"
42+
super(BaseZarrError, self).__init__(_msg.format(*args))
43+
44+
45+
class ArrayNotFoundError(NodeNotFoundError):
46+
"""
47+
Raised when an array isn't found at a certain path.
48+
"""
49+
50+
def __init__(self, *args: Any) -> None:
51+
if len(args) == 1:
52+
# Pre-formatted message
53+
super(BaseZarrError, self).__init__(args[0])
54+
else:
55+
# Store and path arguments - format them
56+
_msg = "No array found in store {!r} at path {!r}"
57+
super(BaseZarrError, self).__init__(_msg.format(*args))
58+
59+
60+
class GroupNotFoundError(NodeNotFoundError):
3061
"""
3162
Raised when a group isn't found at a certain path.
3263
"""
3364

34-
_msg = "No group found in store {!r} at path {!r}"
65+
def __init__(self, *args: Any) -> None:
66+
if len(args) == 1:
67+
# Pre-formatted message
68+
super(BaseZarrError, self).__init__(args[0])
69+
else:
70+
# Store and path arguments - format them
71+
_msg = "No group found in store {!r} at path {!r}"
72+
super(BaseZarrError, self).__init__(_msg.format(*args))
3573

3674

3775
class ContainsGroupError(BaseZarrError):

tests/test_api.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import zarr.codecs
88
import zarr.storage
9-
from zarr.core.array import init_array
9+
from zarr.core.array import AsyncArray, init_array
1010
from zarr.storage import LocalStore, ZipStore
1111
from zarr.storage._common import StorePath
1212

@@ -42,7 +42,13 @@
4242
save_group,
4343
)
4444
from zarr.core.buffer import NDArrayLike
45-
from zarr.errors import MetadataValidationError, ZarrDeprecationWarning, ZarrUserWarning
45+
from zarr.errors import (
46+
ArrayNotFoundError,
47+
MetadataValidationError,
48+
NodeNotFoundError,
49+
ZarrDeprecationWarning,
50+
ZarrUserWarning,
51+
)
4652
from zarr.storage import MemoryStore
4753
from zarr.storage._utils import normalize_path
4854
from zarr.testing.utils import gpu_test
@@ -70,11 +76,11 @@ def test_create(memory_store: Store) -> None:
7076

7177
# create array with float shape
7278
with pytest.raises(TypeError):
73-
z = create(shape=(400.5, 100), store=store, overwrite=True) # type: ignore [arg-type]
79+
z = create(shape=(400.5, 100), store=store, overwrite=True) # type: ignore[arg-type]
7480

7581
# create array with float chunk shape
7682
with pytest.raises(TypeError):
77-
z = create(shape=(400, 100), chunks=(16, 16.5), store=store, overwrite=True) # type: ignore [arg-type]
83+
z = create(shape=(400, 100), chunks=(16, 16.5), store=store, overwrite=True) # type: ignore[arg-type]
7884

7985

8086
# TODO: parametrize over everything this function takes
@@ -185,10 +191,27 @@ async def test_open_array(memory_store: MemoryStore, zarr_format: ZarrFormat) ->
185191
assert z.read_only
186192

187193
# path not found
188-
with pytest.raises(FileNotFoundError):
194+
with pytest.raises(NodeNotFoundError):
189195
zarr.api.synchronous.open(store="doesnotexist", mode="r", zarr_format=zarr_format)
190196

191197

198+
@pytest.mark.asyncio
199+
async def test_async_array_open_array_not_found() -> None:
200+
"""Test that AsyncArray.open raises ArrayNotFoundError when array doesn't exist"""
201+
store = MemoryStore()
202+
# Try to open an array that does not exist
203+
with pytest.raises(ArrayNotFoundError):
204+
await AsyncArray.open(store, zarr_format=2)
205+
206+
207+
def test_array_open_array_not_found_sync() -> None:
208+
"""Test that Array.open raises ArrayNotFoundError when array doesn't exist"""
209+
store = MemoryStore()
210+
# Try to open an array that does not exist
211+
with pytest.raises(ArrayNotFoundError):
212+
Array.open(store)
213+
214+
192215
@pytest.mark.parametrize("store", ["memory", "local", "zip"], indirect=True)
193216
def test_v2_and_v3_exist_at_same_path(store: Store) -> None:
194217
zarr.create_array(store, shape=(10,), dtype="uint8", zarr_format=3)
@@ -266,7 +289,7 @@ def test_save(store: Store, n_args: int, n_kwargs: int, path: None | str) -> Non
266289
assert isinstance(array, Array)
267290
assert_array_equal(array[:], data)
268291
else:
269-
save(store, *args, path=path, **kwargs) # type: ignore [arg-type]
292+
save(store, *args, path=path, **kwargs) # type: ignore[arg-type]
270293
group = zarr.api.synchronous.open(store, path=path)
271294
assert isinstance(group, Group)
272295
for array in group.array_values():
@@ -1208,13 +1231,13 @@ async def test_metadata_validation_error() -> None:
12081231
MetadataValidationError,
12091232
match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.",
12101233
):
1211-
await zarr.api.asynchronous.open_group(zarr_format="3.0") # type: ignore [arg-type]
1234+
await zarr.api.asynchronous.open_group(zarr_format="3.0") # type: ignore[arg-type]
12121235

12131236
with pytest.raises(
12141237
MetadataValidationError,
12151238
match="Invalid value for 'zarr_format'. Expected '2, 3, or None'. Got '3.0'.",
12161239
):
1217-
await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") # type: ignore [arg-type]
1240+
await zarr.api.asynchronous.open_array(shape=(1,), zarr_format="3.0") # type: ignore[arg-type]
12181241

12191242

12201243
@pytest.mark.parametrize(
@@ -1224,7 +1247,7 @@ async def test_metadata_validation_error() -> None:
12241247
)
12251248
def test_open_array_with_mode_r_plus(store: Store, zarr_format: ZarrFormat) -> None:
12261249
# 'r+' means read/write (must exist)
1227-
with pytest.raises(FileNotFoundError):
1250+
with pytest.raises(ArrayNotFoundError):
12281251
zarr.open_array(store=store, mode="r+", zarr_format=zarr_format)
12291252
zarr.ones(store=store, shape=(3, 3), zarr_format=zarr_format)
12301253
z2 = zarr.open_array(store=store, mode="r+")

0 commit comments

Comments
 (0)