Skip to content
Open
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
8 changes: 5 additions & 3 deletions src/zarr/core/metadata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from .v2 import ArrayV2Metadata, ArrayV2MetadataDict
from .v3 import ArrayMetadataJSON_V3, ArrayV3Metadata
from .v2 import ArrayMetadataV2, ArrayV2Metadata, ArrayV2MetadataDict
from .v3 import ArrayMetadataJSON_V3, ArrayMetadataV3, ArrayV3Metadata

ArrayMetadata = ArrayV2Metadata | ArrayV3Metadata
ArrayMetadata = ArrayMetadataV2 | ArrayMetadataV3
type ArrayMetadataDict = ArrayV2MetadataDict | ArrayMetadataJSON_V3

__all__ = [
"ArrayMetadata",
"ArrayMetadataDict",
"ArrayMetadataJSON_V3",
"ArrayMetadataV2",
"ArrayMetadataV3",
"ArrayV2Metadata",
"ArrayV2MetadataDict",
"ArrayV3Metadata",
Expand Down
11 changes: 7 additions & 4 deletions src/zarr/core/metadata/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class ArrayV2MetadataDict(TypedDict):


@dataclass(frozen=True, kw_only=True)
class ArrayV2Metadata(Metadata):
class ArrayMetadataV2(Metadata):
shape: tuple[int, ...]
chunks: tuple[int, ...]
dtype: ZDType[TBaseDType, TBaseScalar]
Expand Down Expand Up @@ -152,7 +152,7 @@ def to_buffer_dict(self, prototype: BufferPrototype) -> dict[str, Buffer]:
}

@classmethod
def from_dict(cls, data: dict[str, Any], *, path: str | None = None) -> ArrayV2Metadata:
def from_dict(cls, data: dict[str, Any], *, path: str | None = None) -> ArrayMetadataV2:
"""Read a stored `.zarray` document (with its attributes). An invalid document
that `zarr.core.metadata.repair` can read is read as repaired; a reading the user
must act on warns, naming the array at `path`."""
Expand Down Expand Up @@ -187,7 +187,7 @@ def from_dict(cls, data: dict[str, Any], *, path: str | None = None) -> ArrayV2M
_data["fill_value"] = fill_value

# zarr v2 allowed arbitrary keys here.
# We don't want the ArrayV2Metadata constructor to fail just because someone put an
# We don't want the ArrayMetadataV2 constructor to fail just because someone put an
# extra key in the metadata.
expected = {x.name for x in fields(cls)}
expected |= {"dtype", "chunks"}
Expand Down Expand Up @@ -248,7 +248,7 @@ def __eq__(self, other: object) -> bool:
# The default dataclass __eq__ compares fields directly, which is wrong for a NaN
# fill_value: NaN != NaN under IEEE 754. Comparing the JSON-serialized form instead
# treats matching NaN (and inf) fill values as equal. See issue #2929.
if not isinstance(other, ArrayV2Metadata):
if not isinstance(other, ArrayMetadataV2):
return NotImplemented
return self.to_dict() == other.to_dict()

Expand Down Expand Up @@ -279,6 +279,9 @@ def update_attributes(self, attributes: dict[str, JSON]) -> Self:
return replace(self, attributes=attributes)


ArrayV2Metadata = ArrayMetadataV2


def parse_dtype(data: npt.DTypeLike) -> np.dtype[Any]:
if isinstance(data, list): # this is a valid _VoidDTypeLike check
data = [tuple(d) for d in data]
Expand Down
13 changes: 8 additions & 5 deletions src/zarr/core/metadata/v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ class RegularChunkGridMetadata(Metadata):
"""Metadata-only description of a regular chunk grid.

Stores just the chunk shape — no array extent, no runtime logic.
This is what lives on ``ArrayV3Metadata.chunk_grid``.
This is what lives on ``ArrayMetadataV3.chunk_grid``.
"""

chunk_shape: tuple[int, ...]
Expand Down Expand Up @@ -400,13 +400,13 @@ def _check_rectilinear_chunks_enabled() -> None:
)


def check_storable(metadata: ArrayV3Metadata) -> None:
def check_storable(metadata: ArrayMetadataV3) -> None:
"""Raise if `metadata` may not be stored: a rectilinear chunk grid requires the
rectilinear chunks flag. `zarr.core.metadata.io.encode_documents` names the node in
the error.

Every serialization of array metadata for a store calls this, before the store is
touched: `ArrayV3Metadata.to_buffer_dict` and, for the arrays in a group's
touched: `ArrayMetadataV3.to_buffer_dict` and, for the arrays in a group's
consolidated metadata, `GroupMetadata.to_buffer_dict`.
"""
if isinstance(metadata.chunk_grid, RectilinearChunkGridMetadata):
Expand Down Expand Up @@ -506,7 +506,7 @@ class ArrayMetadataJSON_V3(TypedDict, extra_items=AllowedExtraField): # type: i


@dataclass(frozen=True, kw_only=True)
class ArrayV3Metadata(Metadata):
class ArrayMetadataV3(Metadata):
shape: tuple[int, ...]
data_type: ZDType[TBaseDType, TBaseScalar]
chunk_grid: ChunkGridMetadata
Expand Down Expand Up @@ -756,7 +756,7 @@ def __eq__(self, other: object) -> bool:
# The default dataclass __eq__ compares fields directly, which is wrong for a NaN
# fill_value: NaN != NaN under IEEE 754. Comparing the JSON-serialized form instead
# treats matching NaN (and inf) fill values as equal. See issue #2929.
if not isinstance(other, ArrayV3Metadata):
if not isinstance(other, ArrayMetadataV3):
return NotImplemented
return self.to_dict() == other.to_dict()

Expand All @@ -773,3 +773,6 @@ def update_shape(self, shape: tuple[int, ...]) -> Self:

def update_attributes(self, attributes: dict[str, JSON]) -> Self:
return replace(self, attributes=attributes)


ArrayV3Metadata = ArrayMetadataV3
30 changes: 29 additions & 1 deletion tests/test_metadata/test_v2.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
from __future__ import annotations

import json
import pickle
from typing import TYPE_CHECKING, Literal

import numpy as np
import pytest

import zarr.api.asynchronous
import zarr.core.metadata as metadata_exports
import zarr.core.metadata.v2 as metadata_v2
import zarr.storage
from zarr.core.buffer import cpu
from zarr.core.buffer.core import default_buffer_prototype
from zarr.core.dtype.npy.float import Float32, Float64
from zarr.core.dtype.npy.int import Int16
from zarr.core.group import ConsolidatedMetadata, GroupMetadata
from zarr.core.metadata import ArrayV2Metadata
from zarr.core.metadata import ArrayMetadata, ArrayMetadataV2, ArrayV2Metadata
from zarr.core.metadata.v2 import parse_zarr_format
from zarr.errors import ZarrUserWarning

Expand All @@ -26,6 +29,31 @@
from zarr.core.dtype.common import DTypeName_V2


def test_array_metadata_v2_names_are_compatible() -> None:
assert metadata_v2.ArrayMetadataV2 is ArrayMetadataV2
assert metadata_v2.ArrayV2Metadata is ArrayV2Metadata is ArrayMetadataV2
assert "ArrayMetadataV2" in metadata_exports.__all__
assert "ArrayV2Metadata" in metadata_exports.__all__
assert ArrayMetadataV2.__name__ == "ArrayMetadataV2"

metadata = ArrayMetadataV2(shape=(8,), chunks=(4,), dtype=Float64(), fill_value=0.0, order="C")
assert type(metadata) is ArrayMetadataV2
assert isinstance(metadata, (ArrayV2Metadata, ArrayMetadata))
match metadata:
case ArrayV2Metadata():
pass
case _:
pytest.fail("Legacy V2 class pattern no longer matches")

restored = ArrayV2Metadata.from_dict(metadata.to_dict())
assert type(restored) is ArrayMetadataV2
assert restored == metadata
assert pickle.loads(pickle.dumps(metadata)) == metadata
assert pickle.loads(b"czarr.core.metadata.v2\nArrayV2Metadata\n.") is ArrayMetadataV2
assert metadata.update_shape((12,)).shape == (12,)
assert metadata.update_attributes({"key": "value"}).attributes == {"key": "value"}


def test_parse_zarr_format_valid() -> None:
assert parse_zarr_format(2) == 2

Expand Down
32 changes: 32 additions & 0 deletions tests/test_metadata/test_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
from __future__ import annotations

import json
import pickle
import re
from typing import TYPE_CHECKING

import pytest

import zarr.core.metadata as metadata_exports
import zarr.core.metadata.v3 as metadata_v3
from tests.conftest import Expect, ExpectFail
from tests.test_metadata.conftest import minimal_metadata_dict_v3
from zarr.core.buffer import default_buffer_prototype
from zarr.core.chunk_grids import ChunkGrid, is_regular_1d, is_regular_nd
from zarr.core.config import config
from zarr.core.dtype import Float64, UInt8
from zarr.core.group import GroupMetadata, parse_node_type
from zarr.core.metadata import ArrayMetadata, ArrayMetadataV3
from zarr.core.metadata.v2 import ArrayV2Metadata
from zarr.core.metadata.v3 import (
ARRAY_METADATA_KEYS,
Expand All @@ -37,6 +41,34 @@
from typing import Any


def test_array_metadata_v3_names_are_compatible() -> None:
assert metadata_v3.ArrayMetadataV3 is ArrayMetadataV3
assert metadata_v3.ArrayV3Metadata is ArrayV3Metadata is ArrayMetadataV3
assert "ArrayMetadataV3" in metadata_exports.__all__
assert "ArrayV3Metadata" in metadata_exports.__all__
assert ArrayMetadataV3.__name__ == "ArrayMetadataV3"

metadata = ArrayMetadataV3.from_dict(
minimal_metadata_dict_v3(data_type="float64", fill_value="NaN") # type: ignore[arg-type]
)
assert type(metadata) is ArrayMetadataV3
assert isinstance(metadata, (ArrayV3Metadata, ArrayMetadata))
match metadata:
case ArrayV3Metadata():
pass
case _:
pytest.fail("Legacy V3 class pattern no longer matches")

restored = ArrayV3Metadata.from_dict(metadata.to_dict())
assert type(restored) is ArrayMetadataV3
assert restored == metadata
assert hash(restored) == hash(metadata)
assert pickle.loads(pickle.dumps(metadata)) == metadata
assert pickle.loads(b"czarr.core.metadata.v3\nArrayV3Metadata\n.") is ArrayMetadataV3
assert metadata.update_shape((8, 8)).shape == (8, 8)
assert metadata.update_attributes({"key": "value"}).attributes == {"key": "value"}


# ---------------------------------------------------------------------------
# Parsing helpers
# ---------------------------------------------------------------------------
Expand Down
Loading