Skip to content

restore v2-like exceptions #3012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 56 additions & 28 deletions src/zarr/api/asynchronous.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import asyncio
import dataclasses
import warnings
from typing import TYPE_CHECKING, Any, Literal, cast
from typing import TYPE_CHECKING, Any, Literal

import numpy as np
import numpy.typing as npt
from typing_extensions import deprecated

from zarr.core.array import Array, AsyncArray, create_array, from_array, get_array_metadata
from zarr.core.array import Array, AsyncArray, create_array, from_array
from zarr.core.array_spec import ArrayConfig, ArrayConfigLike, ArrayConfigParams
from zarr.core.buffer import NDArrayLike
from zarr.core.common import (
Expand All @@ -25,13 +25,19 @@
)
from zarr.core.group import (
AsyncGroup,
ConsolidatedMetadata,
GroupMetadata,
create_hierarchy,
get_node,
)
from zarr.core.metadata import ArrayMetadataDict, ArrayV2Metadata, ArrayV3Metadata
from zarr.core.metadata import ArrayV2Metadata, ArrayV3Metadata
from zarr.core.metadata.group import ConsolidatedMetadata, GroupMetadata
from zarr.core.metadata.v2 import _default_compressor, _default_filters
from zarr.errors import NodeTypeValidationError
from zarr.errors import (
ArrayNotFoundError,
ContainsArrayError,
ContainsGroupError,
GroupNotFoundError,
NodeNotFoundError,
)
from zarr.storage._common import make_store_path

if TYPE_CHECKING:
Expand Down Expand Up @@ -316,27 +322,48 @@

store_path = await make_store_path(store, mode=mode, path=path, storage_options=storage_options)

# TODO: the mode check below seems wrong!
if "shape" not in kwargs and mode in {"a", "r", "r+", "w"}:
extant_node: AsyncGroup | AsyncArray[Any] = None
# All of these modes will defer to an existing mode
if mode in {"a", "r", "r+", "w-"}:
try:
metadata_dict = await get_array_metadata(store_path, zarr_format=zarr_format)
# TODO: remove this cast when we fix typing for array metadata dicts
_metadata_dict = cast(ArrayMetadataDict, metadata_dict)
# for v2, the above would already have raised an exception if not an array
zarr_format = _metadata_dict["zarr_format"]
is_v3_array = zarr_format == 3 and _metadata_dict.get("node_type") == "array"
if is_v3_array or zarr_format == 2:
return AsyncArray(store_path=store_path, metadata=_metadata_dict)
except (AssertionError, FileNotFoundError, NodeTypeValidationError):
extant_node = await get_node(
store=store_path.store, path=store_path.path, zarr_format=zarr_format
)
except NodeNotFoundError:
pass
return await open_group(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs)

try:
# we successfully found an existing node
if extant_node is not None:
# an existing node is an error if mode == w-
if mode == "w-":
if isinstance(extant_node, AsyncArray):
node_type = "array"
exc = ContainsArrayError

Check warning on line 340 in src/zarr/api/asynchronous.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/api/asynchronous.py#L338-L340

Added lines #L338 - L340 were not covered by tests
else:
node_type = "group"
exc = ContainsGroupError
msg = (

Check warning on line 344 in src/zarr/api/asynchronous.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/api/asynchronous.py#L342-L344

Added lines #L342 - L344 were not covered by tests
f"A Zarr V{extant_node.zarr_format} {node_type} exists in store "
f"{store_path.store!r} at path {store_path.path!r}. "
f"Attempting to open a pre-existing {node_type} with access mode {mode} is an error. "
f"Remove the {node_type} from storage, or use an access mode that is compatible with "
"a pre-existing array, such as one of ('r','r+','a','w')."
)
raise exc(msg)

Check warning on line 351 in src/zarr/api/asynchronous.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/api/asynchronous.py#L351

Added line #L351 was not covered by tests
else:
# otherwise, return the existing node
return extant_node
else:
if mode in ("r", "r+"):
msg = (
f"Neither array nor group metadata were found in store {store_path.store!r} at "
f"path {store_path.path!r}. Attempting to open an non-existent node with access mode "
f"{mode} is an error. Create an array or group first, or use an access mode that "
"create an array or group, such as one of ('a', 'w', 'w-')."
)
raise NodeNotFoundError(msg)
if "shape" in kwargs:
return await open_array(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs)
except (KeyError, NodeTypeValidationError):
# KeyError for a missing key
# NodeTypeValidationError for failing to parse node metadata as an array when it's
# actually a group
else:
return await open_group(store=store_path, zarr_format=zarr_format, mode=mode, **kwargs)


Expand Down Expand Up @@ -660,7 +687,7 @@

try:
return await AsyncGroup.open(store=store_path, zarr_format=zarr_format)
except (KeyError, FileNotFoundError):
except (KeyError, GroupNotFoundError):
_zarr_format = zarr_format or _default_zarr_format()
return await AsyncGroup.from_store(
store=store_path,
Expand Down Expand Up @@ -818,7 +845,7 @@
return await AsyncGroup.open(
store_path, zarr_format=zarr_format, use_consolidated=use_consolidated
)
except (KeyError, FileNotFoundError):
except GroupNotFoundError:
pass
if mode in _CREATE_MODES:
overwrite = _infer_overwrite(mode)
Expand All @@ -829,7 +856,8 @@
overwrite=overwrite,
attributes=attributes,
)
raise FileNotFoundError(f"Unable to find group: {store_path}")
msg = f"Group metadata was not found in store {store_path.store!r} at path {store_path.path!r}."
raise GroupNotFoundError(msg)

Check warning on line 860 in src/zarr/api/asynchronous.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/api/asynchronous.py#L859-L860

Added lines #L859 - L860 were not covered by tests


async def create(
Expand Down Expand Up @@ -1259,7 +1287,7 @@

try:
return await AsyncArray.open(store_path, zarr_format=zarr_format)
except FileNotFoundError:
except ArrayNotFoundError:
if not store_path.read_only and mode in _CREATE_MODES:
overwrite = _infer_overwrite(mode)
_zarr_format = zarr_format or _default_zarr_format()
Expand Down
44 changes: 32 additions & 12 deletions src/zarr/core/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
ArrayV3MetadataDict,
T_ArrayMetadata,
)
from zarr.core.metadata._io import _read_array_metadata
from zarr.core.metadata.group import GroupMetadata
from zarr.core.metadata.v2 import (
_default_compressor,
_default_filters,
Expand All @@ -108,7 +110,7 @@
)
from zarr.core.metadata.v3 import DataType, parse_node_type_array
from zarr.core.sync import sync
from zarr.errors import MetadataValidationError
from zarr.errors import ArrayNotFoundError
from zarr.registry import (
_parse_array_array_codec,
_parse_array_bytes_codec,
Expand Down Expand Up @@ -161,7 +163,7 @@
raise TypeError


async def get_array_metadata(
async def xget_array_metadata(
store_path: StorePath, zarr_format: ZarrFormat | None = 3
) -> dict[str, JSON]:
if zarr_format == 2:
Expand All @@ -170,11 +172,19 @@
(store_path / ZATTRS_JSON).get(prototype=cpu_buffer_prototype),
)
if zarray_bytes is None:
raise FileNotFoundError(store_path)
msg = (

Check warning on line 175 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L175

Added line #L175 was not covered by tests
"A Zarr V2 array metadata document was not found in store "
f"{store_path.store!r} at path {store_path.path!r}."
)
raise ArrayNotFoundError(msg)

Check warning on line 179 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L179

Added line #L179 was not covered by tests
elif zarr_format == 3:
zarr_json_bytes = await (store_path / ZARR_JSON).get(prototype=cpu_buffer_prototype)
if zarr_json_bytes is None:
raise FileNotFoundError(store_path)
msg = (

Check warning on line 183 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L183

Added line #L183 was not covered by tests
"A Zarr V3 array metadata document was not found in store "
f"{store_path.store!r} at path {store_path.path!r}."
)
raise ArrayNotFoundError(msg)

Check warning on line 187 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L187

Added line #L187 was not covered by tests
elif zarr_format is None:
zarr_json_bytes, zarray_bytes, zattrs_bytes = await gather(
(store_path / ZARR_JSON).get(prototype=cpu_buffer_prototype),
Expand All @@ -183,17 +193,27 @@
)
if zarr_json_bytes is not None and zarray_bytes is not None:
# warn and favor v3
msg = f"Both zarr.json (Zarr format 3) and .zarray (Zarr format 2) metadata objects exist at {store_path}. Zarr v3 will be used."
msg = (

Check warning on line 196 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L196

Added line #L196 was not covered by tests
"Both Zarr V3 Zarr V2 metadata documents "
f"were found in store {store_path.store!r} at path {store_path.path!r}. "
"The Zarr V3 metadata will be used."
"To open Zarr V2 arrays, set zarr_format=2."
)
warnings.warn(msg, stacklevel=1)
if zarr_json_bytes is None and zarray_bytes is None:
raise FileNotFoundError(store_path)
msg = (

Check warning on line 204 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L204

Added line #L204 was not covered by tests
f"Neither Zarr V3 nor Zarr V2 array metadata documents "
f"were found in store {store_path.store!r} at path {store_path.path!r}."
)
raise ArrayNotFoundError(msg)

Check warning on line 208 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L208

Added line #L208 was not covered by tests
# set zarr_format based on which keys were found
if zarr_json_bytes is not None:
zarr_format = 3
else:
zarr_format = 2
else:
raise MetadataValidationError("zarr_format", "2, 3, or None", zarr_format)
msg = f"Invalid value for zarr_format. Expected one of 2, 3, or None. Got {zarr_format}." # type: ignore[unreachable]
raise ValueError(msg)

Check warning on line 216 in src/zarr/core/array.py

View check run for this annotation

Codecov / codecov/patch

src/zarr/core/array.py#L215-L216

Added lines #L215 - L216 were not covered by tests

metadata_dict: dict[str, JSON]
if zarr_format == 2:
Expand Down Expand Up @@ -895,10 +915,10 @@
<AsyncArray memory://... shape=(100, 100) dtype=int32>
"""
store_path = await make_store_path(store)
metadata_dict = await get_array_metadata(store_path, zarr_format=zarr_format)
# TODO: remove this cast when we have better type hints
_metadata_dict = cast(ArrayV3MetadataDict, metadata_dict)
return cls(store_path=store_path, metadata=_metadata_dict)
metadata_dict = await _read_array_metadata(
store_path.store, store_path.path, zarr_format=zarr_format
)
return cls(store_path=store_path, metadata=metadata_dict)

@property
def store(self) -> Store:
Expand Down Expand Up @@ -3738,7 +3758,7 @@
def _build_parents(
node: AsyncArray[ArrayV2Metadata] | AsyncArray[ArrayV3Metadata] | AsyncGroup,
) -> list[AsyncGroup]:
from zarr.core.group import AsyncGroup, GroupMetadata
from zarr.core.group import AsyncGroup

store = node.store_path.store
path = node.store_path.path
Expand Down
Loading