Skip to content

Commit c019a5f

Browse files
ruaridhgd-v-b
andauthored
Added ArrayNotFoundError (#3367)
* Added ArrayNotFoundError * Add changes file * Add ArrayNotFoundError to test_api.py * Fix linting issue * Address PR comments and add tests to cover new cases * Fix linting errors * Add type: ignore comments back in * Change inheritance of errors so Group and Array inherit from Node * Fix missing import * Update changes/3367.bugfix.rst --------- Co-authored-by: ruaridhg <[email protected]> Co-authored-by: Davis Bennett <[email protected]>
1 parent 0864ee5 commit c019a5f

File tree

5 files changed

+97
-17
lines changed

5 files changed

+97
-17
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.

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: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

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)