Skip to content

zarr_metadata.model

zarr_metadata.model

In-memory models for Zarr metadata documents.

A model holds a metadata document (as written, then refined) and the scope the document was read in. Everything a model returns is read-only.

Every document type has a reader: read_array_metadata_v3, read_group_metadata_v3, read_consolidated_metadata_v3, read_array_metadata_v2, read_group_metadata_v2 and read_consolidated_metadata_v2. A reader checks a document's JSON structure. In a v3 document it also reads each extension point (codecs, chunk grids, data types, ...) through the definition that claims its name in a scope. The scope is CORE_AND_EXTENSIONS unless a context is passed. A v3 reader checks the fill value against the data type the document names, the chunk grid against the shape, and the codecs as a pipeline, each codec against the chunk it receives. Each reader does one read and returns a reading: what it read, every problem in problems (a tuple of ValidationProblem, each with a machine-readable kind), and the model in metadata when there are no problems. A reader is the form of the model's constructor that returns problems as values instead of raising them. A node of either type is read by read_node_metadata_v3, from a zarr.json and its node_type, or by read_node_metadata_v2, from the node's decoded .zarray or .zgroup and .zattrs, which can come from a store or from a .zmetadata. To read a store that another writer made with a known writer bug, use read_repaired_node_metadata_v3 or read_repaired_node_metadata_v2. They undo each bug with repair_node_metadata_v3 or repair_node_metadata_v2 before the strict read, and report what they changed. zarr_metadata.json_schema writes a JSON Schema of what the v3 readers read, except for the rules. One field on its own is read with zarr_metadata.definition.resolve.

The model constructors from_json and from_key_value raise MetadataValidationError for every ingestion failure, including missing store keys and undecodable bytes. The v3 constructors take the same context. A v3 model is its document plus the scope it was read in. ZarrV3ArrayMetadata(document, context=None) reads the document in the scope and raises MetadataValidationError with every problem, so an invalid model is never built. to_json returns the document as written. update reads new members in the model's own scope. with_context and refined_in read the document in another scope. to_key_value writes the model as it is. A v3 group reads its consolidated_metadata as an envelope and does not read the documents it lists, because consolidated metadata is a convention of the reference implementation, not core metadata. ZarrV3ConsolidatedMetadata and read_consolidated_metadata_v3 read those documents. The model also accepts node models as entries: an entry is accepted when its claims refine into the model's scope, and refused with a problem at its path otherwise. Every reader takes context=None for the default scope.

RepairKind module-attribute

RepairKind: TypeAlias = Literal[
    "zero_chunk_length",
    "null_consolidated_metadata",
    "consolidated_metadata_in_zgroup_entry",
    "non_finite_fill_value",
]

The name of each writer bug that a repair undoes.

UNSET module-attribute

UNSET = Sentinel('UNSET')

Marks a metadata-document key as absent (PEP 661 sentinel; usable directly in type expressions, e.g. tuple[str, ...] | UNSET). Test with is UNSET.

ZarrV2NodeMetadata module-attribute

ZarrV2NodeMetadata = TypeAliasType(
    "ZarrV2NodeMetadata",
    ZarrV2ArrayMetadata | ZarrV2GroupMetadata,
)

The model of one v2 node, as node_metadata_from_key_value_v2 returns it and a .zmetadata holds it: an array or a group.

ZarrV2NodeMetadataReading module-attribute

ZarrV2NodeMetadataReading = TypeAliasType(
    "ZarrV2NodeMetadataReading",
    ZarrV2ArrayMetadataReading
    | ZarrV2GroupMetadataReading
    | ZarrV2UnknownNodeMetadataReading,
)

A v2 node as read_node_metadata_v2 reads it from a store: as the array or group its files declare, or as neither.

ZarrV3NodeMetadata module-attribute

ZarrV3NodeMetadata = TypeAliasType(
    "ZarrV3NodeMetadata",
    ZarrV3ArrayMetadata | ZarrV3GroupMetadata,
)

The model of a v3 zarr.json: an array model or a group model, as its node_type declares.

ZarrV3NodeMetadataInput module-attribute

ZarrV3NodeMetadataInput = TypeAliasType(
    "ZarrV3NodeMetadataInput",
    "ZarrV3ArrayMetadataJSON | ZarrV3GroupMetadataJSON | ZarrV3ArrayMetadata | ZarrV3GroupMetadata",
)

An entry of consolidated metadata, at a path, as ZarrV3ConsolidatedMetadata accepts it: a document, or a model of one.

An Input type is what a constructor accepts. A JSON TypedDict is what a document writes, and an Update type is what update replaces. Of these three, only an Input may hold a model.

ZarrV3NodeMetadataReading module-attribute

ZarrV3NodeMetadataReading = TypeAliasType(
    "ZarrV3NodeMetadataReading",
    ZarrV3ArrayMetadataReading
    | ZarrV3GroupMetadataReading
    | ZarrV3UnknownNodeMetadataReading,
)

A v3 zarr.json as read_node_metadata_v3 reads it: as the array or group its node_type declares, or as neither.

__all__ module-attribute

__all__ = [
    "UNSET",
    "Repair",
    "RepairKind",
    "ZarrV2ArrayMetadata",
    "ZarrV2ArrayMetadataReading",
    "ZarrV2ArrayMetadataUpdate",
    "ZarrV2ConsolidatedMetadata",
    "ZarrV2ConsolidatedMetadataReading",
    "ZarrV2GroupMetadata",
    "ZarrV2GroupMetadataReading",
    "ZarrV2GroupMetadataUpdate",
    "ZarrV2NodeMetadata",
    "ZarrV2NodeMetadataReading",
    "ZarrV2RepairedConsolidatedMetadataReading",
    "ZarrV2RepairedNodeMetadataReading",
    "ZarrV2UnknownNodeMetadataReading",
    "ZarrV3ArrayMetadata",
    "ZarrV3ArrayMetadataReading",
    "ZarrV3ArrayMetadataUpdate",
    "ZarrV3ConsolidatedMetadata",
    "ZarrV3ConsolidatedMetadataInput",
    "ZarrV3ConsolidatedMetadataReading",
    "ZarrV3GroupMetadata",
    "ZarrV3GroupMetadataReading",
    "ZarrV3GroupMetadataUpdate",
    "ZarrV3NodeMetadata",
    "ZarrV3NodeMetadataInput",
    "ZarrV3NodeMetadataReading",
    "ZarrV3RepairedNodeMetadataReading",
    "ZarrV3UnknownNodeMetadataReading",
    "is_node_name_v3",
    "is_node_path_v3",
    "node_metadata_from_json_v3",
    "node_metadata_from_key_value_v2",
    "node_metadata_from_key_value_v3",
    "parse_node_name_v3",
    "parse_node_path_v3",
    "read_array_metadata_v2",
    "read_array_metadata_v3",
    "read_consolidated_metadata_v2",
    "read_consolidated_metadata_v3",
    "read_group_metadata_v2",
    "read_group_metadata_v3",
    "read_node_metadata_v2",
    "read_node_metadata_v3",
    "read_repaired_consolidated_metadata_v2",
    "read_repaired_node_metadata_v2",
    "read_repaired_node_metadata_v3",
    "repair_consolidated_metadata_v2",
    "repair_node_metadata_v2",
    "repair_node_metadata_v3",
    "validate_node_name_v3",
    "validate_node_path_v3",
]

Repair dataclass

One change a repair made to a document: its location, the bug it undid, and what it changed.

Source code in src/zarr_metadata/model/_repair.py
@dataclass(frozen=True, slots=True)
class Repair:
    """One change a repair made to a document: its location, the bug it undid, and what it changed."""

    loc: Loc
    """The location of the change in the document: the value that changed, or the key that was removed."""
    kind: RepairKind
    """The writer bug the change undoes."""
    message: str
    """What was written, which writer wrote it, and what it was changed to."""

kind instance-attribute

kind: RepairKind

The writer bug the change undoes.

loc instance-attribute

loc: Loc

The location of the change in the document: the value that changed, or the key that was removed.

message instance-attribute

message: str

What was written, which writer wrote it, and what it was changed to.

__init__

__init__(loc: Loc, kind: RepairKind, message: str) -> None

ZarrV2ArrayMetadata

Bases: Keyed

A v2 array document, and the scope it was read in.

Like the v3 models, this model is a pair. to_json returns the merged document (the .zarray members, plus attributes when a .zattrs holds them) as written, refined. A .zarray that omits dimension_separator means "." by the v2 convention (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L81-L86): the dimension_separator property returns ".", and the model compares equal to one whose document writes ".", but to_json writes the document without the key. dtype, compressor and each filter are views of the reading: an AcceptedField read by the definition in scope that claims the typestr or id, or an UnclaimedField. attributes is empty when no .zattrs exists, as in a v3 model. The document still tells the two apart: to_json and to_key_value write attributes and .zattrs only when a .zattrs exists, even an empty one. A member the spec does not define is kept in extra_fields. A model is only built by reading: the constructor reads document in context and raises MetadataValidationError with every problem, so no model is invalid. Two models are equal when their documents mean the same thing in their scopes, as the model key defines. update reads new members in the model's own scope. with_context and refined_in read the document in another scope. A model pickles as its pair.

Source code in src/zarr_metadata/model/_array_v2.py
class ZarrV2ArrayMetadata(Keyed):
    """A v2 array document, and the scope it was read in.

    Like the v3 models, this model is a pair. `to_json` returns the merged
    document (the `.zarray` members, plus `attributes` when a `.zattrs`
    holds them) as written, refined. A `.zarray` that omits
    `dimension_separator` means `"."` by the v2 convention
    (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L81-L86):
    the `dimension_separator` property returns `"."`, and the model compares
    equal to one whose document writes `"."`, but `to_json` writes the
    document without the key. `dtype`, `compressor` and
    each filter are views of the reading: an `AcceptedField` read by the
    definition in scope that claims the typestr or id, or an
    `UnclaimedField`. `attributes` is empty when no `.zattrs` exists, as
    in a v3 model. The document still tells the two apart: `to_json` and
    `to_key_value` write `attributes` and `.zattrs` only when a `.zattrs`
    exists, even an empty one. A member the spec does not define is kept
    in `extra_fields`. A model is only built by reading:
    the constructor reads `document` in `context` and raises
    `MetadataValidationError` with every problem, so no model is invalid.
    Two models are equal when their documents mean the same thing in
    their scopes, as the model key defines. `update` reads new members
    in the model's own scope. `with_context` and `refined_in` read the
    document in another scope. A model pickles as its pair.
    """

    __slots__ = ("_context", "_document", "_members", "_reading", "_shown")

    zarr_format: Final[Literal[2]] = 2  # noqa: PYI064 - a bare Final does not narrow a union of models
    node_type: Final[Literal["array"]] = "array"  # noqa: PYI064 - as above

    def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
        context = scoped(context, CORE_V2)
        reading, members = read_array_v2(document, context)
        if members is None:
            raise MetadataValidationError(reading.problems)
        self._adopt(members.document, context, reading, members)

    @classmethod
    def _of(
        cls,
        document: dict[str, JSONValue],
        context: ZarrV2Context,
        reading: ZarrV2ArrayMetadataReading,
        members: ArrayMembersV2,
    ) -> ZarrV2ArrayMetadata:
        """Build a model of a document that a read found no problems with, holding that reading, without reading it again. The readers in this package build models through this method; pyright reports these calls as private use."""
        model = object.__new__(cls)
        model._adopt(document, context, reading, members)
        return model

    def _adopt(
        self,
        document: dict[str, JSONValue],
        context: ZarrV2Context,
        reading: ZarrV2ArrayMetadataReading,
        members: ArrayMembersV2,
    ) -> None:
        self._document = document
        self._context = context
        self._reading = dataclasses.replace(reading, metadata=self)
        self._members = members
        # The members the model exposes, read-only at every level.
        self._shown = (
            frozen(members.fill_value),
            frozen({} if members.attributes is UNSET else members.attributes),
            frozen(members.extra_fields),
        )

    # --- the pair ---------------------------------------------------------

    @property
    def context(self) -> ZarrV2Context:
        """The scope the document was read in. `update` reads new members in this scope."""
        return self._context

    @property
    def reading(self) -> ZarrV2ArrayMetadataReading:
        """The document as the scope read it: the dtype, the compressor, and each filter."""
        return self._reading

    def to_json(self) -> ZarrV2ArrayMetadataJSON:
        """The merged document as written, refined. The result shares no objects with the model.

        `attributes` is included when set, even empty. This is not the
        on-disk `.zarray`, which excludes them: `to_key_value` splits the
        document as a store holds it
        (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L323-L330).
        """
        return cast("ZarrV2ArrayMetadataJSON", copied(self._document))

    def to_key_value(
        self, *, indent: int | str | None = None
    ) -> Mapping[ZarrV2ArrayMetadataStoreKey | ZarrV2AttributesStoreKey, bytes]:
        """The document as a store holds it: `.zarray` without the attributes, and `.zattrs` with the attributes when they are set, even if empty."""
        zarray = {key: value for key, value in self._document.items() if key != "attributes"}
        out: dict[ZarrV2ArrayMetadataStoreKey | ZarrV2AttributesStoreKey, bytes] = {
            ZARR_V2_ARRAY_METADATA_STORE_KEY: dump_store_json(zarray, indent=indent)
        }
        if "attributes" in self._document:
            out[ZARR_V2_ATTRIBUTES_STORE_KEY] = dump_store_json(
                self._document["attributes"], indent=indent
            )
        return out

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

    def _key_of(self) -> tuple[object, ...]:
        """The key `==` and `hash` compare for a v2 array model: what its document means.

        The key holds each field's `field_key`, and the fill value as JSON
        text, in its canonical spelling when a definition in scope read the
        dtype. Every other member is included as it is, with JSON members
        as text. No `.zattrs` counts as empty attributes, since both mean
        that the array has none, as in v3.
        """
        members = self._members
        return (
            members.shape,
            members.chunks,
            members.order,
            members.dimension_separator,
            self._fill_value_key(),
            field_key(self.dtype),
            None if self.compressor is None else field_key(self.compressor),
            None if self.filters is None else tuple(field_key(entry) for entry in self.filters),
            json_text({} if members.attributes is UNSET else members.attributes),
            json_text(members.extra_fields),
        )

    def _plain_key(
        self, dtype: AcceptedField[ZarrV2DataTypeDefinition[Any]] | UnclaimedField
    ) -> tuple[object, ...]:
        """The members other than fields that `refines` compares. The fill value is in the spelling of `dtype`, the dtype of the more informed side."""
        members = self._members
        return (
            members.shape,
            members.chunks,
            members.order,
            members.dimension_separator,
            json_text(spelled_canonically(dtype, members.fill_value)),
            json_text({} if members.attributes is UNSET else members.attributes),
            json_text(members.extra_fields),
        )

    def _fill_value_key(self) -> str:
        """The fill value as `==` compares it, as JSON text: in its canonical spelling when a definition in scope read the dtype, and as written otherwise."""
        fill_value = self._members.fill_value
        if isinstance(self.dtype, AcceptedField):
            return json_text(spelled_canonically(self.dtype, fill_value))
        return json_text(fill_value)

    def __reduce__(self) -> tuple[type[ZarrV2ArrayMetadata], tuple[object, ZarrV2Context]]:
        return type(self), (self._document, self._context)

    # --- typed views ------------------------------------------------------

    @property
    def shape(self) -> tuple[int, ...]:
        """The array's shape."""
        return self._members.shape

    @property
    def chunks(self) -> tuple[int, ...]:
        """The shape of each chunk."""
        return self._members.chunks

    @property
    def fill_value(self) -> JSONValue:
        """The fill value as written, refined; read-only at every level."""
        return self._shown[0]

    @property
    def order(self) -> ZarrV2ArrayOrder:
        """The in-chunk layout, `"C"` or `"F"`."""
        return self._members.order

    @property
    def dimension_separator(self) -> ZarrV2ArrayDimensionSeparator:
        """The separator between chunk indices in a chunk key: `"."` when the document writes none."""
        return self._members.dimension_separator

    @property
    def unclaimed_fields(self) -> dict[Loc, UnclaimedField]:
        """Every field nothing in scope claims, its dtype or a codec, located as `reading.fields()` locates it: at its place in the document, except that a struct dtype's field types are located by record and position, as their problems are.

        This answers whether a reader can open the array: a reader cannot
        decode data with a dtype or a codec it does not understand, and the
        scope is what the reader understands. The model only reports these
        fields; refusing to open is up to the reader.
        """
        return unclaimed_fields_of(self._reading.fields(), ())

    @property
    def attributes(self) -> Mapping[str, JSONValue]:
        """The user attributes a `.zattrs` holds, read-only at every level; empty when there is no `.zattrs`."""
        return self._shown[1]

    @property
    def extra_fields(self) -> Mapping[str, JSONValue]:
        """Every member the spec does not define, as written, read-only at every level."""
        return self._shown[2]

    @property
    def dtype(self) -> AcceptedField[ZarrV2DataTypeDefinition[Any]] | UnclaimedField:
        """The dtype as the scope read it: read by its family's definition, or unclaimed."""
        return held(self._reading.dtype)

    @property
    def compressor(self) -> AcceptedField[ZarrV2CodecDefinition[Any]] | UnclaimedField | None:
        """The compressor as the scope read it; None when written as `null`."""
        compressor = self._reading.compressor
        return None if compressor is None else held(compressor)

    @property
    def filters(
        self,
    ) -> tuple[AcceptedField[ZarrV2CodecDefinition[Any]] | UnclaimedField, ...] | None:
        """The filters, each as the scope read it; None when written as `null`."""
        filters = self._reading.filters
        if filters is None:
            return None
        if filters is UNSET:
            msg = "expected filters a read found nothing wrong with, got UNSET"
            raise TypeError(msg)
        return tuple(held(entry) for entry in filters)

    # --- changing ---------------------------------------------------------

    def update(self, **members: Unpack[ZarrV2ArrayMetadataUpdate]) -> ZarrV2ArrayMetadata:
        """Return this model with `members` (as JSON) replacing the document's members, read in this model's own scope. A member set to `UNSET` is removed.

        Raises `MetadataValidationError` when the resulting document has a
        problem. So members that depend on each other must be passed
        together, for example a `dtype` with a fill value of that dtype.
        """
        changes = member_changes(
            members, ZarrV2ArrayMetadataUpdate, _ARRAY_METADATA_STANDARD_KEYS_V2
        )
        document: dict[str, object] = {**self._document, **changes}
        for key, value in changes.items():
            if value is UNSET:
                del document[key]
        return type(self)(document, context=self._context)

    def with_context(self, context: ZarrV2Context | None = None) -> ZarrV2ArrayMetadata:
        """Return this document read in `context`, whatever that changes: a gain, a loss, or a conflict.

        Raises `MetadataValidationError` when the document has a problem
        in `context`. The existing reading is reused when `context` reads
        every claim identically.
        """
        context = scoped(context, CORE_V2)
        if disagreements(context, claims_of(self._reading.fields())).agrees:
            return self._of(self._document, context, self._reading, self._members)
        return type(self)(self._document, context=context)

    def refined_in(self, context: ZarrV2Context | None = None) -> ZarrV2ArrayMetadata:
        """Return this document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

        Raises `ScopeConflictError` when `context` reads a typestr or id
        with a different definition, or with no definition, where this
        scope read it with one. The error names each one and its location
        in the document. Raises `MetadataValidationError` when a definition
        that `context` claims rejects what was written.
        """
        context = scoped(context, CORE_V2)
        found = disagreements(context, claims_of(self._reading.fields()))
        if len(found.conflicts) != 0:
            raise ScopeConflictError(located_conflicts(self._reading.fields(), found.conflicts))
        return self.with_context(context)

    def refines(self, other: ZarrV2ArrayMetadata) -> bool:
        """Whether this model holds everything `other` holds. Each field must refine its counterpart. A `null` compressor or `null` filters refine only a `null`. Every other member must be the same, with the fill value compared in the spelling of the more informed dtype. If that dtype rejects the other model's fill value, this is not a refinement."""
        if type(other) is not type(self):
            return False
        if (self.compressor is None) != (other.compressor is None):
            return False
        if (self.filters is None) != (other.filters is None):
            return False
        mine = () if self.filters is None else self.filters
        theirs = () if other.filters is None else other.filters
        if len(mine) != len(theirs):
            return False
        pairs = [(self.dtype, other.dtype), *zip(mine, theirs, strict=True)]
        if self.compressor is not None and other.compressor is not None:
            pairs.append((self.compressor, other.compressor))
        if not all(refines_field(one, another) for one, another in pairs):
            return False
        if len(fill_value_problems(self.dtype, other._members.fill_value)) != 0:
            return False
        return self._plain_key(self.dtype) == other._plain_key(self.dtype)

    # --- constructors -----------------------------------------------------

    @classmethod
    def create_default(
        cls, *, context: ZarrV2Context | None = None, **overrides: Unpack[ZarrV2ArrayMetadataUpdate]
    ) -> ZarrV2ArrayMetadata:
        """Return a scalar `|u1` array, with any document members in `overrides` applied, read in `context`.

        Raises `MetadataValidationError` when the resulting document has a
        problem. Overriding `shape` without `chunks` sets `chunks` equal to
        `shape`, so one chunk covers the array. Overriding `chunks` without
        `shape` keeps the scalar default shape, which chunks of another
        rank do not fit. A dtype given without a fill value gets a fill
        value of `0` when its family accepts `0`, and `null` otherwise.
        Every family accepts `null`.
        """
        document: dict[str, object] = {
            "zarr_format": 2,
            "shape": (),
            "chunks": (),
            "dtype": "|u1",
            "fill_value": 0,
            "order": "C",
            "compressor": None,
            "filters": None,
            "dimension_separator": ".",
        }
        given = member_changes(
            overrides, ZarrV2ArrayMetadataUpdate, _ARRAY_METADATA_STANDARD_KEYS_V2
        )
        if "shape" in given and "chunks" not in given:
            lengths, _ = dimension_lengths(given, "shape")
            if lengths is not None:
                given["chunks"] = lengths
        if "dtype" in given and "fill_value" not in given:
            dtype, _ = resolve(given["dtype"], ZarrV2DataTypeDefinition, scoped(context, CORE_V2))
            if len(fill_value_problems(dtype, 0)) != 0:
                given["fill_value"] = None
        merged = {key: value for key, value in {**document, **given}.items() if value is not UNSET}
        return cls(merged, context=context)

    @classmethod
    def from_json(
        cls, data: object, *, context: ZarrV2Context | None = None
    ) -> ZarrV2ArrayMetadata:
        """Return the model of `data`, a v2 array document with its attributes under `attributes`, read in `context`.

        Raises `MetadataValidationError` with every problem the read finds.
        `read_array_metadata_v2` returns the reading this model is built
        from, and the problems of a document that has any.
        """
        return cls(data, context=context)

    @classmethod
    def from_key_value(
        cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV2Context | None = None
    ) -> ZarrV2ArrayMetadata:
        """Return the model of the array at `.zarray` in `mapping`, with the attributes at `.zattrs` when that key exists, read in `context`.

        Raises `MetadataValidationError` when `.zarray` is missing, bytes
        are not JSON, `.zarray` holds `attributes`, or the document is not
        valid, with every problem the read finds. Raises `TypeError` for a
        scope of another format before any bytes are decoded.
        """
        context = scoped(context, CORE_V2)
        files, problems = decoded_node_files_v2(
            mapping, (ZARR_V2_ARRAY_METADATA_STORE_KEY, ZARR_V2_ATTRIBUTES_STORE_KEY)
        )
        if len(problems) != 0:
            raise MetadataValidationError(problems)
        document, problems = node_document_v2(
            files, ZARR_V2_ARRAY_METADATA_STORE_KEY, ARRAY_FILE_ATTRIBUTES
        )
        if len(problems) != 0:
            if document is not None:
                problems = (*problems, *read_array_metadata_v2(document, context=context).problems)
            raise MetadataValidationError(problems)
        return cls(document, context=context)

__slots__ class-attribute instance-attribute

__slots__ = (
    "_context",
    "_document",
    "_members",
    "_reading",
    "_shown",
)

attributes property

attributes: Mapping[str, JSONValue]

The user attributes a .zattrs holds, read-only at every level; empty when there is no .zattrs.

chunks property

chunks: tuple[int, ...]

The shape of each chunk.

compressor property

The compressor as the scope read it; None when written as null.

context property

context: ZarrV2Context

The scope the document was read in. update reads new members in this scope.

dimension_separator property

dimension_separator: ZarrV2ArrayDimensionSeparator

The separator between chunk indices in a chunk key: "." when the document writes none.

dtype property

The dtype as the scope read it: read by its family's definition, or unclaimed.

extra_fields property

extra_fields: Mapping[str, JSONValue]

Every member the spec does not define, as written, read-only at every level.

fill_value property

fill_value: JSONValue

The fill value as written, refined; read-only at every level.

filters property

filters: (
    tuple[
        AcceptedField[ZarrV2CodecDefinition[Any]]
        | UnclaimedField,
        ...,
    ]
    | None
)

The filters, each as the scope read it; None when written as null.

node_type class-attribute instance-attribute

node_type: Final[Literal['array']] = 'array'

order property

The in-chunk layout, "C" or "F".

reading property

The document as the scope read it: the dtype, the compressor, and each filter.

shape property

shape: tuple[int, ...]

The array's shape.

unclaimed_fields property

unclaimed_fields: dict[Loc, UnclaimedField]

Every field nothing in scope claims, its dtype or a codec, located as reading.fields() locates it: at its place in the document, except that a struct dtype's field types are located by record and position, as their problems are.

This answers whether a reader can open the array: a reader cannot decode data with a dtype or a codec it does not understand, and the scope is what the reader understands. The model only reports these fields; refusing to open is up to the reader.

zarr_format class-attribute instance-attribute

zarr_format: Final[Literal[2]] = 2

__eq__

__eq__(other: object) -> bool
Source code in src/zarr_metadata/model/_keyed.py
def __eq__(self, other: object) -> bool:
    if type(other) is not type(self):
        return NotImplemented
    return self._model_key == other._model_key

__hash__

__hash__() -> int
Source code in src/zarr_metadata/model/_keyed.py
def __hash__(self) -> int:
    return hash(self._model_key)

__init__

__init__(
    document: object, context: ZarrV2Context | None = None
) -> None
Source code in src/zarr_metadata/model/_array_v2.py
def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
    context = scoped(context, CORE_V2)
    reading, members = read_array_v2(document, context)
    if members is None:
        raise MetadataValidationError(reading.problems)
    self._adopt(members.document, context, reading, members)

__reduce__

Source code in src/zarr_metadata/model/_array_v2.py
def __reduce__(self) -> tuple[type[ZarrV2ArrayMetadata], tuple[object, ZarrV2Context]]:
    return type(self), (self._document, self._context)

__repr__

__repr__() -> str
Source code in src/zarr_metadata/model/_array_v2.py
def __repr__(self) -> str:
    return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

create_default classmethod

create_default(
    *,
    context: ZarrV2Context | None = None,
    **overrides: Unpack[ZarrV2ArrayMetadataUpdate],
) -> ZarrV2ArrayMetadata

Return a scalar |u1 array, with any document members in overrides applied, read in context.

Raises MetadataValidationError when the resulting document has a problem. Overriding shape without chunks sets chunks equal to shape, so one chunk covers the array. Overriding chunks without shape keeps the scalar default shape, which chunks of another rank do not fit. A dtype given without a fill value gets a fill value of 0 when its family accepts 0, and null otherwise. Every family accepts null.

Source code in src/zarr_metadata/model/_array_v2.py
@classmethod
def create_default(
    cls, *, context: ZarrV2Context | None = None, **overrides: Unpack[ZarrV2ArrayMetadataUpdate]
) -> ZarrV2ArrayMetadata:
    """Return a scalar `|u1` array, with any document members in `overrides` applied, read in `context`.

    Raises `MetadataValidationError` when the resulting document has a
    problem. Overriding `shape` without `chunks` sets `chunks` equal to
    `shape`, so one chunk covers the array. Overriding `chunks` without
    `shape` keeps the scalar default shape, which chunks of another
    rank do not fit. A dtype given without a fill value gets a fill
    value of `0` when its family accepts `0`, and `null` otherwise.
    Every family accepts `null`.
    """
    document: dict[str, object] = {
        "zarr_format": 2,
        "shape": (),
        "chunks": (),
        "dtype": "|u1",
        "fill_value": 0,
        "order": "C",
        "compressor": None,
        "filters": None,
        "dimension_separator": ".",
    }
    given = member_changes(
        overrides, ZarrV2ArrayMetadataUpdate, _ARRAY_METADATA_STANDARD_KEYS_V2
    )
    if "shape" in given and "chunks" not in given:
        lengths, _ = dimension_lengths(given, "shape")
        if lengths is not None:
            given["chunks"] = lengths
    if "dtype" in given and "fill_value" not in given:
        dtype, _ = resolve(given["dtype"], ZarrV2DataTypeDefinition, scoped(context, CORE_V2))
        if len(fill_value_problems(dtype, 0)) != 0:
            given["fill_value"] = None
    merged = {key: value for key, value in {**document, **given}.items() if value is not UNSET}
    return cls(merged, context=context)

from_json classmethod

from_json(
    data: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ArrayMetadata

Return the model of data, a v2 array document with its attributes under attributes, read in context.

Raises MetadataValidationError with every problem the read finds. read_array_metadata_v2 returns the reading this model is built from, and the problems of a document that has any.

Source code in src/zarr_metadata/model/_array_v2.py
@classmethod
def from_json(
    cls, data: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ArrayMetadata:
    """Return the model of `data`, a v2 array document with its attributes under `attributes`, read in `context`.

    Raises `MetadataValidationError` with every problem the read finds.
    `read_array_metadata_v2` returns the reading this model is built
    from, and the problems of a document that has any.
    """
    return cls(data, context=context)

from_key_value classmethod

from_key_value(
    mapping: Mapping[StoreKey, bytes],
    *,
    context: ZarrV2Context | None = None,
) -> ZarrV2ArrayMetadata

Return the model of the array at .zarray in mapping, with the attributes at .zattrs when that key exists, read in context.

Raises MetadataValidationError when .zarray is missing, bytes are not JSON, .zarray holds attributes, or the document is not valid, with every problem the read finds. Raises TypeError for a scope of another format before any bytes are decoded.

Source code in src/zarr_metadata/model/_array_v2.py
@classmethod
def from_key_value(
    cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV2Context | None = None
) -> ZarrV2ArrayMetadata:
    """Return the model of the array at `.zarray` in `mapping`, with the attributes at `.zattrs` when that key exists, read in `context`.

    Raises `MetadataValidationError` when `.zarray` is missing, bytes
    are not JSON, `.zarray` holds `attributes`, or the document is not
    valid, with every problem the read finds. Raises `TypeError` for a
    scope of another format before any bytes are decoded.
    """
    context = scoped(context, CORE_V2)
    files, problems = decoded_node_files_v2(
        mapping, (ZARR_V2_ARRAY_METADATA_STORE_KEY, ZARR_V2_ATTRIBUTES_STORE_KEY)
    )
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    document, problems = node_document_v2(
        files, ZARR_V2_ARRAY_METADATA_STORE_KEY, ARRAY_FILE_ATTRIBUTES
    )
    if len(problems) != 0:
        if document is not None:
            problems = (*problems, *read_array_metadata_v2(document, context=context).problems)
        raise MetadataValidationError(problems)
    return cls(document, context=context)

refined_in

refined_in(
    context: ZarrV2Context | None = None,
) -> ZarrV2ArrayMetadata

Return this document read in context. context may claim names this scope left unclaimed, but must not contradict any claim.

Raises ScopeConflictError when context reads a typestr or id with a different definition, or with no definition, where this scope read it with one. The error names each one and its location in the document. Raises MetadataValidationError when a definition that context claims rejects what was written.

Source code in src/zarr_metadata/model/_array_v2.py
def refined_in(self, context: ZarrV2Context | None = None) -> ZarrV2ArrayMetadata:
    """Return this document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

    Raises `ScopeConflictError` when `context` reads a typestr or id
    with a different definition, or with no definition, where this
    scope read it with one. The error names each one and its location
    in the document. Raises `MetadataValidationError` when a definition
    that `context` claims rejects what was written.
    """
    context = scoped(context, CORE_V2)
    found = disagreements(context, claims_of(self._reading.fields()))
    if len(found.conflicts) != 0:
        raise ScopeConflictError(located_conflicts(self._reading.fields(), found.conflicts))
    return self.with_context(context)

refines

refines(other: ZarrV2ArrayMetadata) -> bool

Whether this model holds everything other holds. Each field must refine its counterpart. A null compressor or null filters refine only a null. Every other member must be the same, with the fill value compared in the spelling of the more informed dtype. If that dtype rejects the other model's fill value, this is not a refinement.

Source code in src/zarr_metadata/model/_array_v2.py
def refines(self, other: ZarrV2ArrayMetadata) -> bool:
    """Whether this model holds everything `other` holds. Each field must refine its counterpart. A `null` compressor or `null` filters refine only a `null`. Every other member must be the same, with the fill value compared in the spelling of the more informed dtype. If that dtype rejects the other model's fill value, this is not a refinement."""
    if type(other) is not type(self):
        return False
    if (self.compressor is None) != (other.compressor is None):
        return False
    if (self.filters is None) != (other.filters is None):
        return False
    mine = () if self.filters is None else self.filters
    theirs = () if other.filters is None else other.filters
    if len(mine) != len(theirs):
        return False
    pairs = [(self.dtype, other.dtype), *zip(mine, theirs, strict=True)]
    if self.compressor is not None and other.compressor is not None:
        pairs.append((self.compressor, other.compressor))
    if not all(refines_field(one, another) for one, another in pairs):
        return False
    if len(fill_value_problems(self.dtype, other._members.fill_value)) != 0:
        return False
    return self._plain_key(self.dtype) == other._plain_key(self.dtype)

to_json

The merged document as written, refined. The result shares no objects with the model.

attributes is included when set, even empty. This is not the on-disk .zarray, which excludes them: to_key_value splits the document as a store holds it (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L323-L330).

Source code in src/zarr_metadata/model/_array_v2.py
def to_json(self) -> ZarrV2ArrayMetadataJSON:
    """The merged document as written, refined. The result shares no objects with the model.

    `attributes` is included when set, even empty. This is not the
    on-disk `.zarray`, which excludes them: `to_key_value` splits the
    document as a store holds it
    (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L323-L330).
    """
    return cast("ZarrV2ArrayMetadataJSON", copied(self._document))

to_key_value

to_key_value(
    *, indent: int | str | None = None
) -> Mapping[
    ZarrV2ArrayMetadataStoreKey | ZarrV2AttributesStoreKey,
    bytes,
]

The document as a store holds it: .zarray without the attributes, and .zattrs with the attributes when they are set, even if empty.

Source code in src/zarr_metadata/model/_array_v2.py
def to_key_value(
    self, *, indent: int | str | None = None
) -> Mapping[ZarrV2ArrayMetadataStoreKey | ZarrV2AttributesStoreKey, bytes]:
    """The document as a store holds it: `.zarray` without the attributes, and `.zattrs` with the attributes when they are set, even if empty."""
    zarray = {key: value for key, value in self._document.items() if key != "attributes"}
    out: dict[ZarrV2ArrayMetadataStoreKey | ZarrV2AttributesStoreKey, bytes] = {
        ZARR_V2_ARRAY_METADATA_STORE_KEY: dump_store_json(zarray, indent=indent)
    }
    if "attributes" in self._document:
        out[ZARR_V2_ATTRIBUTES_STORE_KEY] = dump_store_json(
            self._document["attributes"], indent=indent
        )
    return out

update

update(
    **members: Unpack[ZarrV2ArrayMetadataUpdate],
) -> ZarrV2ArrayMetadata

Return this model with members (as JSON) replacing the document's members, read in this model's own scope. A member set to UNSET is removed.

Raises MetadataValidationError when the resulting document has a problem. So members that depend on each other must be passed together, for example a dtype with a fill value of that dtype.

Source code in src/zarr_metadata/model/_array_v2.py
def update(self, **members: Unpack[ZarrV2ArrayMetadataUpdate]) -> ZarrV2ArrayMetadata:
    """Return this model with `members` (as JSON) replacing the document's members, read in this model's own scope. A member set to `UNSET` is removed.

    Raises `MetadataValidationError` when the resulting document has a
    problem. So members that depend on each other must be passed
    together, for example a `dtype` with a fill value of that dtype.
    """
    changes = member_changes(
        members, ZarrV2ArrayMetadataUpdate, _ARRAY_METADATA_STANDARD_KEYS_V2
    )
    document: dict[str, object] = {**self._document, **changes}
    for key, value in changes.items():
        if value is UNSET:
            del document[key]
    return type(self)(document, context=self._context)

with_context

with_context(
    context: ZarrV2Context | None = None,
) -> ZarrV2ArrayMetadata

Return this document read in context, whatever that changes: a gain, a loss, or a conflict.

Raises MetadataValidationError when the document has a problem in context. The existing reading is reused when context reads every claim identically.

Source code in src/zarr_metadata/model/_array_v2.py
def with_context(self, context: ZarrV2Context | None = None) -> ZarrV2ArrayMetadata:
    """Return this document read in `context`, whatever that changes: a gain, a loss, or a conflict.

    Raises `MetadataValidationError` when the document has a problem
    in `context`. The existing reading is reused when `context` reads
    every claim identically.
    """
    context = scoped(context, CORE_V2)
    if disagreements(context, claims_of(self._reading.fields())).agrees:
        return self._of(self._document, context, self._reading, self._members)
    return type(self)(self._document, context=context)

ZarrV2ArrayMetadataReading dataclass

A v2 array document as a scope read it, whatever it holds: its dtype, compressor and filters, every problem, and the model when there are no problems.

A field the document does not hold is UNSET, and so is a filters that is not an array, which holds no filters to read. A compressor or filters written as null is None.

Source code in src/zarr_metadata/model/_array_v2.py
@dataclass(frozen=True, slots=True)
class ZarrV2ArrayMetadataReading:
    """A v2 array document as a scope read it, whatever it holds: its dtype, compressor and filters, every problem, and the model when there are no problems.

    A field the document does not hold is `UNSET`, and so is a `filters`
    that is not an array, which holds no filters to read. A `compressor`
    or `filters` written as `null` is None.
    """

    dtype: ResolvedField[ZarrV2DataTypeDefinition[Any]] | UNSET = UNSET
    """The dtype, as the scope read it."""
    compressor: ResolvedField[ZarrV2CodecDefinition[Any]] | UNSET | None = UNSET
    """The compressor, as the scope read it; None when written as `null`."""
    filters: tuple[ResolvedField[ZarrV2CodecDefinition[Any]], ...] | UNSET | None = UNSET
    """The filters, each as the scope read it; None when written as `null`."""
    problems: tuple[ValidationProblem, ...] = ()
    """Every reason the document is not valid."""
    metadata: ZarrV2ArrayMetadata | None = None
    """The document's model, which holds these fields, when there is no problem; None otherwise."""

    def __reduce__(self) -> str | tuple[object, ...]:
        # Same as the v3 reading: pickle through the model, when there is one.
        if self.metadata is not None:
            return (reading_of, (self.metadata,))
        return object.__reduce__(self)

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Each field in the document, as the scope read it, with its location: the dtype, followed by a struct's record types, then the compressor, then each filter at its index."""
        if self.dtype is not UNSET:
            yield from fields_of(self.dtype, ("dtype",))
        if self.compressor is not UNSET and self.compressor is not None:
            yield from fields_of(self.compressor, ("compressor",))
        if self.filters is not UNSET and self.filters is not None:
            for index, entry in enumerate(self.filters):
                yield from fields_of(entry, ("filters", index))

compressor class-attribute instance-attribute

compressor: (
    ResolvedField[ZarrV2CodecDefinition[Any]] | UNSET | None
) = UNSET

The compressor, as the scope read it; None when written as null.

dtype class-attribute instance-attribute

The dtype, as the scope read it.

filters class-attribute instance-attribute

filters: (
    tuple[ResolvedField[ZarrV2CodecDefinition[Any]], ...]
    | UNSET
    | None
) = UNSET

The filters, each as the scope read it; None when written as null.

metadata class-attribute instance-attribute

metadata: ZarrV2ArrayMetadata | None = None

The document's model, which holds these fields, when there is no problem; None otherwise.

problems class-attribute instance-attribute

problems: tuple[ValidationProblem, ...] = ()

Every reason the document is not valid.

__init__

__init__(
    dtype: ResolvedField[ZarrV2DataTypeDefinition[Any]]
    | UNSET = UNSET,
    compressor: ResolvedField[ZarrV2CodecDefinition[Any]]
    | UNSET
    | None = UNSET,
    filters: tuple[
        ResolvedField[ZarrV2CodecDefinition[Any]], ...
    ]
    | UNSET
    | None = UNSET,
    problems: tuple[ValidationProblem, ...] = (),
    metadata: ZarrV2ArrayMetadata | None = None,
) -> None

__reduce__

__reduce__() -> str | tuple[object, ...]
Source code in src/zarr_metadata/model/_array_v2.py
def __reduce__(self) -> str | tuple[object, ...]:
    # Same as the v3 reading: pickle through the model, when there is one.
    if self.metadata is not None:
        return (reading_of, (self.metadata,))
    return object.__reduce__(self)

fields

Each field in the document, as the scope read it, with its location: the dtype, followed by a struct's record types, then the compressor, then each filter at its index.

Source code in src/zarr_metadata/model/_array_v2.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Each field in the document, as the scope read it, with its location: the dtype, followed by a struct's record types, then the compressor, then each filter at its index."""
    if self.dtype is not UNSET:
        yield from fields_of(self.dtype, ("dtype",))
    if self.compressor is not UNSET and self.compressor is not None:
        yield from fields_of(self.compressor, ("compressor",))
    if self.filters is not UNSET and self.filters is not None:
        for index, entry in enumerate(self.filters):
            yield from fields_of(entry, ("filters", index))

ZarrV2ArrayMetadataUpdate

Bases: TypedDict

The members ZarrV2ArrayMetadata.update replaces: each in the form a document writes it, or UNSET to remove a member that a document may omit.

The members a document may omit are attributes (meaning no .zattrs), dimension_separator (read as "."), and any member the spec does not define. Those go in extra_fields, by key: each one given is set, or removed when given as UNSET, and the others are kept. A member is given by its own name only when the spec defines it, so a misspelled name is refused.

Source code in src/zarr_metadata/model/_array_v2.py
class ZarrV2ArrayMetadataUpdate(TypedDict, total=False, closed=True):
    """The members `ZarrV2ArrayMetadata.update` replaces: each in the form a document writes it, or `UNSET` to remove a member that a document may omit.

    The members a document may omit are `attributes` (meaning no
    `.zattrs`), `dimension_separator` (read as `"."`), and any member the
    spec does not define. Those go in `extra_fields`, by key: each one
    given is set, or removed when given as `UNSET`, and the others are
    kept. A member is given by its own name only when the spec defines it,
    so a misspelled name is refused.
    """

    shape: tuple[int, ...]
    dtype: ZarrV2DataTypeMetadata
    chunks: tuple[int, ...]
    fill_value: JSONValue
    order: ZarrV2ArrayOrder
    compressor: ZarrV2CodecMetadata | None
    filters: tuple[ZarrV2CodecMetadata, ...] | None
    dimension_separator: ZarrV2ArrayDimensionSeparator | UNSET
    attributes: Mapping[str, JSONValue] | UNSET
    extra_fields: Mapping[str, JSONValue | UNSET]

attributes instance-attribute

attributes: Mapping[str, JSONValue] | UNSET

chunks instance-attribute

chunks: tuple[int, ...]

compressor instance-attribute

compressor: ZarrV2CodecMetadata | None

dimension_separator instance-attribute

dimension_separator: ZarrV2ArrayDimensionSeparator | UNSET

dtype instance-attribute

extra_fields instance-attribute

extra_fields: Mapping[str, JSONValue | UNSET]

fill_value instance-attribute

fill_value: JSONValue

filters instance-attribute

filters: tuple[ZarrV2CodecMetadata, ...] | None

order instance-attribute

shape instance-attribute

shape: tuple[int, ...]

ZarrV2ConsolidatedMetadata

Bases: Keyed

A v2 .zmetadata document, and the scope its nodes were read in.

metadata maps the path of each node to its model in this scope, as ZarrV3ConsolidatedMetadata.metadata does. A path is written without the leading /, and the root's path is "": the node at /a/b is at a/b. Each node is a .zarray or .zgroup entry, merged with its sibling .zattrs. A .zattrs with no sibling is kept but does not form a node. Any other entry must be JSON, and is kept. to_json returns the entries keyed by file ("a/.zarray", "a/.zattrs", ...) as written, refined, so the document still tells which nodes have a .zattrs at all. A model is only built by reading: the constructor raises MetadataValidationError with every problem, each located under its entry. Two documents are equal when each node means the same thing and the other entries are written the same way, as the model key defines. refines, with_context and refined_in work through the nodes.

Source code in src/zarr_metadata/model/_group_v2.py
class ZarrV2ConsolidatedMetadata(Keyed):
    """A v2 `.zmetadata` document, and the scope its nodes were read in.

    `metadata` maps the path of each node to its model in this scope, as
    `ZarrV3ConsolidatedMetadata.metadata` does. A path is written without
    the leading `/`, and the root's path is `""`: the node at `/a/b` is at
    `a/b`. Each node is a `.zarray` or `.zgroup` entry, merged with its
    sibling `.zattrs`. A `.zattrs` with no sibling is kept but does not
    form a node. Any other entry must be JSON, and is kept. `to_json`
    returns the entries keyed by file (`"a/.zarray"`, `"a/.zattrs"`, ...)
    as written, refined, so the document still tells which nodes have a
    `.zattrs` at all. A model is only built by reading: the constructor
    raises `MetadataValidationError` with every problem, each located
    under its entry. Two documents are equal when each node means the same
    thing and the other entries are written the same way, as the model
    key defines. `refines`, `with_context` and `refined_in` work through
    the nodes.
    """

    __slots__ = ("_context", "_document", "_files", "_metadata")

    _context: ZarrV2Context
    _document: dict[str, JSONValue]
    _files: dict[str, dict[str, str]]
    _metadata: dict[str, ZarrV2NodeMetadata]

    zarr_consolidated_format: Final = 1

    def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
        context = scoped(context, CORE_V2)
        read, nodes, files, problems = _read_consolidated_v2(document, context)
        if len(problems) != 0:
            raise MetadataValidationError(problems)
        self._adopt(read, context, nodes, files)

    @classmethod
    def _of(
        cls,
        document: dict[str, JSONValue],
        context: ZarrV2Context,
        metadata: dict[str, ZarrV2NodeMetadata],
        files: dict[str, dict[str, str]],
    ) -> ZarrV2ConsolidatedMetadata:
        """Build a model of a document that a read found no problems with, holding the nodes that read built and the entry key of each node's files. The readers in this package build models through this method; pyright reports these calls as private use."""
        model = object.__new__(cls)
        model._adopt(document, context, metadata, files)
        return model

    def _adopt(
        self,
        document: dict[str, JSONValue],
        context: ZarrV2Context,
        metadata: dict[str, ZarrV2NodeMetadata],
        files: dict[str, dict[str, str]],
    ) -> None:
        self._document = document
        self._context = context
        self._metadata = metadata
        # The entry key of each file of each node, by node path and then by
        # file name, as the document writes it.
        self._files = files

    @property
    def reading(self) -> ZarrV2ConsolidatedMetadataReading:
        """The document as the scope read it: no problems, and this model."""
        return ZarrV2ConsolidatedMetadataReading(metadata=self)

    @property
    def context(self) -> ZarrV2Context:
        """The scope the nodes were read in."""
        return self._context

    @property
    def metadata(self) -> Mapping[str, ZarrV2NodeMetadata]:
        """A read-only view of the model of each node, keyed by its path without the leading `/` (`""` for the root)."""
        return MappingProxyType(self._metadata)

    def to_json(self) -> dict[str, JSONValue]:
        """The `.zmetadata` document as written, refined. The result shares no objects with the model."""
        return cast("dict[str, JSONValue]", copied(self._document))

    def to_key_value(
        self, *, indent: int | str | None = None
    ) -> Mapping[ZarrV2ConsolidatedMetadataStoreKey, bytes]:
        """The document as a store holds it: JSON bytes at `.zmetadata`, indented by `indent`."""
        return {
            ZARR_V2_CONSOLIDATED_METADATA_STORE_KEY: dump_store_json(self._document, indent=indent)
        }

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

    def _key_of(self) -> tuple[object, ...]:
        """The key `==` and `hash` compare for v2 consolidated metadata: each node's path and key, and every other entry as JSON text."""
        return (
            tuple(sorted((path, node._model_key) for path, node in self._metadata.items())),
            self._other_entries_text(),
        )

    def _other_entries_text(self) -> str:
        """The entries that no node is read from (an orphan `.zattrs`, or any other key) as JSON text, which `==` compares."""
        entries = object_at(self._document, "metadata")
        consumed = {key for names in self._files.values() for key in names.values()}
        return json_text({key: value for key, value in entries.items() if key not in consumed})

    def _located_fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Each field of each array node, as read, located in the document: under the node's `.zarray` entry, at the key the document writes for it."""
        for path, node in self._metadata.items():
            if isinstance(node, ZarrV2ArrayMetadata):
                key = self._files[path][ZARR_V2_ARRAY_METADATA_STORE_KEY]
                for loc, field in node.reading.fields():
                    yield ("metadata", key, *loc), field

    def __reduce__(self) -> tuple[type[ZarrV2ConsolidatedMetadata], tuple[object, ZarrV2Context]]:
        return type(self), (self._document, self._context)

    def with_context(self, context: ZarrV2Context | None = None) -> ZarrV2ConsolidatedMetadata:
        """Return this document with every node read in `context`, whatever that changes. Raises `MetadataValidationError` when a node has a problem in `context`."""
        context = scoped(context, CORE_V2)
        return type(self)(self._document, context=context)

    def refined_in(self, context: ZarrV2Context | None = None) -> ZarrV2ConsolidatedMetadata:
        """Return this document with every node read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

        Raises `ScopeConflictError` naming each conflict, located at the
        node's entry. Raises `MetadataValidationError` when a gain exposes
        a problem.
        """
        context = scoped(context, CORE_V2)
        conflicts: list[Conflict] = []
        for path, node in self._metadata.items():
            if not isinstance(node, ZarrV2ArrayMetadata):
                continue
            found = disagreements(context, claims_of(node.reading.fields()))
            key = self._files[path][ZARR_V2_ARRAY_METADATA_STORE_KEY]
            conflicts.extend(
                dataclasses.replace(
                    conflict,
                    loc=("metadata", key, *(() if conflict.loc is None else conflict.loc)),
                )
                for conflict in located_conflicts(node.reading.fields(), found.conflicts)
            )
        if len(conflicts) != 0:
            raise ScopeConflictError(conflicts)
        return self.with_context(context)

    def refines(self, other: ZarrV2ConsolidatedMetadata) -> bool:
        """Whether every node this holds refines the node `other` holds at the same path, both hold the same paths, and the other entries are written the same way. False when `other` is not v2 consolidated metadata."""
        if type(other) is not type(self):
            return False
        if self._metadata.keys() != other._metadata.keys():
            return False
        if self._other_entries_text() != other._other_entries_text():
            return False
        return all(
            _v2_node_refines(self._metadata[path], other._metadata[path]) for path in self._metadata
        )

    @classmethod
    def from_json(
        cls, data: object, *, context: ZarrV2Context | None = None
    ) -> ZarrV2ConsolidatedMetadata:
        """Return the model of `data`, a `.zmetadata` document, with its nodes read in `context`. Raises `MetadataValidationError` with every problem."""
        return cls(data, context=context)

    @classmethod
    def from_key_value(
        cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV2Context | None = None
    ) -> ZarrV2ConsolidatedMetadata:
        """Return the model of the document at `.zmetadata` in `mapping`, read in `context`.

        Raises `MetadataValidationError` when the key is missing, its bytes
        are not JSON, or the document is not valid. Raises `TypeError` for
        a scope of another format before the bytes are decoded.
        """
        context = scoped(context, CORE_V2)
        return cls(
            load_store_json(mapping, ZARR_V2_CONSOLIDATED_METADATA_STORE_KEY), context=context
        )

__slots__ class-attribute instance-attribute

__slots__ = ("_context", "_document", "_files", "_metadata")

context property

context: ZarrV2Context

The scope the nodes were read in.

metadata property

A read-only view of the model of each node, keyed by its path without the leading / ("" for the root).

reading property

The document as the scope read it: no problems, and this model.

zarr_consolidated_format class-attribute instance-attribute

zarr_consolidated_format: Final = 1

__eq__

__eq__(other: object) -> bool
Source code in src/zarr_metadata/model/_keyed.py
def __eq__(self, other: object) -> bool:
    if type(other) is not type(self):
        return NotImplemented
    return self._model_key == other._model_key

__hash__

__hash__() -> int
Source code in src/zarr_metadata/model/_keyed.py
def __hash__(self) -> int:
    return hash(self._model_key)

__init__

__init__(
    document: object, context: ZarrV2Context | None = None
) -> None
Source code in src/zarr_metadata/model/_group_v2.py
def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
    context = scoped(context, CORE_V2)
    read, nodes, files, problems = _read_consolidated_v2(document, context)
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    self._adopt(read, context, nodes, files)

__reduce__

Source code in src/zarr_metadata/model/_group_v2.py
def __reduce__(self) -> tuple[type[ZarrV2ConsolidatedMetadata], tuple[object, ZarrV2Context]]:
    return type(self), (self._document, self._context)

__repr__

__repr__() -> str
Source code in src/zarr_metadata/model/_group_v2.py
def __repr__(self) -> str:
    return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

from_json classmethod

from_json(
    data: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ConsolidatedMetadata

Return the model of data, a .zmetadata document, with its nodes read in context. Raises MetadataValidationError with every problem.

Source code in src/zarr_metadata/model/_group_v2.py
@classmethod
def from_json(
    cls, data: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ConsolidatedMetadata:
    """Return the model of `data`, a `.zmetadata` document, with its nodes read in `context`. Raises `MetadataValidationError` with every problem."""
    return cls(data, context=context)

from_key_value classmethod

from_key_value(
    mapping: Mapping[StoreKey, bytes],
    *,
    context: ZarrV2Context | None = None,
) -> ZarrV2ConsolidatedMetadata

Return the model of the document at .zmetadata in mapping, read in context.

Raises MetadataValidationError when the key is missing, its bytes are not JSON, or the document is not valid. Raises TypeError for a scope of another format before the bytes are decoded.

Source code in src/zarr_metadata/model/_group_v2.py
@classmethod
def from_key_value(
    cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV2Context | None = None
) -> ZarrV2ConsolidatedMetadata:
    """Return the model of the document at `.zmetadata` in `mapping`, read in `context`.

    Raises `MetadataValidationError` when the key is missing, its bytes
    are not JSON, or the document is not valid. Raises `TypeError` for
    a scope of another format before the bytes are decoded.
    """
    context = scoped(context, CORE_V2)
    return cls(
        load_store_json(mapping, ZARR_V2_CONSOLIDATED_METADATA_STORE_KEY), context=context
    )

refined_in

refined_in(
    context: ZarrV2Context | None = None,
) -> ZarrV2ConsolidatedMetadata

Return this document with every node read in context. context may claim names this scope left unclaimed, but must not contradict any claim.

Raises ScopeConflictError naming each conflict, located at the node's entry. Raises MetadataValidationError when a gain exposes a problem.

Source code in src/zarr_metadata/model/_group_v2.py
def refined_in(self, context: ZarrV2Context | None = None) -> ZarrV2ConsolidatedMetadata:
    """Return this document with every node read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

    Raises `ScopeConflictError` naming each conflict, located at the
    node's entry. Raises `MetadataValidationError` when a gain exposes
    a problem.
    """
    context = scoped(context, CORE_V2)
    conflicts: list[Conflict] = []
    for path, node in self._metadata.items():
        if not isinstance(node, ZarrV2ArrayMetadata):
            continue
        found = disagreements(context, claims_of(node.reading.fields()))
        key = self._files[path][ZARR_V2_ARRAY_METADATA_STORE_KEY]
        conflicts.extend(
            dataclasses.replace(
                conflict,
                loc=("metadata", key, *(() if conflict.loc is None else conflict.loc)),
            )
            for conflict in located_conflicts(node.reading.fields(), found.conflicts)
        )
    if len(conflicts) != 0:
        raise ScopeConflictError(conflicts)
    return self.with_context(context)

refines

refines(other: ZarrV2ConsolidatedMetadata) -> bool

Whether every node this holds refines the node other holds at the same path, both hold the same paths, and the other entries are written the same way. False when other is not v2 consolidated metadata.

Source code in src/zarr_metadata/model/_group_v2.py
def refines(self, other: ZarrV2ConsolidatedMetadata) -> bool:
    """Whether every node this holds refines the node `other` holds at the same path, both hold the same paths, and the other entries are written the same way. False when `other` is not v2 consolidated metadata."""
    if type(other) is not type(self):
        return False
    if self._metadata.keys() != other._metadata.keys():
        return False
    if self._other_entries_text() != other._other_entries_text():
        return False
    return all(
        _v2_node_refines(self._metadata[path], other._metadata[path]) for path in self._metadata
    )

to_json

to_json() -> dict[str, JSONValue]

The .zmetadata document as written, refined. The result shares no objects with the model.

Source code in src/zarr_metadata/model/_group_v2.py
def to_json(self) -> dict[str, JSONValue]:
    """The `.zmetadata` document as written, refined. The result shares no objects with the model."""
    return cast("dict[str, JSONValue]", copied(self._document))

to_key_value

to_key_value(
    *, indent: int | str | None = None
) -> Mapping[ZarrV2ConsolidatedMetadataStoreKey, bytes]

The document as a store holds it: JSON bytes at .zmetadata, indented by indent.

Source code in src/zarr_metadata/model/_group_v2.py
def to_key_value(
    self, *, indent: int | str | None = None
) -> Mapping[ZarrV2ConsolidatedMetadataStoreKey, bytes]:
    """The document as a store holds it: JSON bytes at `.zmetadata`, indented by `indent`."""
    return {
        ZARR_V2_CONSOLIDATED_METADATA_STORE_KEY: dump_store_json(self._document, indent=indent)
    }

with_context

with_context(
    context: ZarrV2Context | None = None,
) -> ZarrV2ConsolidatedMetadata

Return this document with every node read in context, whatever that changes. Raises MetadataValidationError when a node has a problem in context.

Source code in src/zarr_metadata/model/_group_v2.py
def with_context(self, context: ZarrV2Context | None = None) -> ZarrV2ConsolidatedMetadata:
    """Return this document with every node read in `context`, whatever that changes. Raises `MetadataValidationError` when a node has a problem in `context`."""
    context = scoped(context, CORE_V2)
    return type(self)(self._document, context=context)

ZarrV2ConsolidatedMetadataReading dataclass

A v2 .zmetadata document as a scope read it: every problem, and the model when there are no problems.

Source code in src/zarr_metadata/model/_group_v2.py
@dataclass(frozen=True, slots=True)
class ZarrV2ConsolidatedMetadataReading:
    """A v2 `.zmetadata` document as a scope read it: every problem, and the model when there are no problems."""

    problems: tuple[ValidationProblem, ...] = ()
    """Every reason the document is not valid."""
    metadata: ZarrV2ConsolidatedMetadata | None = None
    """The document's model when there is no problem; None otherwise."""

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Each field of each array node, as read, located in the `.zmetadata`: under the node's `.zarray` entry, at the key the document writes for it. Nothing when there is no model, and nothing for a group, which has no fields."""
        if self.metadata is None:
            return
        yield from self.metadata._located_fields()  # pyright: ignore[reportPrivateUsage]

    def __reduce__(self) -> str | tuple[object, ...]:
        # Same as the other readings: pickle through the model, when there is one.
        if self.metadata is not None:
            return (reading_of, (self.metadata,))
        return object.__reduce__(self)

metadata class-attribute instance-attribute

metadata: ZarrV2ConsolidatedMetadata | None = None

The document's model when there is no problem; None otherwise.

problems class-attribute instance-attribute

problems: tuple[ValidationProblem, ...] = ()

Every reason the document is not valid.

__init__

__init__(
    problems: tuple[ValidationProblem, ...] = (),
    metadata: ZarrV2ConsolidatedMetadata | None = None,
) -> None

__reduce__

__reduce__() -> str | tuple[object, ...]
Source code in src/zarr_metadata/model/_group_v2.py
def __reduce__(self) -> str | tuple[object, ...]:
    # Same as the other readings: pickle through the model, when there is one.
    if self.metadata is not None:
        return (reading_of, (self.metadata,))
    return object.__reduce__(self)

fields

Each field of each array node, as read, located in the .zmetadata: under the node's .zarray entry, at the key the document writes for it. Nothing when there is no model, and nothing for a group, which has no fields.

Source code in src/zarr_metadata/model/_group_v2.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Each field of each array node, as read, located in the `.zmetadata`: under the node's `.zarray` entry, at the key the document writes for it. Nothing when there is no model, and nothing for a group, which has no fields."""
    if self.metadata is None:
        return
    yield from self.metadata._located_fields()  # pyright: ignore[reportPrivateUsage]

ZarrV2GroupMetadata

Bases: Keyed

A v2 group document, and the scope it was read in.

Like the v3 models, this model is a pair. to_json returns the merged document (.zgroup, plus attributes when a .zattrs holds them) as written, refined. attributes is empty when no .zattrs exists, as in a v3 model. The document still tells the two apart: to_json and to_key_value write attributes and .zattrs only when a .zattrs exists, even an empty one. A group has no fields for a scope to read, so the scope is held only for consistency with the other models: update reads new attributes in it, and no other scope conflicts with the reading. A model is only built by reading: the constructor raises MetadataValidationError with every problem. Two groups are equal when their attributes are written the same way, as _key_of defines; no .zattrs and an empty one are equal.

Source code in src/zarr_metadata/model/_group_v2.py
class ZarrV2GroupMetadata(Keyed):
    """A v2 group document, and the scope it was read in.

    Like the v3 models, this model is a pair. `to_json` returns the
    merged document (`.zgroup`, plus `attributes` when a `.zattrs` holds
    them) as written, refined. `attributes` is empty when no `.zattrs`
    exists, as in a v3 model. The document still tells the two apart:
    `to_json` and `to_key_value` write `attributes` and `.zattrs` only when
    a `.zattrs` exists, even an empty one. A group has no fields
    for a scope to read, so the scope is held only for consistency with
    the other models: `update` reads new attributes in it, and no other
    scope conflicts with the reading. A model is only built by reading:
    the constructor raises `MetadataValidationError` with every problem.
    Two groups are equal when their attributes are written the same way,
    as `_key_of` defines; no `.zattrs` and an empty one are equal.
    """

    __slots__ = ("_attributes", "_context", "_document", "_shown")

    _attributes: dict[str, JSONValue] | UNSET
    _context: ZarrV2Context
    _document: dict[str, JSONValue]
    _shown: Mapping[str, JSONValue]

    zarr_format: Final[Literal[2]] = 2  # noqa: PYI064 - a bare Final does not narrow a union of models
    node_type: Final[Literal["group"]] = "group"  # noqa: PYI064 - as above

    def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
        context = scoped(context, CORE_V2)
        problems = _group_problems_v2(document)
        if len(problems) != 0:
            raise MetadataValidationError(problems)
        # Refine once. The attributes the model holds are taken from the
        # refined document, which shares no objects with the input.
        refined = refined_object(document)
        attributes: dict[str, JSONValue] | UNSET = (
            object_at(refined, "attributes") if "attributes" in refined else UNSET
        )
        self._adopt(refined, context, attributes)

    @classmethod
    def _of(
        cls,
        document: dict[str, JSONValue],
        context: ZarrV2Context,
        attributes: dict[str, JSONValue] | UNSET,
    ) -> ZarrV2GroupMetadata:
        """Build a model of a document that a read found no problems with, without reading it again. The readers in this package build models through this method; pyright reports these calls as private use."""
        model = object.__new__(cls)
        model._adopt(document, context, attributes)
        return model

    def _adopt(
        self,
        document: dict[str, JSONValue],
        context: ZarrV2Context,
        attributes: dict[str, JSONValue] | UNSET,
    ) -> None:
        self._document = document
        self._context = context
        self._attributes = attributes
        self._shown = frozen({} if attributes is UNSET else attributes)

    @property
    def reading(self) -> ZarrV2GroupMetadataReading:
        """The document as the scope read it: no problems, and this model."""
        return ZarrV2GroupMetadataReading(metadata=self)

    @property
    def context(self) -> ZarrV2Context:
        """The scope the document was read in. `update` reads new attributes in this scope."""
        return self._context

    @property
    def unclaimed_fields(self) -> dict[Loc, UnclaimedField]:
        """Every field nothing in scope claims. A v2 group holds no fields, so this is always empty; it is here so a reader can ask any node the same question."""
        return {}

    @property
    def attributes(self) -> Mapping[str, JSONValue]:
        """The user attributes a `.zattrs` holds, read-only at every level; empty when there is no `.zattrs`."""
        return self._shown

    def to_json(self) -> ZarrV2GroupMetadataJSON:
        """The merged document as written, refined. The result shares no objects with the model.

        `attributes` is included when set, even if empty. This is not the
        on-disk `.zgroup`, which excludes them: `to_key_value` splits the
        document as a store holds it
        (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L313; https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L323-L330).
        """
        return cast("ZarrV2GroupMetadataJSON", copied(self._document))

    def to_key_value(
        self, *, indent: int | str | None = None
    ) -> Mapping[ZarrV2GroupMetadataStoreKey | ZarrV2AttributesStoreKey, bytes]:
        """The document as a store holds it: `.zgroup` without the attributes, and `.zattrs` with the attributes when they are set, even if empty."""
        zgroup = {key: value for key, value in self._document.items() if key != "attributes"}
        out: dict[ZarrV2GroupMetadataStoreKey | ZarrV2AttributesStoreKey, bytes] = {
            ZARR_V2_GROUP_METADATA_STORE_KEY: dump_store_json(zgroup, indent=indent)
        }
        if "attributes" in self._document:
            out[ZARR_V2_ATTRIBUTES_STORE_KEY] = dump_store_json(
                self._document["attributes"], indent=indent
            )
        return out

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

    def _key_of(self) -> tuple[object, ...]:
        """The key `==` and `hash` compare for a v2 group model: its attributes as JSON text. No `.zattrs` counts as empty attributes, since both mean that the group has none, as in v3."""
        attributes = self._attributes
        return (json_text({} if attributes is UNSET else attributes),)

    def __reduce__(self) -> tuple[type[ZarrV2GroupMetadata], tuple[object, ZarrV2Context]]:
        return type(self), (self._document, self._context)

    def update(self, **members: Unpack[ZarrV2GroupMetadataUpdate]) -> ZarrV2GroupMetadata:
        """Return this model with `attributes` replacing the document's attributes, read in this model's own scope. `UNSET` removes them. Raises `MetadataValidationError` when the resulting document has a problem."""
        changes = member_changes(members, ZarrV2GroupMetadataUpdate, frozenset())
        document: dict[str, object] = {**self._document, **changes}
        for key, value in changes.items():
            if value is UNSET:
                del document[key]
        return type(self)(document, context=self._context)

    def with_context(self, context: ZarrV2Context | None = None) -> ZarrV2GroupMetadata:
        """Return this document read in `context`: the same group, holding that scope."""
        context = scoped(context, CORE_V2)
        return self._of(self._document, context, self._attributes)

    def refined_in(self, context: ZarrV2Context | None = None) -> ZarrV2GroupMetadata:
        """Return this document read in `context`. A group has no fields, so no scope conflicts with its reading, and this is the same as `with_context`."""
        return self.with_context(context)

    def refines(self, other: ZarrV2GroupMetadata) -> bool:
        """Whether this group holds everything `other` holds, which means its attributes are written the same way, no `.zattrs` counting as empty attributes. False when `other` is not a v2 group."""
        return type(other) is type(self) and self._model_key == other._model_key

    @classmethod
    def create_default(
        cls, *, context: ZarrV2Context | None = None, **overrides: Unpack[ZarrV2GroupMetadataUpdate]
    ) -> ZarrV2GroupMetadata:
        """Return a group with no `.zattrs`, with `overrides` applied, read in `context`. Raises `MetadataValidationError` when the resulting document has a problem."""
        changes = member_changes(overrides, ZarrV2GroupMetadataUpdate, frozenset())
        given = {key: value for key, value in changes.items() if value is not UNSET}
        return cls({"zarr_format": 2, **given}, context=context)

    @classmethod
    def from_json(
        cls, data: object, *, context: ZarrV2Context | None = None
    ) -> ZarrV2GroupMetadata:
        """Return the model of `data`, a v2 group document with its attributes under `attributes`, read in `context`. Raises `MetadataValidationError` with every problem."""
        return cls(data, context=context)

    @classmethod
    def from_key_value(
        cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV2Context | None = None
    ) -> ZarrV2GroupMetadata:
        """Return the model of the group at `.zgroup` in `mapping`, with the attributes at `.zattrs` when that key exists, read in `context`.

        Raises `MetadataValidationError` when `.zgroup` is missing, bytes
        are not JSON, `.zgroup` holds `attributes`, or the document is not
        valid, with every problem the read finds. Raises `TypeError` for a
        scope of another format before any bytes are decoded.
        """
        context = scoped(context, CORE_V2)
        files, problems = decoded_node_files_v2(
            mapping, (ZARR_V2_GROUP_METADATA_STORE_KEY, ZARR_V2_ATTRIBUTES_STORE_KEY)
        )
        if len(problems) != 0:
            raise MetadataValidationError(problems)
        document, problems = node_document_v2(
            files, ZARR_V2_GROUP_METADATA_STORE_KEY, _GROUP_FILE_ATTRIBUTES
        )
        if len(problems) != 0:
            if document is not None:
                problems = (*problems, *_group_problems_v2(document))
            raise MetadataValidationError(problems)
        return cls(document, context=context)

__slots__ class-attribute instance-attribute

__slots__ = (
    "_attributes",
    "_context",
    "_document",
    "_shown",
)

attributes property

attributes: Mapping[str, JSONValue]

The user attributes a .zattrs holds, read-only at every level; empty when there is no .zattrs.

context property

context: ZarrV2Context

The scope the document was read in. update reads new attributes in this scope.

node_type class-attribute instance-attribute

node_type: Final[Literal['group']] = 'group'

reading property

The document as the scope read it: no problems, and this model.

unclaimed_fields property

unclaimed_fields: dict[Loc, UnclaimedField]

Every field nothing in scope claims. A v2 group holds no fields, so this is always empty; it is here so a reader can ask any node the same question.

zarr_format class-attribute instance-attribute

zarr_format: Final[Literal[2]] = 2

__eq__

__eq__(other: object) -> bool
Source code in src/zarr_metadata/model/_keyed.py
def __eq__(self, other: object) -> bool:
    if type(other) is not type(self):
        return NotImplemented
    return self._model_key == other._model_key

__hash__

__hash__() -> int
Source code in src/zarr_metadata/model/_keyed.py
def __hash__(self) -> int:
    return hash(self._model_key)

__init__

__init__(
    document: object, context: ZarrV2Context | None = None
) -> None
Source code in src/zarr_metadata/model/_group_v2.py
def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
    context = scoped(context, CORE_V2)
    problems = _group_problems_v2(document)
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    # Refine once. The attributes the model holds are taken from the
    # refined document, which shares no objects with the input.
    refined = refined_object(document)
    attributes: dict[str, JSONValue] | UNSET = (
        object_at(refined, "attributes") if "attributes" in refined else UNSET
    )
    self._adopt(refined, context, attributes)

__reduce__

Source code in src/zarr_metadata/model/_group_v2.py
def __reduce__(self) -> tuple[type[ZarrV2GroupMetadata], tuple[object, ZarrV2Context]]:
    return type(self), (self._document, self._context)

__repr__

__repr__() -> str
Source code in src/zarr_metadata/model/_group_v2.py
def __repr__(self) -> str:
    return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

create_default classmethod

create_default(
    *,
    context: ZarrV2Context | None = None,
    **overrides: Unpack[ZarrV2GroupMetadataUpdate],
) -> ZarrV2GroupMetadata

Return a group with no .zattrs, with overrides applied, read in context. Raises MetadataValidationError when the resulting document has a problem.

Source code in src/zarr_metadata/model/_group_v2.py
@classmethod
def create_default(
    cls, *, context: ZarrV2Context | None = None, **overrides: Unpack[ZarrV2GroupMetadataUpdate]
) -> ZarrV2GroupMetadata:
    """Return a group with no `.zattrs`, with `overrides` applied, read in `context`. Raises `MetadataValidationError` when the resulting document has a problem."""
    changes = member_changes(overrides, ZarrV2GroupMetadataUpdate, frozenset())
    given = {key: value for key, value in changes.items() if value is not UNSET}
    return cls({"zarr_format": 2, **given}, context=context)

from_json classmethod

from_json(
    data: object, *, context: ZarrV2Context | None = None
) -> ZarrV2GroupMetadata

Return the model of data, a v2 group document with its attributes under attributes, read in context. Raises MetadataValidationError with every problem.

Source code in src/zarr_metadata/model/_group_v2.py
@classmethod
def from_json(
    cls, data: object, *, context: ZarrV2Context | None = None
) -> ZarrV2GroupMetadata:
    """Return the model of `data`, a v2 group document with its attributes under `attributes`, read in `context`. Raises `MetadataValidationError` with every problem."""
    return cls(data, context=context)

from_key_value classmethod

from_key_value(
    mapping: Mapping[StoreKey, bytes],
    *,
    context: ZarrV2Context | None = None,
) -> ZarrV2GroupMetadata

Return the model of the group at .zgroup in mapping, with the attributes at .zattrs when that key exists, read in context.

Raises MetadataValidationError when .zgroup is missing, bytes are not JSON, .zgroup holds attributes, or the document is not valid, with every problem the read finds. Raises TypeError for a scope of another format before any bytes are decoded.

Source code in src/zarr_metadata/model/_group_v2.py
@classmethod
def from_key_value(
    cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV2Context | None = None
) -> ZarrV2GroupMetadata:
    """Return the model of the group at `.zgroup` in `mapping`, with the attributes at `.zattrs` when that key exists, read in `context`.

    Raises `MetadataValidationError` when `.zgroup` is missing, bytes
    are not JSON, `.zgroup` holds `attributes`, or the document is not
    valid, with every problem the read finds. Raises `TypeError` for a
    scope of another format before any bytes are decoded.
    """
    context = scoped(context, CORE_V2)
    files, problems = decoded_node_files_v2(
        mapping, (ZARR_V2_GROUP_METADATA_STORE_KEY, ZARR_V2_ATTRIBUTES_STORE_KEY)
    )
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    document, problems = node_document_v2(
        files, ZARR_V2_GROUP_METADATA_STORE_KEY, _GROUP_FILE_ATTRIBUTES
    )
    if len(problems) != 0:
        if document is not None:
            problems = (*problems, *_group_problems_v2(document))
        raise MetadataValidationError(problems)
    return cls(document, context=context)

refined_in

refined_in(
    context: ZarrV2Context | None = None,
) -> ZarrV2GroupMetadata

Return this document read in context. A group has no fields, so no scope conflicts with its reading, and this is the same as with_context.

Source code in src/zarr_metadata/model/_group_v2.py
def refined_in(self, context: ZarrV2Context | None = None) -> ZarrV2GroupMetadata:
    """Return this document read in `context`. A group has no fields, so no scope conflicts with its reading, and this is the same as `with_context`."""
    return self.with_context(context)

refines

refines(other: ZarrV2GroupMetadata) -> bool

Whether this group holds everything other holds, which means its attributes are written the same way, no .zattrs counting as empty attributes. False when other is not a v2 group.

Source code in src/zarr_metadata/model/_group_v2.py
def refines(self, other: ZarrV2GroupMetadata) -> bool:
    """Whether this group holds everything `other` holds, which means its attributes are written the same way, no `.zattrs` counting as empty attributes. False when `other` is not a v2 group."""
    return type(other) is type(self) and self._model_key == other._model_key

to_json

The merged document as written, refined. The result shares no objects with the model.

attributes is included when set, even if empty. This is not the on-disk .zgroup, which excludes them: to_key_value splits the document as a store holds it (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L313; https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L323-L330).

Source code in src/zarr_metadata/model/_group_v2.py
def to_json(self) -> ZarrV2GroupMetadataJSON:
    """The merged document as written, refined. The result shares no objects with the model.

    `attributes` is included when set, even if empty. This is not the
    on-disk `.zgroup`, which excludes them: `to_key_value` splits the
    document as a store holds it
    (https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L313; https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v2/v2.0.rst#L323-L330).
    """
    return cast("ZarrV2GroupMetadataJSON", copied(self._document))

to_key_value

to_key_value(
    *, indent: int | str | None = None
) -> Mapping[
    ZarrV2GroupMetadataStoreKey | ZarrV2AttributesStoreKey,
    bytes,
]

The document as a store holds it: .zgroup without the attributes, and .zattrs with the attributes when they are set, even if empty.

Source code in src/zarr_metadata/model/_group_v2.py
def to_key_value(
    self, *, indent: int | str | None = None
) -> Mapping[ZarrV2GroupMetadataStoreKey | ZarrV2AttributesStoreKey, bytes]:
    """The document as a store holds it: `.zgroup` without the attributes, and `.zattrs` with the attributes when they are set, even if empty."""
    zgroup = {key: value for key, value in self._document.items() if key != "attributes"}
    out: dict[ZarrV2GroupMetadataStoreKey | ZarrV2AttributesStoreKey, bytes] = {
        ZARR_V2_GROUP_METADATA_STORE_KEY: dump_store_json(zgroup, indent=indent)
    }
    if "attributes" in self._document:
        out[ZARR_V2_ATTRIBUTES_STORE_KEY] = dump_store_json(
            self._document["attributes"], indent=indent
        )
    return out

update

update(
    **members: Unpack[ZarrV2GroupMetadataUpdate],
) -> ZarrV2GroupMetadata

Return this model with attributes replacing the document's attributes, read in this model's own scope. UNSET removes them. Raises MetadataValidationError when the resulting document has a problem.

Source code in src/zarr_metadata/model/_group_v2.py
def update(self, **members: Unpack[ZarrV2GroupMetadataUpdate]) -> ZarrV2GroupMetadata:
    """Return this model with `attributes` replacing the document's attributes, read in this model's own scope. `UNSET` removes them. Raises `MetadataValidationError` when the resulting document has a problem."""
    changes = member_changes(members, ZarrV2GroupMetadataUpdate, frozenset())
    document: dict[str, object] = {**self._document, **changes}
    for key, value in changes.items():
        if value is UNSET:
            del document[key]
    return type(self)(document, context=self._context)

with_context

with_context(
    context: ZarrV2Context | None = None,
) -> ZarrV2GroupMetadata

Return this document read in context: the same group, holding that scope.

Source code in src/zarr_metadata/model/_group_v2.py
def with_context(self, context: ZarrV2Context | None = None) -> ZarrV2GroupMetadata:
    """Return this document read in `context`: the same group, holding that scope."""
    context = scoped(context, CORE_V2)
    return self._of(self._document, context, self._attributes)

ZarrV2GroupMetadataReading dataclass

A v2 group document as a scope read it: every problem, and the model when there are no problems. A group has no fields for a scope to read.

Source code in src/zarr_metadata/model/_group_v2.py
@dataclass(frozen=True, slots=True)
class ZarrV2GroupMetadataReading:
    """A v2 group document as a scope read it: every problem, and the model when there are no problems. A group has no fields for a scope to read."""

    problems: tuple[ValidationProblem, ...] = ()
    """Every reason the document is not valid."""
    metadata: ZarrV2GroupMetadata | None = None
    """The document's model when there is no problem; None otherwise."""

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Its fields as read: none, since a group has no fields."""
        return iter(())

    def __reduce__(self) -> str | tuple[object, ...]:
        # Same as the other readings: pickle through the model, when there is one.
        if self.metadata is not None:
            return (reading_of, (self.metadata,))
        return object.__reduce__(self)

metadata class-attribute instance-attribute

metadata: ZarrV2GroupMetadata | None = None

The document's model when there is no problem; None otherwise.

problems class-attribute instance-attribute

problems: tuple[ValidationProblem, ...] = ()

Every reason the document is not valid.

__init__

__init__(
    problems: tuple[ValidationProblem, ...] = (),
    metadata: ZarrV2GroupMetadata | None = None,
) -> None

__reduce__

__reduce__() -> str | tuple[object, ...]
Source code in src/zarr_metadata/model/_group_v2.py
def __reduce__(self) -> str | tuple[object, ...]:
    # Same as the other readings: pickle through the model, when there is one.
    if self.metadata is not None:
        return (reading_of, (self.metadata,))
    return object.__reduce__(self)

fields

Its fields as read: none, since a group has no fields.

Source code in src/zarr_metadata/model/_group_v2.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Its fields as read: none, since a group has no fields."""
    return iter(())

ZarrV2GroupMetadataUpdate

Bases: TypedDict

The members ZarrV2GroupMetadata.update replaces: attributes in the form a .zattrs writes them, or UNSET for no .zattrs.

Source code in src/zarr_metadata/model/_group_v2.py
class ZarrV2GroupMetadataUpdate(TypedDict, total=False):
    """The members `ZarrV2GroupMetadata.update` replaces: `attributes` in the form a `.zattrs` writes them, or `UNSET` for no `.zattrs`."""

    attributes: Mapping[str, JSONValue] | UNSET

attributes instance-attribute

attributes: Mapping[str, JSONValue] | UNSET

ZarrV2RepairedConsolidatedMetadataReading dataclass

A v2 .zmetadata read after its known writer bugs were undone: the strict reading of the repaired document, and the repairs.

Source code in src/zarr_metadata/model/_repair.py
@dataclass(frozen=True, slots=True)
class ZarrV2RepairedConsolidatedMetadataReading:
    """A v2 `.zmetadata` read after its known writer bugs were undone: the strict reading of the repaired document, and the repairs."""

    reading: ZarrV2ConsolidatedMetadataReading
    """The repaired document, as `read_consolidated_metadata_v2` reads it. The problems are those of the repaired document. The model is present when there are no problems."""
    repairs: tuple[Repair, ...]
    """The changes that produced the document that was read."""

reading instance-attribute

The repaired document, as read_consolidated_metadata_v2 reads it. The problems are those of the repaired document. The model is present when there are no problems.

repairs instance-attribute

repairs: tuple[Repair, ...]

The changes that produced the document that was read.

__init__

__init__(
    reading: ZarrV2ConsolidatedMetadataReading,
    repairs: tuple[Repair, ...],
) -> None

ZarrV2RepairedNodeMetadataReading dataclass

A v2 node's files read after their known writer bugs were undone: the strict reading of the repaired files, and the repairs.

Source code in src/zarr_metadata/model/_repair.py
@dataclass(frozen=True, slots=True)
class ZarrV2RepairedNodeMetadataReading:
    """A v2 node's files read after their known writer bugs were undone: the strict reading of the repaired files, and the repairs."""

    reading: ZarrV2NodeMetadataReading
    """The repaired files, as `read_node_metadata_v2` reads them. The problems are those of the repaired files. The model is present when there are no problems."""
    repairs: tuple[Repair, ...]
    """The changes that produced the files that were read."""

reading instance-attribute

The repaired files, as read_node_metadata_v2 reads them. The problems are those of the repaired files. The model is present when there are no problems.

repairs instance-attribute

repairs: tuple[Repair, ...]

The changes that produced the files that were read.

__init__

__init__(
    reading: ZarrV2NodeMetadataReading,
    repairs: tuple[Repair, ...],
) -> None

ZarrV2UnknownNodeMetadataReading dataclass

A store that holds no v2 node: neither a .zarray nor a .zgroup, or both. The problems say which.

Source code in src/zarr_metadata/model/_group_v2.py
@dataclass(frozen=True, slots=True)
class ZarrV2UnknownNodeMetadataReading:
    """A store that holds no v2 node: neither a `.zarray` nor a `.zgroup`, or both. The problems say which."""

    problems: tuple[ValidationProblem, ...]
    """Why the store holds no node."""

    @property
    def metadata(self) -> None:
        """Its model: always None, since there is no node to model."""
        return None

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Its fields as read: none, since no document is read."""
        return iter(())

metadata property

metadata: None

Its model: always None, since there is no node to model.

problems instance-attribute

problems: tuple[ValidationProblem, ...]

Why the store holds no node.

__init__

__init__(problems: tuple[ValidationProblem, ...]) -> None

fields

Its fields as read: none, since no document is read.

Source code in src/zarr_metadata/model/_group_v2.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Its fields as read: none, since no document is read."""
    return iter(())

ZarrV3ArrayMetadata

Bases: Keyed

A v3 array document, and the scope it was read in.

The model is this pair. to_json returns the document as written, refined (arrays as tuples, string keys), and context is the scope. Every typed member is a view of the reading of the document in the scope. data_type, chunk_grid, chunk_key_encoding, and each codec and storage transformer are fields as the scope read them: an AcceptedField (read by the definition that claims its name) or an UnclaimedField, and so is each member in extra_fields, which the spec does not define. shape, fill_value, dimension_names and attributes are the values the read refined. A model is only built by reading: the constructor reads document in context and raises MetadataValidationError with every problem, so no model is invalid. Two models are equal when their documents mean the same thing in their scopes, as the model key defines. The scope itself is not compared. update reads new members in the model's own scope. with_context and refined_in read the document in another scope. A model pickles as its pair, provided the definitions in its scope can be pickled, which requires their functions to be defined at the top level of a module.

Source code in src/zarr_metadata/model/_array_v3.py
class ZarrV3ArrayMetadata(Keyed):
    """A v3 array document, and the scope it was read in.

    The model is this pair. `to_json` returns the document as written,
    refined (arrays as tuples, string keys), and `context` is the scope.
    Every typed member is a view of the reading of the document in the
    scope. `data_type`, `chunk_grid`, `chunk_key_encoding`, and each codec
    and storage transformer are fields as the scope read them: an
    `AcceptedField` (read by the definition that claims its name) or an
    `UnclaimedField`, and so is each member in `extra_fields`, which the
    spec does not define. `shape`, `fill_value`, `dimension_names` and
    `attributes` are the values the read refined. A
    model is only built by reading: the constructor reads `document` in
    `context` and raises `MetadataValidationError` with every problem, so
    no model is invalid. Two models are equal when their documents mean
    the same thing in their scopes, as the model key defines. The scope
    itself is not compared. `update` reads new members in the model's own
    scope. `with_context` and `refined_in` read the document in another
    scope. A model pickles as its pair, provided the definitions in its
    scope can be pickled, which requires their functions to be defined at
    the top level of a module.
    """

    __slots__ = ("_context", "_document", "_members", "_reading", "_shown")

    zarr_format: Final[Literal[3]] = 3  # noqa: PYI064 - a bare Final does not narrow a union of models
    node_type: Final[Literal["array"]] = "array"  # noqa: PYI064 - as above

    def __init__(self, document: object, context: ZarrV3Context | None = None) -> None:
        context = scoped(context, CORE_AND_EXTENSIONS)
        reading, members = read_array_v3(document, context)
        if members is None:
            raise MetadataValidationError(reading.problems)
        self._adopt(members.document, context, reading, members)

    @classmethod
    def _of(
        cls,
        document: dict[str, JSONValue],
        context: ZarrV3Context,
        reading: ZarrV3ArrayMetadataReading,
        members: ArrayMembersV3,
    ) -> ZarrV3ArrayMetadata:
        """Build a model of a document that a read found no problems with, holding that reading, without reading it again. The readers in this package build models through this method; pyright reports these calls as private use."""
        model = object.__new__(cls)
        model._adopt(document, context, reading, members)
        return model

    def _adopt(
        self,
        document: dict[str, JSONValue],
        context: ZarrV3Context,
        reading: ZarrV3ArrayMetadataReading,
        members: ArrayMembersV3,
    ) -> None:
        self._document = document
        self._context = context
        # The reading holds this model, in the form `read_array_metadata_v3`
        # returns it, no matter how the model was built.
        self._reading = dataclasses.replace(reading, metadata=self)
        self._members = members
        # The members the model exposes, read-only at every level.
        self._shown = (
            frozen(members.fill_value),
            frozen(members.attributes),
            frozen(members.extra_fields),
            MappingProxyType({key: held(extra) for key, extra in reading.extra_fields.items()}),
        )

    # --- the pair ---------------------------------------------------------

    @property
    def context(self) -> ZarrV3Context:
        """The scope the document was read in. `update` reads new members in this scope."""
        return self._context

    @property
    def reading(self) -> ZarrV3ArrayMetadataReading:
        """The document as the scope read it: each field, the pipeline, and the chunk each codec receives."""
        return self._reading

    def to_json(self) -> ZarrV3ArrayMetadataJSON:
        """The document as written, refined. The result shares no objects with the model."""
        return cast("ZarrV3ArrayMetadataJSON", copied(self._document))

    def to_key_value(
        self, *, indent: int | str | None = None
    ) -> Mapping[ZarrV3ArrayMetadataStoreKey, bytes]:
        """The document as a store holds it: JSON bytes at `zarr.json`, indented by `indent`.

        `NaN`, `Infinity` and `-Infinity` in `attributes` are written as
        bare tokens, as zarr-python writes them. A strict JSON parser
        rejects these tokens.
        """
        return {ZARR_V3_ARRAY_METADATA_STORE_KEY: dump_store_json(self._document, indent=indent)}

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

    def _key_of(self) -> tuple[object, ...]:
        """The key `==` and `hash` compare for a v3 array model: what its document means, as computed by `array_key_of`."""
        return array_key_of(self._reading, self._members)

    def _plain_key(
        self, data_type: AcceptedField[DataTypeDefinition[Any]] | UnclaimedField
    ) -> tuple[object, ...]:
        """The members other than fields that `refines` compares. The fill value is in the spelling of `data_type`, the data type of the more informed side."""
        members = self._members
        return (
            members.shape,
            json_text(spelled_canonically(data_type, members.fill_value)),
            members.dimension_names,
            json_text(members.attributes),
        )

    def __reduce__(self) -> tuple[type[ZarrV3ArrayMetadata], tuple[object, ZarrV3Context]]:
        # Pickle the pair and read it again on load, so a model's reading
        # always matches its document.
        return type(self), (self._document, self._context)

    # --- typed views ------------------------------------------------------

    @property
    def shape(self) -> tuple[int, ...]:
        """The array's shape."""
        return self._members.shape

    @property
    def fill_value(self) -> JSONValue:
        """The fill value as written, read-only at every level."""
        return self._shown[0]

    @property
    def dimension_names(self) -> tuple[str | None, ...] | UNSET:
        """The dimension names; `UNSET` when the document writes none."""
        return self._members.dimension_names

    @property
    def attributes(self) -> Mapping[str, JSONValue]:
        """The attributes, read-only at every level; empty when the document writes none."""
        return self._shown[1]

    @property
    def extra_fields(
        self,
    ) -> Mapping[str, AcceptedField[ArrayExtraFieldDefinition[Any]] | UnclaimedField]:
        """Each member the spec does not define, by key, as the scope read it: an `AcceptedField` when a definition in scope claims the key, and an `UnclaimedField` otherwise."""
        return self._shown[3]

    @property
    def unclaimed_fields(self) -> dict[Loc, UnclaimedField]:
        """Every field nothing in scope claims, at any depth, by its location in the document, except an extra member that says `"must_understand": false` and every field inside it, claimed or not, since a reader may ignore such a member whole.

        This answers whether a reader can open the array: the spec requires
        a reader to refuse a node with a field it does not understand, and
        the scope is what the reader understands. A codec inside a shard is
        located inside the shard's configuration. The model only reports
        these fields; refusing to open is up to the reader.
        """
        return unclaimed_fields_of(self._reading.fields(), waived_members(self._shown[2]))

    @property
    def must_understand_fields(self) -> dict[str, ZarrV3ExtensionField]:
        """The extra fields, by key and as written, that no definition in scope reads and that a reader is obligated to understand.

        Each unclaimed member of `extra_fields` not explicitly waived with
        `must_understand: false` (the spec's implicit-true rule, https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1571-L1578). The scope is what
        the reader recognizes, so a compliant reader MUST fail to open the
        array when this is not empty. The model only reports these fields;
        refusing to open is up to the reader.
        """
        return must_understand_subset(self.extra_fields, self._shown[2])

    @property
    def data_type(self) -> AcceptedField[DataTypeDefinition[Any]] | UnclaimedField:
        """The data type, as the scope read it."""
        return held(self._reading.data_type)

    @property
    def chunk_grid(self) -> AcceptedField[ChunkGridDefinition[Any]] | UnclaimedField:
        """The chunk grid, as the scope read it."""
        return held(self._reading.chunk_grid)

    @property
    def chunk_key_encoding(self) -> AcceptedField[ChunkKeyEncodingDefinition[Any]] | UnclaimedField:
        """The chunk key encoding, as the scope read it."""
        return held(self._reading.chunk_key_encoding)

    @property
    def codecs(self) -> tuple[AcceptedField[CodecDefinition[Any]] | UnclaimedField, ...]:
        """The codecs, each as the scope read it, in pipeline order."""
        return tuple(held(stage.codec) for stage in self._reading.pipeline)

    @property
    def storage_transformers(
        self,
    ) -> tuple[AcceptedField[StorageTransformerDefinition[Any]] | UnclaimedField, ...]:
        """The storage transformers, each as the scope read it."""
        return tuple(held(entry) for entry in self._reading.storage_transformers)

    # --- changing ---------------------------------------------------------

    def update(self, **members: Unpack[ZarrV3ArrayMetadataUpdate]) -> ZarrV3ArrayMetadata:
        """Return this model with `members` (as JSON) replacing the document's members, read in this model's own scope. A member set to `UNSET` is removed. A member the spec does not define is given in `extra_fields`.

        Raises `TypeError` for a member the update does not take. Raises
        `MetadataValidationError` when the resulting document has a
        problem. So members that depend on each other must be passed
        together, for example a `shape` with a grid that fits it.
        """
        changes = member_changes(
            members, ZarrV3ArrayMetadataUpdate, ArrayExtraFieldDefinition.spec_members
        )
        document: dict[str, object] = {**self._document, **changes}
        for key, value in changes.items():
            if value is UNSET:
                del document[key]
        return type(self)(document, context=self._context)

    def with_context(self, context: ZarrV3Context | None = None) -> ZarrV3ArrayMetadata:
        """Return this document read in `context`, whatever that changes: a gain, a loss, or a conflict.

        Raises `MetadataValidationError` when the document has a problem
        in `context`. The existing reading is reused when `context` reads
        every claim identically.
        """
        context = scoped(context, CORE_AND_EXTENSIONS)
        if disagreements(context, claims_of(self._reading.fields())).agrees:
            return self._of(self._document, context, self._reading, self._members)
        return type(self)(self._document, context=context)

    def refined_in(self, context: ZarrV3Context | None = None) -> ZarrV3ArrayMetadata:
        """Return this document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

        Raises `ScopeConflictError` when `context` reads a name with a
        different definition, or with no definition, where this scope read
        it with one. A loss of meaning is refused in the same way as a
        conflict. The error names each such name and its location in the
        document. Raises `MetadataValidationError` when a definition that
        `context` newly claims rejects what was written under its name: a
        gain can expose a problem. `with_context` reads the document in
        any scope.
        """
        context = scoped(context, CORE_AND_EXTENSIONS)
        found = disagreements(context, claims_of(self._reading.fields()))
        if len(found.conflicts) != 0:
            raise ScopeConflictError(located_conflicts(self._reading.fields(), found.conflicts))
        return self.with_context(context)

    def refines(self, other: ZarrV3ArrayMetadata) -> bool:
        """Whether this model holds everything `other` holds. Each field must refine its counterpart, in the order `refines` defines for fields (including the fields nested in each field). Every other member must be the same, with the fill value compared in the spelling of the more informed data type. If that data type rejects the other model's fill value, this is not a refinement."""
        if type(other) is not type(self):
            return False
        if len(self.codecs) != len(other.codecs) or len(self.storage_transformers) != len(
            other.storage_transformers
        ):
            return False
        if self.extra_fields.keys() != other.extra_fields.keys():
            return False
        pairs = (
            (self.data_type, other.data_type),
            (self.chunk_grid, other.chunk_grid),
            (self.chunk_key_encoding, other.chunk_key_encoding),
            *zip(self.codecs, other.codecs, strict=True),
            *zip(self.storage_transformers, other.storage_transformers, strict=True),
            *((self.extra_fields[key], other.extra_fields[key]) for key in self.extra_fields),
        )
        if not all(refines_field(mine, theirs) for mine, theirs in pairs):
            return False
        if len(fill_value_problems(self.data_type, other._members.fill_value)) != 0:
            return False
        return self._plain_key(self.data_type) == other._plain_key(self.data_type)

    # --- constructors -----------------------------------------------------

    @classmethod
    def create_default(
        cls,
        *,
        context: ZarrV3Context | None = None,
        **overrides: Unpack[ZarrV3ArrayMetadataUpdate],
    ) -> ZarrV3ArrayMetadata:
        """Return a scalar `uint8` array, with any document members in `overrides` applied, read in `context`. `overrides` takes what `update` takes, including `UNSET` to drop a member.

        Raises `MetadataValidationError` when the resulting document has a
        problem. So members that depend on each other must be passed
        together: a data type with a fill value of that type, or a grid
        with the shape it fits. The default codec is `bytes` with little
        `endian`, which accepts a data type of any fixed size. Overriding
        `shape` without `chunk_grid` derives a consistent default grid: one
        regular chunk covering the array (`chunk_shape` equal to `shape`,
        with a length of 1 for a dimension of length 0, since "Chunk sizes
        must be greater than zero",
        https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/chunk-grids/regular-grid/index.rst#L40).
        """
        # The grid is derived only from a valid shape. An invalid shape
        # derives no grid, and the read reports it.
        changes = member_changes(
            overrides, ZarrV3ArrayMetadataUpdate, ArrayExtraFieldDefinition.spec_members
        )
        lengths, _ = dimension_lengths(changes, "shape")
        document: dict[str, object] = {
            "zarr_format": 3,
            "node_type": "array",
            "shape": (),
            "fill_value": 0,
            "data_type": "uint8",
            "chunk_grid": {
                "name": "regular",
                "configuration": {"chunk_shape": tuple(max(length, 1) for length in lengths or ())},
            },
            "codecs": ({"name": "bytes", "configuration": {"endian": "little"}},),
            "chunk_key_encoding": {"name": "default"},
        }
        merged = {
            key: value for key, value in {**document, **changes}.items() if value is not UNSET
        }
        return cls(merged, context=context)

    @classmethod
    def from_json(
        cls, data: object, *, context: ZarrV3Context | None = None
    ) -> ZarrV3ArrayMetadata:
        """Return the model of `data`, a v3 array document read in `context`.

        Raises `MetadataValidationError` with every problem the read finds.
        `read_array_metadata_v3` returns the reading this model is built
        from, and the problems of a document that has any.
        """
        return cls(data, context=context)

    @classmethod
    def from_key_value(
        cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV3Context | None = None
    ) -> ZarrV3ArrayMetadata:
        """Return the model of the array document at `zarr.json` in `mapping`, read in `context`.

        Raises `MetadataValidationError` when the key is missing, its bytes
        are not JSON, or the document is not valid. Raises `TypeError` for
        a scope of another format before any bytes are decoded.
        """
        context = scoped(context, CORE_AND_EXTENSIONS)
        return cls(load_store_json(mapping, ZARR_V3_ARRAY_METADATA_STORE_KEY), context=context)

__slots__ class-attribute instance-attribute

__slots__ = (
    "_context",
    "_document",
    "_members",
    "_reading",
    "_shown",
)

attributes property

attributes: Mapping[str, JSONValue]

The attributes, read-only at every level; empty when the document writes none.

chunk_grid property

The chunk grid, as the scope read it.

chunk_key_encoding property

The chunk key encoding, as the scope read it.

codecs property

The codecs, each as the scope read it, in pipeline order.

context property

context: ZarrV3Context

The scope the document was read in. update reads new members in this scope.

data_type property

The data type, as the scope read it.

dimension_names property

dimension_names: tuple[str | None, ...] | UNSET

The dimension names; UNSET when the document writes none.

extra_fields property

Each member the spec does not define, by key, as the scope read it: an AcceptedField when a definition in scope claims the key, and an UnclaimedField otherwise.

fill_value property

fill_value: JSONValue

The fill value as written, read-only at every level.

must_understand_fields property

must_understand_fields: dict[str, ZarrV3ExtensionField]

The extra fields, by key and as written, that no definition in scope reads and that a reader is obligated to understand.

Each unclaimed member of extra_fields not explicitly waived with must_understand: false (the spec's implicit-true rule, https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1571-L1578). The scope is what the reader recognizes, so a compliant reader MUST fail to open the array when this is not empty. The model only reports these fields; refusing to open is up to the reader.

node_type class-attribute instance-attribute

node_type: Final[Literal['array']] = 'array'

reading property

The document as the scope read it: each field, the pipeline, and the chunk each codec receives.

shape property

shape: tuple[int, ...]

The array's shape.

storage_transformers property

The storage transformers, each as the scope read it.

unclaimed_fields property

unclaimed_fields: dict[Loc, UnclaimedField]

Every field nothing in scope claims, at any depth, by its location in the document, except an extra member that says "must_understand": false and every field inside it, claimed or not, since a reader may ignore such a member whole.

This answers whether a reader can open the array: the spec requires a reader to refuse a node with a field it does not understand, and the scope is what the reader understands. A codec inside a shard is located inside the shard's configuration. The model only reports these fields; refusing to open is up to the reader.

zarr_format class-attribute instance-attribute

zarr_format: Final[Literal[3]] = 3

__eq__

__eq__(other: object) -> bool
Source code in src/zarr_metadata/model/_keyed.py
def __eq__(self, other: object) -> bool:
    if type(other) is not type(self):
        return NotImplemented
    return self._model_key == other._model_key

__hash__

__hash__() -> int
Source code in src/zarr_metadata/model/_keyed.py
def __hash__(self) -> int:
    return hash(self._model_key)

__init__

__init__(
    document: object, context: ZarrV3Context | None = None
) -> None
Source code in src/zarr_metadata/model/_array_v3.py
def __init__(self, document: object, context: ZarrV3Context | None = None) -> None:
    context = scoped(context, CORE_AND_EXTENSIONS)
    reading, members = read_array_v3(document, context)
    if members is None:
        raise MetadataValidationError(reading.problems)
    self._adopt(members.document, context, reading, members)

__reduce__

Source code in src/zarr_metadata/model/_array_v3.py
def __reduce__(self) -> tuple[type[ZarrV3ArrayMetadata], tuple[object, ZarrV3Context]]:
    # Pickle the pair and read it again on load, so a model's reading
    # always matches its document.
    return type(self), (self._document, self._context)

__repr__

__repr__() -> str
Source code in src/zarr_metadata/model/_array_v3.py
def __repr__(self) -> str:
    return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

create_default classmethod

create_default(
    *,
    context: ZarrV3Context | None = None,
    **overrides: Unpack[ZarrV3ArrayMetadataUpdate],
) -> ZarrV3ArrayMetadata

Return a scalar uint8 array, with any document members in overrides applied, read in context. overrides takes what update takes, including UNSET to drop a member.

Raises MetadataValidationError when the resulting document has a problem. So members that depend on each other must be passed together: a data type with a fill value of that type, or a grid with the shape it fits. The default codec is bytes with little endian, which accepts a data type of any fixed size. Overriding shape without chunk_grid derives a consistent default grid: one regular chunk covering the array (chunk_shape equal to shape, with a length of 1 for a dimension of length 0, since "Chunk sizes must be greater than zero", https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/chunk-grids/regular-grid/index.rst#L40).

Source code in src/zarr_metadata/model/_array_v3.py
@classmethod
def create_default(
    cls,
    *,
    context: ZarrV3Context | None = None,
    **overrides: Unpack[ZarrV3ArrayMetadataUpdate],
) -> ZarrV3ArrayMetadata:
    """Return a scalar `uint8` array, with any document members in `overrides` applied, read in `context`. `overrides` takes what `update` takes, including `UNSET` to drop a member.

    Raises `MetadataValidationError` when the resulting document has a
    problem. So members that depend on each other must be passed
    together: a data type with a fill value of that type, or a grid
    with the shape it fits. The default codec is `bytes` with little
    `endian`, which accepts a data type of any fixed size. Overriding
    `shape` without `chunk_grid` derives a consistent default grid: one
    regular chunk covering the array (`chunk_shape` equal to `shape`,
    with a length of 1 for a dimension of length 0, since "Chunk sizes
    must be greater than zero",
    https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/chunk-grids/regular-grid/index.rst#L40).
    """
    # The grid is derived only from a valid shape. An invalid shape
    # derives no grid, and the read reports it.
    changes = member_changes(
        overrides, ZarrV3ArrayMetadataUpdate, ArrayExtraFieldDefinition.spec_members
    )
    lengths, _ = dimension_lengths(changes, "shape")
    document: dict[str, object] = {
        "zarr_format": 3,
        "node_type": "array",
        "shape": (),
        "fill_value": 0,
        "data_type": "uint8",
        "chunk_grid": {
            "name": "regular",
            "configuration": {"chunk_shape": tuple(max(length, 1) for length in lengths or ())},
        },
        "codecs": ({"name": "bytes", "configuration": {"endian": "little"}},),
        "chunk_key_encoding": {"name": "default"},
    }
    merged = {
        key: value for key, value in {**document, **changes}.items() if value is not UNSET
    }
    return cls(merged, context=context)

from_json classmethod

from_json(
    data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ArrayMetadata

Return the model of data, a v3 array document read in context.

Raises MetadataValidationError with every problem the read finds. read_array_metadata_v3 returns the reading this model is built from, and the problems of a document that has any.

Source code in src/zarr_metadata/model/_array_v3.py
@classmethod
def from_json(
    cls, data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ArrayMetadata:
    """Return the model of `data`, a v3 array document read in `context`.

    Raises `MetadataValidationError` with every problem the read finds.
    `read_array_metadata_v3` returns the reading this model is built
    from, and the problems of a document that has any.
    """
    return cls(data, context=context)

from_key_value classmethod

from_key_value(
    mapping: Mapping[StoreKey, bytes],
    *,
    context: ZarrV3Context | None = None,
) -> ZarrV3ArrayMetadata

Return the model of the array document at zarr.json in mapping, read in context.

Raises MetadataValidationError when the key is missing, its bytes are not JSON, or the document is not valid. Raises TypeError for a scope of another format before any bytes are decoded.

Source code in src/zarr_metadata/model/_array_v3.py
@classmethod
def from_key_value(
    cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV3Context | None = None
) -> ZarrV3ArrayMetadata:
    """Return the model of the array document at `zarr.json` in `mapping`, read in `context`.

    Raises `MetadataValidationError` when the key is missing, its bytes
    are not JSON, or the document is not valid. Raises `TypeError` for
    a scope of another format before any bytes are decoded.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    return cls(load_store_json(mapping, ZARR_V3_ARRAY_METADATA_STORE_KEY), context=context)

refined_in

refined_in(
    context: ZarrV3Context | None = None,
) -> ZarrV3ArrayMetadata

Return this document read in context. context may claim names this scope left unclaimed, but must not contradict any claim.

Raises ScopeConflictError when context reads a name with a different definition, or with no definition, where this scope read it with one. A loss of meaning is refused in the same way as a conflict. The error names each such name and its location in the document. Raises MetadataValidationError when a definition that context newly claims rejects what was written under its name: a gain can expose a problem. with_context reads the document in any scope.

Source code in src/zarr_metadata/model/_array_v3.py
def refined_in(self, context: ZarrV3Context | None = None) -> ZarrV3ArrayMetadata:
    """Return this document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

    Raises `ScopeConflictError` when `context` reads a name with a
    different definition, or with no definition, where this scope read
    it with one. A loss of meaning is refused in the same way as a
    conflict. The error names each such name and its location in the
    document. Raises `MetadataValidationError` when a definition that
    `context` newly claims rejects what was written under its name: a
    gain can expose a problem. `with_context` reads the document in
    any scope.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    found = disagreements(context, claims_of(self._reading.fields()))
    if len(found.conflicts) != 0:
        raise ScopeConflictError(located_conflicts(self._reading.fields(), found.conflicts))
    return self.with_context(context)

refines

refines(other: ZarrV3ArrayMetadata) -> bool

Whether this model holds everything other holds. Each field must refine its counterpart, in the order refines defines for fields (including the fields nested in each field). Every other member must be the same, with the fill value compared in the spelling of the more informed data type. If that data type rejects the other model's fill value, this is not a refinement.

Source code in src/zarr_metadata/model/_array_v3.py
def refines(self, other: ZarrV3ArrayMetadata) -> bool:
    """Whether this model holds everything `other` holds. Each field must refine its counterpart, in the order `refines` defines for fields (including the fields nested in each field). Every other member must be the same, with the fill value compared in the spelling of the more informed data type. If that data type rejects the other model's fill value, this is not a refinement."""
    if type(other) is not type(self):
        return False
    if len(self.codecs) != len(other.codecs) or len(self.storage_transformers) != len(
        other.storage_transformers
    ):
        return False
    if self.extra_fields.keys() != other.extra_fields.keys():
        return False
    pairs = (
        (self.data_type, other.data_type),
        (self.chunk_grid, other.chunk_grid),
        (self.chunk_key_encoding, other.chunk_key_encoding),
        *zip(self.codecs, other.codecs, strict=True),
        *zip(self.storage_transformers, other.storage_transformers, strict=True),
        *((self.extra_fields[key], other.extra_fields[key]) for key in self.extra_fields),
    )
    if not all(refines_field(mine, theirs) for mine, theirs in pairs):
        return False
    if len(fill_value_problems(self.data_type, other._members.fill_value)) != 0:
        return False
    return self._plain_key(self.data_type) == other._plain_key(self.data_type)

to_json

The document as written, refined. The result shares no objects with the model.

Source code in src/zarr_metadata/model/_array_v3.py
def to_json(self) -> ZarrV3ArrayMetadataJSON:
    """The document as written, refined. The result shares no objects with the model."""
    return cast("ZarrV3ArrayMetadataJSON", copied(self._document))

to_key_value

to_key_value(
    *, indent: int | str | None = None
) -> Mapping[ZarrV3ArrayMetadataStoreKey, bytes]

The document as a store holds it: JSON bytes at zarr.json, indented by indent.

NaN, Infinity and -Infinity in attributes are written as bare tokens, as zarr-python writes them. A strict JSON parser rejects these tokens.

Source code in src/zarr_metadata/model/_array_v3.py
def to_key_value(
    self, *, indent: int | str | None = None
) -> Mapping[ZarrV3ArrayMetadataStoreKey, bytes]:
    """The document as a store holds it: JSON bytes at `zarr.json`, indented by `indent`.

    `NaN`, `Infinity` and `-Infinity` in `attributes` are written as
    bare tokens, as zarr-python writes them. A strict JSON parser
    rejects these tokens.
    """
    return {ZARR_V3_ARRAY_METADATA_STORE_KEY: dump_store_json(self._document, indent=indent)}

update

update(
    **members: Unpack[ZarrV3ArrayMetadataUpdate],
) -> ZarrV3ArrayMetadata

Return this model with members (as JSON) replacing the document's members, read in this model's own scope. A member set to UNSET is removed. A member the spec does not define is given in extra_fields.

Raises TypeError for a member the update does not take. Raises MetadataValidationError when the resulting document has a problem. So members that depend on each other must be passed together, for example a shape with a grid that fits it.

Source code in src/zarr_metadata/model/_array_v3.py
def update(self, **members: Unpack[ZarrV3ArrayMetadataUpdate]) -> ZarrV3ArrayMetadata:
    """Return this model with `members` (as JSON) replacing the document's members, read in this model's own scope. A member set to `UNSET` is removed. A member the spec does not define is given in `extra_fields`.

    Raises `TypeError` for a member the update does not take. Raises
    `MetadataValidationError` when the resulting document has a
    problem. So members that depend on each other must be passed
    together, for example a `shape` with a grid that fits it.
    """
    changes = member_changes(
        members, ZarrV3ArrayMetadataUpdate, ArrayExtraFieldDefinition.spec_members
    )
    document: dict[str, object] = {**self._document, **changes}
    for key, value in changes.items():
        if value is UNSET:
            del document[key]
    return type(self)(document, context=self._context)

with_context

with_context(
    context: ZarrV3Context | None = None,
) -> ZarrV3ArrayMetadata

Return this document read in context, whatever that changes: a gain, a loss, or a conflict.

Raises MetadataValidationError when the document has a problem in context. The existing reading is reused when context reads every claim identically.

Source code in src/zarr_metadata/model/_array_v3.py
def with_context(self, context: ZarrV3Context | None = None) -> ZarrV3ArrayMetadata:
    """Return this document read in `context`, whatever that changes: a gain, a loss, or a conflict.

    Raises `MetadataValidationError` when the document has a problem
    in `context`. The existing reading is reused when `context` reads
    every claim identically.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    if disagreements(context, claims_of(self._reading.fields())).agrees:
        return self._of(self._document, context, self._reading, self._members)
    return type(self)(self._document, context=context)

ZarrV3ArrayMetadataReading dataclass

A v3 array document as a scope read it, whatever it holds: each extension point, its codecs as a pipeline, every problem, and the model when there are no problems.

A field the document does not have is UNSET. A list of fields that the document does not have as a list is empty.

Source code in src/zarr_metadata/model/_array_v3.py
@dataclass(frozen=True, slots=True)
class ZarrV3ArrayMetadataReading:
    """A v3 array document as a scope read it, whatever it holds: each extension point, its codecs as a pipeline, every problem, and the model when there are no problems.

    A field the document does not have is `UNSET`. A list of fields that
    the document does not have as a list is empty.
    """

    data_type: ResolvedField[DataTypeDefinition[Any]] | UNSET = UNSET
    """The data type, as the scope read it."""
    chunk_grid: ResolvedField[ChunkGridDefinition[Any]] | UNSET = UNSET
    """The chunk grid, as the scope read it."""
    chunk_key_encoding: ResolvedField[ChunkKeyEncodingDefinition[Any]] | UNSET = UNSET
    """The chunk key encoding, as the scope read it."""
    chunk: Chunk = dataclasses.field(default_factory=Chunk)
    """The chunks the first codec receives: the length of the grid's chunks along each axis of the shape, and the data type."""
    pipeline: tuple[Stage, ...] = ()
    """The codecs, read as a pipeline: each as the scope read it, with the chunk it receives."""
    storage_transformers: tuple[ResolvedField[StorageTransformerDefinition[Any]], ...] = ()
    """The storage transformers, each as the scope read it."""
    extra_fields: Mapping[str, ResolvedField[ArrayExtraFieldDefinition[Any]]] = dataclasses.field(
        default_factory=no_extra_fields
    )
    """Each member the spec does not define, as the scope read it, by key: an `UnclaimedField` when no definition in scope claims the key."""
    problems: tuple[ValidationProblem, ...] = ()
    """Every reason the document is not valid."""
    metadata: ZarrV3ArrayMetadata | None = None
    """The document's model, which holds these fields, when there is no problem; None otherwise."""

    def __reduce__(self) -> str | tuple[object, ...]:
        # A reading that holds a model pickles and copies the same way the
        # model does (as the pair, read once on load). It is restored as
        # that model's own reading, so there is still one model per
        # document.
        if self.metadata is not None:
            return (reading_of, (self.metadata,))
        # A read-only view does not pickle, so the extra fields go as a dict.
        values = {item.name: getattr(self, item.name) for item in dataclasses.fields(self)}
        return (_array_reading, ({**values, "extra_fields": dict(self.extra_fields)},))

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Each field in the document, as the scope read it, with its location in the document.

        The order is: the extension points, then each codec and storage
        transformer at its index, then each extra field at its key. Each is
        followed by the fields nested in it, as `fields_of` returns them: a
        shard's codecs, or a struct's field types. `with_problems` returns
        each field with its problems.
        """
        own: tuple[tuple[str, ResolvedField[Any] | UNSET], ...] = (
            ("data_type", self.data_type),
            ("chunk_grid", self.chunk_grid),
            ("chunk_key_encoding", self.chunk_key_encoding),
        )
        for key, field in own:
            if field is not UNSET:
                yield from fields_of(field, (key,))
        for index, stage in enumerate(self.pipeline):
            yield from fields_of(stage.codec, ("codecs", index))
        for index, transformer in enumerate(self.storage_transformers):
            yield from fields_of(transformer, ("storage_transformers", index))
        for key, extra in self.extra_fields.items():
            yield from fields_of(extra, (key,))

chunk class-attribute instance-attribute

chunk: Chunk = dataclasses.field(default_factory=Chunk)

The chunks the first codec receives: the length of the grid's chunks along each axis of the shape, and the data type.

chunk_grid class-attribute instance-attribute

The chunk grid, as the scope read it.

chunk_key_encoding class-attribute instance-attribute

chunk_key_encoding: (
    ResolvedField[ChunkKeyEncodingDefinition[Any]] | UNSET
) = UNSET

The chunk key encoding, as the scope read it.

data_type class-attribute instance-attribute

The data type, as the scope read it.

extra_fields class-attribute instance-attribute

extra_fields: Mapping[
    str, ResolvedField[ArrayExtraFieldDefinition[Any]]
] = dataclasses.field(default_factory=no_extra_fields)

Each member the spec does not define, as the scope read it, by key: an UnclaimedField when no definition in scope claims the key.

metadata class-attribute instance-attribute

metadata: ZarrV3ArrayMetadata | None = None

The document's model, which holds these fields, when there is no problem; None otherwise.

pipeline class-attribute instance-attribute

pipeline: tuple[Stage, ...] = ()

The codecs, read as a pipeline: each as the scope read it, with the chunk it receives.

problems class-attribute instance-attribute

problems: tuple[ValidationProblem, ...] = ()

Every reason the document is not valid.

storage_transformers class-attribute instance-attribute

storage_transformers: tuple[
    ResolvedField[StorageTransformerDefinition[Any]], ...
] = ()

The storage transformers, each as the scope read it.

__init__

__init__(
    data_type: ResolvedField[DataTypeDefinition[Any]]
    | UNSET = UNSET,
    chunk_grid: ResolvedField[ChunkGridDefinition[Any]]
    | UNSET = UNSET,
    chunk_key_encoding: ResolvedField[
        ChunkKeyEncodingDefinition[Any]
    ]
    | UNSET = UNSET,
    chunk: Chunk = Chunk(),
    pipeline: tuple[Stage, ...] = (),
    storage_transformers: tuple[
        ResolvedField[StorageTransformerDefinition[Any]],
        ...,
    ] = (),
    extra_fields: Mapping[
        str, ResolvedField[ArrayExtraFieldDefinition[Any]]
    ] = no_extra_fields(),
    problems: tuple[ValidationProblem, ...] = (),
    metadata: ZarrV3ArrayMetadata | None = None,
) -> None

__reduce__

__reduce__() -> str | tuple[object, ...]
Source code in src/zarr_metadata/model/_array_v3.py
def __reduce__(self) -> str | tuple[object, ...]:
    # A reading that holds a model pickles and copies the same way the
    # model does (as the pair, read once on load). It is restored as
    # that model's own reading, so there is still one model per
    # document.
    if self.metadata is not None:
        return (reading_of, (self.metadata,))
    # A read-only view does not pickle, so the extra fields go as a dict.
    values = {item.name: getattr(self, item.name) for item in dataclasses.fields(self)}
    return (_array_reading, ({**values, "extra_fields": dict(self.extra_fields)},))

fields

Each field in the document, as the scope read it, with its location in the document.

The order is: the extension points, then each codec and storage transformer at its index, then each extra field at its key. Each is followed by the fields nested in it, as fields_of returns them: a shard's codecs, or a struct's field types. with_problems returns each field with its problems.

Source code in src/zarr_metadata/model/_array_v3.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Each field in the document, as the scope read it, with its location in the document.

    The order is: the extension points, then each codec and storage
    transformer at its index, then each extra field at its key. Each is
    followed by the fields nested in it, as `fields_of` returns them: a
    shard's codecs, or a struct's field types. `with_problems` returns
    each field with its problems.
    """
    own: tuple[tuple[str, ResolvedField[Any] | UNSET], ...] = (
        ("data_type", self.data_type),
        ("chunk_grid", self.chunk_grid),
        ("chunk_key_encoding", self.chunk_key_encoding),
    )
    for key, field in own:
        if field is not UNSET:
            yield from fields_of(field, (key,))
    for index, stage in enumerate(self.pipeline):
        yield from fields_of(stage.codec, ("codecs", index))
    for index, transformer in enumerate(self.storage_transformers):
        yield from fields_of(transformer, ("storage_transformers", index))
    for key, extra in self.extra_fields.items():
        yield from fields_of(extra, (key,))

ZarrV3ArrayMetadataUpdate

Bases: TypedDict

The members ZarrV3ArrayMetadata.update replaces: each in the form a document writes it, or UNSET to remove a member that a document may omit.

The members a document may omit are dimension_names, attributes, storage_transformers, and any member the spec does not define. Those go in extra_fields, by key: each one given is set, or removed when given as UNSET, and the others are kept. A member is given by its own name only when the spec defines it, so a misspelled name is refused.

Source code in src/zarr_metadata/model/_array_v3.py
class ZarrV3ArrayMetadataUpdate(TypedDict, total=False, closed=True):
    """The members `ZarrV3ArrayMetadata.update` replaces: each in the form a document writes it, or `UNSET` to remove a member that a document may omit.

    The members a document may omit are `dimension_names`, `attributes`,
    `storage_transformers`, and any member the spec does not define. Those
    go in `extra_fields`, by key: each one given is set, or removed when
    given as `UNSET`, and the others are kept. A member is given by its own
    name only when the spec defines it, so a misspelled name is refused.
    """

    shape: tuple[int, ...]
    data_type: ZarrV3MetadataFieldJSON
    chunk_grid: ZarrV3MetadataFieldJSON
    chunk_key_encoding: ZarrV3MetadataFieldJSON
    fill_value: JSONValue
    codecs: tuple[ZarrV3MetadataFieldJSON, ...]
    attributes: Mapping[str, JSONValue] | UNSET
    storage_transformers: tuple[ZarrV3MetadataFieldJSON, ...] | UNSET
    dimension_names: tuple[str | None, ...] | UNSET
    extra_fields: Mapping[str, ZarrV3ExtensionField | UNSET]

attributes instance-attribute

attributes: Mapping[str, JSONValue] | UNSET

chunk_grid instance-attribute

chunk_key_encoding instance-attribute

chunk_key_encoding: ZarrV3MetadataFieldJSON

codecs instance-attribute

data_type instance-attribute

dimension_names instance-attribute

dimension_names: tuple[str | None, ...] | UNSET

extra_fields instance-attribute

fill_value instance-attribute

fill_value: JSONValue

shape instance-attribute

shape: tuple[int, ...]

storage_transformers instance-attribute

storage_transformers: (
    tuple[ZarrV3MetadataFieldJSON, ...] | UNSET
)

ZarrV3ConsolidatedMetadata

Bases: Keyed

A group's inline consolidated_metadata member with every document it lists read, and the scope they were read in.

Models the reference-implementation convention where consolidated metadata is embedded in a group's zarr.json. A group's own read checks only the member's envelope. This model reads each document the member lists, once, as the array or group its node_type declares, and checks that the documents and the group form a hierarchy with the group as its root. Build it to check consolidated metadata before ZarrV3GroupMetadata.update writes it into a group. metadata maps each path to the model, in this scope, of the document at that path. Each document is stored at its node's path without the leading /: the node at /a/b is at a/b. kind is inline and must_understand is False, as the convention declares. A model is only built by reading: the constructor raises MetadataValidationError with every problem, located in the member.

Source code in src/zarr_metadata/model/_group_v3.py
class ZarrV3ConsolidatedMetadata(Keyed):
    """A group's inline `consolidated_metadata` member with every document it lists read, and the scope they were read in.

    Models the reference-implementation convention where consolidated
    metadata is embedded in a group's `zarr.json`. A group's own read
    checks only the member's envelope. This model reads each document the
    member lists, once, as the array or group its `node_type` declares,
    and checks that the documents and the group form a hierarchy with the
    group as its root. Build it to check consolidated metadata before
    `ZarrV3GroupMetadata.update` writes it into a group. `metadata` maps
    each path to the model, in this scope, of the document at that path.
    Each document is stored at its node's path without the leading `/`:
    the node at `/a/b` is at `a/b`. `kind` is `inline` and
    `must_understand` is `False`, as the convention declares. A model is
    only built by reading: the constructor raises
    `MetadataValidationError` with every problem, located in the member.
    """

    __slots__ = ("_context", "_document", "_metadata")

    kind: Final = "inline"
    must_understand: Final = False

    def __init__(self, member: object, context: ZarrV3Context | None = None) -> None:
        context = scoped(context, CORE_AND_EXTENSIONS)
        # The member is always under a group's key, even when read on its
        # own, so nesting depth is counted from there, as in the group.
        readings, members, document, problems = _read_consolidated_v3(
            member, context, (ZARR_V3_CONSOLIDATED_METADATA_KEY,)
        )
        if len(problems) != 0 or document is None:
            raise MetadataValidationError(problems)
        self._adopt(document, context, _nested_models(context, readings, members))

    @classmethod
    def _of(
        cls,
        document: dict[str, JSONValue],
        context: ZarrV3Context,
        metadata: dict[str, ZarrV3NodeMetadata],
    ) -> ZarrV3ConsolidatedMetadata:
        """Build the model of a member that a read found no problems with, holding the models that read built. The readers in this package build models through this method; pyright reports these calls as private use."""
        model = object.__new__(cls)
        model._adopt(document, context, metadata)
        return model

    def _adopt(
        self,
        document: dict[str, JSONValue],
        context: ZarrV3Context,
        metadata: dict[str, ZarrV3NodeMetadata],
    ) -> None:
        self._document = document
        self._context = context
        self._metadata = metadata

    @property
    def reading(self) -> ZarrV3ConsolidatedMetadataReading:
        """The member as the scope read it: no problems, and this model."""
        return ZarrV3ConsolidatedMetadataReading(metadata=self)

    @property
    def context(self) -> ZarrV3Context:
        """The scope the documents were read in."""
        return self._context

    @property
    def metadata(self) -> Mapping[str, ZarrV3NodeMetadata]:
        """A read-only view of the model of each document, keyed by its path below the group."""
        return MappingProxyType(self._metadata)

    def to_json(self) -> ZarrV3ConsolidatedMetadataJSON:
        """The member as written, refined. The result shares no objects with the model."""
        return cast("ZarrV3ConsolidatedMetadataJSON", copied(self._document))

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

    def _key_of(self) -> tuple[object, ...]:
        """The key `==` and `hash` compare for consolidated metadata: each document's path and key, sorted by path."""
        return tuple(
            (path, node._model_key)
            for path, node in sorted(self.metadata.items(), key=lambda item: item[0])
        )

    def __reduce__(self) -> tuple[type[ZarrV3ConsolidatedMetadata], tuple[object, ZarrV3Context]]:
        return type(self), (self._document, self._context)

    def with_context(self, context: ZarrV3Context | None = None) -> ZarrV3ConsolidatedMetadata:
        """Return this member with every document read in `context`, whatever that changes. Raises `MetadataValidationError` when a document has a problem in `context`. The existing readings are reused when `context` reads every claim of every document identically."""
        context = scoped(context, CORE_AND_EXTENSIONS)
        if all(
            disagreements(context, claims_of(node.reading.fields())).agrees
            for node in self._metadata.values()
        ):
            moved = {path: node.with_context(context) for path, node in self._metadata.items()}
            return self._of(self._document, context, moved)
        return type(self)(self._document, context=context)

    def refined_in(self, context: ZarrV3Context | None = None) -> ZarrV3ConsolidatedMetadata:
        """Return this member with every document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

        Raises `ScopeConflictError` naming each conflict, located at the
        document's path. Raises `MetadataValidationError` when a gain
        exposes a problem.
        """
        context = scoped(context, CORE_AND_EXTENSIONS)
        conflicts = [
            dataclasses.replace(
                conflict, loc=("metadata", path, *(() if conflict.loc is None else conflict.loc))
            )
            for path, node in self._metadata.items()
            for conflict in located_conflicts(
                node.reading.fields(),
                disagreements(context, claims_of(node.reading.fields())).conflicts,
            )
        ]
        if len(conflicts) != 0:
            raise ScopeConflictError(conflicts)
        return self.with_context(context)

    def refines(self, other: ZarrV3ConsolidatedMetadata) -> bool:
        """Whether every document this holds refines the document `other` holds at the same path, and both hold the same paths. False when `other` is not consolidated metadata."""
        if type(other) is not type(self):
            return False
        if self._metadata.keys() != other._metadata.keys():
            return False
        return all(
            _node_refines(self._metadata[path], other._metadata[path]) for path in self._metadata
        )

    @classmethod
    def from_json(
        cls, data: object, *, context: ZarrV3Context | None = None
    ) -> ZarrV3ConsolidatedMetadata:
        """Return the model of `data`, a group's `consolidated_metadata` member. Each document is read once in `context`, as the array or group its `node_type` declares. Raises `MetadataValidationError` with every problem found."""
        return cls(data, context=context)

__slots__ class-attribute instance-attribute

__slots__ = ('_context', '_document', '_metadata')

context property

context: ZarrV3Context

The scope the documents were read in.

kind class-attribute instance-attribute

kind: Final = 'inline'

metadata property

A read-only view of the model of each document, keyed by its path below the group.

must_understand class-attribute instance-attribute

must_understand: Final = False

reading property

The member as the scope read it: no problems, and this model.

__eq__

__eq__(other: object) -> bool
Source code in src/zarr_metadata/model/_keyed.py
def __eq__(self, other: object) -> bool:
    if type(other) is not type(self):
        return NotImplemented
    return self._model_key == other._model_key

__hash__

__hash__() -> int
Source code in src/zarr_metadata/model/_keyed.py
def __hash__(self) -> int:
    return hash(self._model_key)

__init__

__init__(
    member: object, context: ZarrV3Context | None = None
) -> None
Source code in src/zarr_metadata/model/_group_v3.py
def __init__(self, member: object, context: ZarrV3Context | None = None) -> None:
    context = scoped(context, CORE_AND_EXTENSIONS)
    # The member is always under a group's key, even when read on its
    # own, so nesting depth is counted from there, as in the group.
    readings, members, document, problems = _read_consolidated_v3(
        member, context, (ZARR_V3_CONSOLIDATED_METADATA_KEY,)
    )
    if len(problems) != 0 or document is None:
        raise MetadataValidationError(problems)
    self._adopt(document, context, _nested_models(context, readings, members))

__reduce__

Source code in src/zarr_metadata/model/_group_v3.py
def __reduce__(self) -> tuple[type[ZarrV3ConsolidatedMetadata], tuple[object, ZarrV3Context]]:
    return type(self), (self._document, self._context)

__repr__

__repr__() -> str
Source code in src/zarr_metadata/model/_group_v3.py
def __repr__(self) -> str:
    return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

from_json classmethod

from_json(
    data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ConsolidatedMetadata

Return the model of data, a group's consolidated_metadata member. Each document is read once in context, as the array or group its node_type declares. Raises MetadataValidationError with every problem found.

Source code in src/zarr_metadata/model/_group_v3.py
@classmethod
def from_json(
    cls, data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ConsolidatedMetadata:
    """Return the model of `data`, a group's `consolidated_metadata` member. Each document is read once in `context`, as the array or group its `node_type` declares. Raises `MetadataValidationError` with every problem found."""
    return cls(data, context=context)

refined_in

refined_in(
    context: ZarrV3Context | None = None,
) -> ZarrV3ConsolidatedMetadata

Return this member with every document read in context. context may claim names this scope left unclaimed, but must not contradict any claim.

Raises ScopeConflictError naming each conflict, located at the document's path. Raises MetadataValidationError when a gain exposes a problem.

Source code in src/zarr_metadata/model/_group_v3.py
def refined_in(self, context: ZarrV3Context | None = None) -> ZarrV3ConsolidatedMetadata:
    """Return this member with every document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

    Raises `ScopeConflictError` naming each conflict, located at the
    document's path. Raises `MetadataValidationError` when a gain
    exposes a problem.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    conflicts = [
        dataclasses.replace(
            conflict, loc=("metadata", path, *(() if conflict.loc is None else conflict.loc))
        )
        for path, node in self._metadata.items()
        for conflict in located_conflicts(
            node.reading.fields(),
            disagreements(context, claims_of(node.reading.fields())).conflicts,
        )
    ]
    if len(conflicts) != 0:
        raise ScopeConflictError(conflicts)
    return self.with_context(context)

refines

refines(other: ZarrV3ConsolidatedMetadata) -> bool

Whether every document this holds refines the document other holds at the same path, and both hold the same paths. False when other is not consolidated metadata.

Source code in src/zarr_metadata/model/_group_v3.py
def refines(self, other: ZarrV3ConsolidatedMetadata) -> bool:
    """Whether every document this holds refines the document `other` holds at the same path, and both hold the same paths. False when `other` is not consolidated metadata."""
    if type(other) is not type(self):
        return False
    if self._metadata.keys() != other._metadata.keys():
        return False
    return all(
        _node_refines(self._metadata[path], other._metadata[path]) for path in self._metadata
    )

to_json

The member as written, refined. The result shares no objects with the model.

Source code in src/zarr_metadata/model/_group_v3.py
def to_json(self) -> ZarrV3ConsolidatedMetadataJSON:
    """The member as written, refined. The result shares no objects with the model."""
    return cast("ZarrV3ConsolidatedMetadataJSON", copied(self._document))

with_context

with_context(
    context: ZarrV3Context | None = None,
) -> ZarrV3ConsolidatedMetadata

Return this member with every document read in context, whatever that changes. Raises MetadataValidationError when a document has a problem in context. The existing readings are reused when context reads every claim of every document identically.

Source code in src/zarr_metadata/model/_group_v3.py
def with_context(self, context: ZarrV3Context | None = None) -> ZarrV3ConsolidatedMetadata:
    """Return this member with every document read in `context`, whatever that changes. Raises `MetadataValidationError` when a document has a problem in `context`. The existing readings are reused when `context` reads every claim of every document identically."""
    context = scoped(context, CORE_AND_EXTENSIONS)
    if all(
        disagreements(context, claims_of(node.reading.fields())).agrees
        for node in self._metadata.values()
    ):
        moved = {path: node.with_context(context) for path, node in self._metadata.items()}
        return self._of(self._document, context, moved)
    return type(self)(self._document, context=context)

ZarrV3ConsolidatedMetadataInput

Bases: TypedDict

The consolidated_metadata member as ZarrV3ConsolidatedMetadata accepts it: in the form a document writes it, with each entry a document or a node model.

A node model is accepted when the scope of the consolidated metadata reads every one of its claims identically, or claims names the model's scope left unclaimed. In the second case the model's document is read again in that scope. A node model is refused, with a problem at its path, when the two scopes read a name differently, or when the scope of the consolidated metadata leaves a claimed name unclaimed.

Source code in src/zarr_metadata/model/_group_v3.py
class ZarrV3ConsolidatedMetadataInput(TypedDict, closed=True):
    """The `consolidated_metadata` member as `ZarrV3ConsolidatedMetadata` accepts it: in the form a document writes it, with each entry a document or a node model.

    A node model is accepted when the scope of the consolidated metadata
    reads every one of its claims identically, or claims names the
    model's scope left unclaimed. In the second case the model's document
    is read again in that scope. A node model is refused, with a problem
    at its path, when the two scopes read a name differently, or when the
    scope of the consolidated metadata leaves a claimed name unclaimed.
    """

    kind: Literal["inline"]
    must_understand: Literal[False]
    metadata: Mapping[str, ZarrV3NodeMetadataInput]

kind instance-attribute

kind: Literal['inline']

metadata instance-attribute

must_understand instance-attribute

must_understand: Literal[False]

ZarrV3ConsolidatedMetadataReading dataclass

A group's consolidated_metadata member as a scope read it: every problem, and the model when there are no problems.

Source code in src/zarr_metadata/model/_group_v3.py
@dataclass(frozen=True, slots=True)
class ZarrV3ConsolidatedMetadataReading:
    """A group's `consolidated_metadata` member as a scope read it: every problem, and the model when there are no problems."""

    problems: tuple[ValidationProblem, ...] = ()
    """Every reason the member is not valid."""
    metadata: ZarrV3ConsolidatedMetadata | None = None
    """The member's model when there is no problem; None otherwise."""

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Each field of each document, as read, located in the member: under `metadata` and the document's path. Nothing when there is no model."""
        if self.metadata is None:
            return
        for path, node in self.metadata.metadata.items():
            for loc, field in node.reading.fields():
                yield ("metadata", path, *loc), field

    def __reduce__(self) -> str | tuple[object, ...]:
        # Same as the other readings: pickle through the model, when there is one.
        if self.metadata is not None:
            return (reading_of, (self.metadata,))
        return object.__reduce__(self)

metadata class-attribute instance-attribute

metadata: ZarrV3ConsolidatedMetadata | None = None

The member's model when there is no problem; None otherwise.

problems class-attribute instance-attribute

problems: tuple[ValidationProblem, ...] = ()

Every reason the member is not valid.

__init__

__init__(
    problems: tuple[ValidationProblem, ...] = (),
    metadata: ZarrV3ConsolidatedMetadata | None = None,
) -> None

__reduce__

__reduce__() -> str | tuple[object, ...]
Source code in src/zarr_metadata/model/_group_v3.py
def __reduce__(self) -> str | tuple[object, ...]:
    # Same as the other readings: pickle through the model, when there is one.
    if self.metadata is not None:
        return (reading_of, (self.metadata,))
    return object.__reduce__(self)

fields

Each field of each document, as read, located in the member: under metadata and the document's path. Nothing when there is no model.

Source code in src/zarr_metadata/model/_group_v3.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Each field of each document, as read, located in the member: under `metadata` and the document's path. Nothing when there is no model."""
    if self.metadata is None:
        return
    for path, node in self.metadata.metadata.items():
        for loc, field in node.reading.fields():
            yield ("metadata", path, *loc), field

ZarrV3GroupMetadata

Bases: Keyed

A v3 group document, and the scope it was read in.

Like ZarrV3ArrayMetadata, the model is this pair. to_json returns the document as written, refined, and context is the scope. attributes and consolidated_metadata are views of the values the read refined. Each member the spec does not define is an extra field, in extra_fields, as the scope read it. CORE reads a consolidated_metadata member as the envelope the core spec fixes, and no definition reads the documents the member lists: they follow a convention of the reference implementation, not the core spec. To read them, pass the member to ZarrV3ConsolidatedMetadata or read_consolidated_metadata_v3, or read one listed document with read_node_metadata_v3. A model is only built by reading: the constructor reads document in context and raises MetadataValidationError with every problem.

Source code in src/zarr_metadata/model/_group_v3.py
class ZarrV3GroupMetadata(Keyed):
    """A v3 group document, and the scope it was read in.

    Like `ZarrV3ArrayMetadata`, the model is this pair. `to_json` returns
    the document as written, refined, and `context` is the scope.
    `attributes` and `consolidated_metadata` are views of the values the
    read refined. Each member the spec does not define is an extra field,
    in `extra_fields`, as the scope read it. `CORE` reads a
    `consolidated_metadata` member as the envelope the core spec fixes,
    and no definition reads the documents the member lists: they follow a
    convention of the reference implementation, not the core spec. To
    read them, pass the member to `ZarrV3ConsolidatedMetadata` or
    `read_consolidated_metadata_v3`, or read one listed document with
    `read_node_metadata_v3`. A model is only built by reading: the
    constructor reads `document` in `context` and raises
    `MetadataValidationError` with every problem.
    """

    __slots__ = ("_context", "_document", "_members", "_reading", "_shown")

    zarr_format: Final[Literal[3]] = 3  # noqa: PYI064 - a bare Final does not narrow a union of models
    node_type: Final[Literal["group"]] = "group"  # noqa: PYI064 - as above

    def __init__(self, document: object, context: ZarrV3Context | None = None) -> None:
        context = scoped(context, CORE_AND_EXTENSIONS)
        reading, members = read_group_v3(document, context)
        if members is None or len(reading.problems) != 0:
            raise MetadataValidationError(reading.problems)
        self._adopt(_document_of(members), context, reading, members)

    @classmethod
    def _of(
        cls,
        document: dict[str, JSONValue],
        context: ZarrV3Context,
        reading: ZarrV3GroupMetadataReading,
        members: GroupMembersV3,
    ) -> ZarrV3GroupMetadata:
        """Build a model of a document that a read found no problems with, holding that reading, without reading it again. The readers in this package build models through this method; pyright reports these calls as private use."""
        model = object.__new__(cls)
        model._adopt(document, context, reading, members)
        return model

    def _adopt(
        self,
        document: dict[str, JSONValue],
        context: ZarrV3Context,
        reading: ZarrV3GroupMetadataReading,
        members: GroupMembersV3,
    ) -> None:
        self._document = document
        self._context = context
        self._members = members
        if members.attributes is None:
            msg = "a group model holds members a read found nothing wrong with"
            raise TypeError(msg)
        # The members the model exposes, read-only at every level.
        self._shown = (
            frozen(members.attributes),
            frozen(members.extra_fields),
            UNSET if members.consolidated is UNSET else frozen(members.consolidated),
            MappingProxyType({key: held(extra) for key, extra in reading.extra_fields.items()}),
        )
        # The reading holds this model, no matter how the model was built.
        self._reading = dataclasses.replace(reading, metadata=self)

    # --- the pair ---------------------------------------------------------

    @property
    def context(self) -> ZarrV3Context:
        """The scope the document was read in. `update` reads new members in this scope."""
        return self._context

    @property
    def reading(self) -> ZarrV3GroupMetadataReading:
        """The document as the scope read it: its extra fields, no problems, and this model."""
        return self._reading

    def to_json(self) -> ZarrV3GroupMetadataJSON:
        """The document as written, refined. The result shares no objects with the model."""
        return cast("ZarrV3GroupMetadataJSON", copied(self._document))

    def to_key_value(
        self, *, indent: int | str | None = None
    ) -> Mapping[ZarrV3GroupMetadataStoreKey, bytes]:
        """The document as a store holds it: JSON bytes at `zarr.json`, indented by `indent`.

        `NaN`, `Infinity` and `-Infinity` in `attributes`, or in the
        documents the consolidated metadata lists, are written as bare
        tokens, as zarr-python writes them. A strict JSON parser rejects
        these tokens.
        """
        return {ZARR_V3_GROUP_METADATA_STORE_KEY: dump_store_json(self._document, indent=indent)}

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

    def _key_of(self) -> tuple[object, ...]:
        """The key `==` and `hash` compare for a v3 group model: its attributes as JSON text, and what each extra field means, as `extra_fields_key` computes it."""
        return (
            json_text(self._members.attributes),
            extra_fields_key(self._reading.extra_fields),
        )

    def __reduce__(self) -> tuple[type[ZarrV3GroupMetadata], tuple[object, ZarrV3Context]]:
        # Pickle the pair, and read it again on load.
        return type(self), (self._document, self._context)

    # --- typed views ------------------------------------------------------

    @property
    def attributes(self) -> Mapping[str, JSONValue]:
        """The attributes, read-only at every level; empty when the document writes none."""
        return self._shown[0]

    @property
    def extra_fields(
        self,
    ) -> Mapping[str, AcceptedField[GroupExtraFieldDefinition[Any]] | UnclaimedField]:
        """Each member the spec does not define, by key, as the scope read it: an `AcceptedField` when a definition in scope claims the key (as `CORE` claims `consolidated_metadata`), and an `UnclaimedField` otherwise."""
        return self._shown[3]

    @property
    def consolidated_metadata(self) -> Mapping[str, JSONValue] | UNSET:
        """The `consolidated_metadata` member as written, refined, and read-only at every level. `UNSET` when the document writes none, or writes one that is not an object, which only a scope that leaves the member unclaimed allows.

        The documents it lists are not read. Pass the member to
        `ZarrV3ConsolidatedMetadata` to read them all, or read one with
        `read_node_metadata_v3`.
        """
        return self._shown[2]

    @property
    def must_understand_fields(self) -> dict[str, ZarrV3ExtensionField]:
        """The extra fields, by key and as written, that no definition in scope reads and that a reader is obligated to understand.

        Each unclaimed member of `extra_fields` not explicitly waived with
        `must_understand: false` (the spec's implicit-true rule, https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1571-L1578). The scope is what
        the reader recognizes, so a compliant reader MUST fail to open the
        group when this is not empty. The model only reports these fields;
        refusing to open is up to the reader.
        """
        return must_understand_subset(self.extra_fields, self._shown[1])

    @property
    def unclaimed_fields(self) -> dict[Loc, UnclaimedField]:
        """Every field nothing in scope claims, at any depth, by its location in the document, except an extra member that says `"must_understand": false` and every field inside it, claimed or not, since a reader may ignore such a member whole.

        This answers whether a reader can open the group: the spec requires
        a reader to refuse a node with a field it does not understand, and
        the scope is what the reader understands. The fields of the
        documents that the group's `consolidated_metadata` lists are not
        included, because the group does not read those documents; read
        them with `consolidated()`. The model only reports these fields;
        refusing to open is up to the reader.
        """
        return unclaimed_fields_of(self._reading.fields(), waived_members(self._shown[1]))

    def consolidated(self) -> ZarrV3ConsolidatedMetadata | None:
        """The documents this group's `consolidated_metadata` lists, read in this group's own scope; None when the group writes no such member.

        A group opens without reading them, so a document with a problem
        fails only here. Raises `MetadataValidationError` with every problem
        in them, located in this group's document, under
        `consolidated_metadata`. A `consolidated_metadata` that is not an
        object, which only a scope that leaves it unclaimed opens, is such a
        problem too. That includes `"consolidated_metadata": null`, which
        zarr-python 3.0 and 3.1 wrote; `read_repaired_node_metadata_v3`
        removes it.
        """
        if ZARR_V3_CONSOLIDATED_METADATA_KEY not in self._document:
            return None
        try:
            return ZarrV3ConsolidatedMetadata(
                self._document[ZARR_V3_CONSOLIDATED_METADATA_KEY], context=self._context
            )
        except MetadataValidationError as error:
            raise MetadataValidationError(
                prefixed(ZARR_V3_CONSOLIDATED_METADATA_KEY, error.problems)
            ) from error

    # --- changing ---------------------------------------------------------

    def update(self, **members: Unpack[ZarrV3GroupMetadataUpdate]) -> ZarrV3GroupMetadata:
        """Return this model with `members` (as JSON) replacing the document's members, read in this model's own scope. A member set to `UNSET` is removed. A member the spec does not define is given in `extra_fields`.

        A `ZarrV3ConsolidatedMetadata` given as `consolidated_metadata` is
        read in this model's scope and written as its document. Raises
        `TypeError` for a member the update does not take, and
        `MetadataValidationError` when the resulting document has a
        problem, such as a listed model that conflicts with this scope.
        """
        changes = member_changes(
            members, ZarrV3GroupMetadataUpdate, GroupExtraFieldDefinition.spec_members
        )
        document: dict[str, object] = {**self._document, **_written(changes, self._context)}
        for key, value in changes.items():
            if value is UNSET:
                del document[key]
        return type(self)(document, context=self._context)

    def with_context(self, context: ZarrV3Context | None = None) -> ZarrV3GroupMetadata:
        """Return this document read in `context`, whatever that changes. Raises `MetadataValidationError` when the document has a problem in `context`. The existing reading is reused when `context` reads every claim identically."""
        context = scoped(context, CORE_AND_EXTENSIONS)
        if disagreements(context, claims_of(self._reading.fields())).agrees:
            return self._of(self._document, context, self._reading, self._members)
        return type(self)(self._document, context=context)

    def refined_in(self, context: ZarrV3Context | None = None) -> ZarrV3GroupMetadata:
        """Return this document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

        Raises `ScopeConflictError` when `context` reads an extra field with
        a different definition, or with no definition, naming each one and
        its key. Raises `MetadataValidationError` when a definition that
        `context` claims rejects what was written under its name. The group
        does not read the documents its `consolidated_metadata` lists, so
        this does not check them: `consolidated()` on the result reads them
        in `context`, and `ZarrV3ConsolidatedMetadata.refined_in` checks
        that they read the same there.
        """
        context = scoped(context, CORE_AND_EXTENSIONS)
        found = disagreements(context, claims_of(self._reading.fields()))
        if len(found.conflicts) != 0:
            raise ScopeConflictError(located_conflicts(self._reading.fields(), found.conflicts))
        return self.with_context(context)

    def refines(self, other: ZarrV3GroupMetadata) -> bool:
        """Whether this group holds everything `other` holds: the same attributes, and the same extra fields, each refining its counterpart in the order `refines` defines for fields. False when `other` is not a v3 group."""
        if type(other) is not type(self):
            return False
        if json_text(self._members.attributes) != json_text(other._members.attributes):
            return False
        mine, theirs = self.extra_fields, other.extra_fields
        if mine.keys() != theirs.keys():
            return False
        return all(refines_field(mine[key], theirs[key]) for key in mine)

    # --- constructors -----------------------------------------------------

    @classmethod
    def create_default(
        cls,
        *,
        context: ZarrV3Context | None = None,
        **overrides: Unpack[ZarrV3GroupMetadataUpdate],
    ) -> ZarrV3GroupMetadata:
        """Return a group with no attributes, with any document members in `overrides` applied, read in `context`. `overrides` takes what `update` takes, including `UNSET` to drop a member and a `ZarrV3ConsolidatedMetadata` as `consolidated_metadata`. Raises `MetadataValidationError` when the resulting document has a problem."""
        context = scoped(context, CORE_AND_EXTENSIONS)
        changes = member_changes(
            overrides, ZarrV3GroupMetadataUpdate, GroupExtraFieldDefinition.spec_members
        )
        given = {key: value for key, value in changes.items() if value is not UNSET}
        document = {"zarr_format": 3, "node_type": "group", **_written(given, context)}
        return cls(document, context=context)

    @classmethod
    def from_json(
        cls, data: object, *, context: ZarrV3Context | None = None
    ) -> ZarrV3GroupMetadata:
        """Return the model of `data`, a v3 group document read in `context`.

        Raises `MetadataValidationError` with every problem the read finds.
        A `consolidated_metadata` of `null`, which zarr-python 3.0 and 3.1
        wrote, is a value the document wrote that is not an object. In a
        scope that claims the member, as `CORE` does, this is a problem,
        because the spec requires an object. Use
        `read_repaired_node_metadata_v3` to read such a store. A member the
        spec does not define is kept in `extra_fields`.
        """
        return cls(data, context=context)

    @classmethod
    def from_key_value(
        cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV3Context | None = None
    ) -> ZarrV3GroupMetadata:
        """Return the model of the group document at `zarr.json` in `mapping`, read in `context`.

        Raises `MetadataValidationError` when the key is missing, its bytes
        are not JSON, or the document is not valid. Raises `TypeError` for
        a scope of another format before any bytes are decoded.
        """
        context = scoped(context, CORE_AND_EXTENSIONS)
        return cls(load_store_json(mapping, ZARR_V3_GROUP_METADATA_STORE_KEY), context=context)

__slots__ class-attribute instance-attribute

__slots__ = (
    "_context",
    "_document",
    "_members",
    "_reading",
    "_shown",
)

attributes property

attributes: Mapping[str, JSONValue]

The attributes, read-only at every level; empty when the document writes none.

consolidated_metadata property

consolidated_metadata: Mapping[str, JSONValue] | UNSET

The consolidated_metadata member as written, refined, and read-only at every level. UNSET when the document writes none, or writes one that is not an object, which only a scope that leaves the member unclaimed allows.

The documents it lists are not read. Pass the member to ZarrV3ConsolidatedMetadata to read them all, or read one with read_node_metadata_v3.

context property

context: ZarrV3Context

The scope the document was read in. update reads new members in this scope.

extra_fields property

Each member the spec does not define, by key, as the scope read it: an AcceptedField when a definition in scope claims the key (as CORE claims consolidated_metadata), and an UnclaimedField otherwise.

must_understand_fields property

must_understand_fields: dict[str, ZarrV3ExtensionField]

The extra fields, by key and as written, that no definition in scope reads and that a reader is obligated to understand.

Each unclaimed member of extra_fields not explicitly waived with must_understand: false (the spec's implicit-true rule, https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1571-L1578). The scope is what the reader recognizes, so a compliant reader MUST fail to open the group when this is not empty. The model only reports these fields; refusing to open is up to the reader.

node_type class-attribute instance-attribute

node_type: Final[Literal['group']] = 'group'

reading property

The document as the scope read it: its extra fields, no problems, and this model.

unclaimed_fields property

unclaimed_fields: dict[Loc, UnclaimedField]

Every field nothing in scope claims, at any depth, by its location in the document, except an extra member that says "must_understand": false and every field inside it, claimed or not, since a reader may ignore such a member whole.

This answers whether a reader can open the group: the spec requires a reader to refuse a node with a field it does not understand, and the scope is what the reader understands. The fields of the documents that the group's consolidated_metadata lists are not included, because the group does not read those documents; read them with consolidated(). The model only reports these fields; refusing to open is up to the reader.

zarr_format class-attribute instance-attribute

zarr_format: Final[Literal[3]] = 3

__eq__

__eq__(other: object) -> bool
Source code in src/zarr_metadata/model/_keyed.py
def __eq__(self, other: object) -> bool:
    if type(other) is not type(self):
        return NotImplemented
    return self._model_key == other._model_key

__hash__

__hash__() -> int
Source code in src/zarr_metadata/model/_keyed.py
def __hash__(self) -> int:
    return hash(self._model_key)

__init__

__init__(
    document: object, context: ZarrV3Context | None = None
) -> None
Source code in src/zarr_metadata/model/_group_v3.py
def __init__(self, document: object, context: ZarrV3Context | None = None) -> None:
    context = scoped(context, CORE_AND_EXTENSIONS)
    reading, members = read_group_v3(document, context)
    if members is None or len(reading.problems) != 0:
        raise MetadataValidationError(reading.problems)
    self._adopt(_document_of(members), context, reading, members)

__reduce__

Source code in src/zarr_metadata/model/_group_v3.py
def __reduce__(self) -> tuple[type[ZarrV3GroupMetadata], tuple[object, ZarrV3Context]]:
    # Pickle the pair, and read it again on load.
    return type(self), (self._document, self._context)

__repr__

__repr__() -> str
Source code in src/zarr_metadata/model/_group_v3.py
def __repr__(self) -> str:
    return f"{type(self).__name__}({self._document!r}, context={self._context!r})"

consolidated

consolidated() -> ZarrV3ConsolidatedMetadata | None

The documents this group's consolidated_metadata lists, read in this group's own scope; None when the group writes no such member.

A group opens without reading them, so a document with a problem fails only here. Raises MetadataValidationError with every problem in them, located in this group's document, under consolidated_metadata. A consolidated_metadata that is not an object, which only a scope that leaves it unclaimed opens, is such a problem too. That includes "consolidated_metadata": null, which zarr-python 3.0 and 3.1 wrote; read_repaired_node_metadata_v3 removes it.

Source code in src/zarr_metadata/model/_group_v3.py
def consolidated(self) -> ZarrV3ConsolidatedMetadata | None:
    """The documents this group's `consolidated_metadata` lists, read in this group's own scope; None when the group writes no such member.

    A group opens without reading them, so a document with a problem
    fails only here. Raises `MetadataValidationError` with every problem
    in them, located in this group's document, under
    `consolidated_metadata`. A `consolidated_metadata` that is not an
    object, which only a scope that leaves it unclaimed opens, is such a
    problem too. That includes `"consolidated_metadata": null`, which
    zarr-python 3.0 and 3.1 wrote; `read_repaired_node_metadata_v3`
    removes it.
    """
    if ZARR_V3_CONSOLIDATED_METADATA_KEY not in self._document:
        return None
    try:
        return ZarrV3ConsolidatedMetadata(
            self._document[ZARR_V3_CONSOLIDATED_METADATA_KEY], context=self._context
        )
    except MetadataValidationError as error:
        raise MetadataValidationError(
            prefixed(ZARR_V3_CONSOLIDATED_METADATA_KEY, error.problems)
        ) from error

create_default classmethod

create_default(
    *,
    context: ZarrV3Context | None = None,
    **overrides: Unpack[ZarrV3GroupMetadataUpdate],
) -> ZarrV3GroupMetadata

Return a group with no attributes, with any document members in overrides applied, read in context. overrides takes what update takes, including UNSET to drop a member and a ZarrV3ConsolidatedMetadata as consolidated_metadata. Raises MetadataValidationError when the resulting document has a problem.

Source code in src/zarr_metadata/model/_group_v3.py
@classmethod
def create_default(
    cls,
    *,
    context: ZarrV3Context | None = None,
    **overrides: Unpack[ZarrV3GroupMetadataUpdate],
) -> ZarrV3GroupMetadata:
    """Return a group with no attributes, with any document members in `overrides` applied, read in `context`. `overrides` takes what `update` takes, including `UNSET` to drop a member and a `ZarrV3ConsolidatedMetadata` as `consolidated_metadata`. Raises `MetadataValidationError` when the resulting document has a problem."""
    context = scoped(context, CORE_AND_EXTENSIONS)
    changes = member_changes(
        overrides, ZarrV3GroupMetadataUpdate, GroupExtraFieldDefinition.spec_members
    )
    given = {key: value for key, value in changes.items() if value is not UNSET}
    document = {"zarr_format": 3, "node_type": "group", **_written(given, context)}
    return cls(document, context=context)

from_json classmethod

from_json(
    data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3GroupMetadata

Return the model of data, a v3 group document read in context.

Raises MetadataValidationError with every problem the read finds. A consolidated_metadata of null, which zarr-python 3.0 and 3.1 wrote, is a value the document wrote that is not an object. In a scope that claims the member, as CORE does, this is a problem, because the spec requires an object. Use read_repaired_node_metadata_v3 to read such a store. A member the spec does not define is kept in extra_fields.

Source code in src/zarr_metadata/model/_group_v3.py
@classmethod
def from_json(
    cls, data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3GroupMetadata:
    """Return the model of `data`, a v3 group document read in `context`.

    Raises `MetadataValidationError` with every problem the read finds.
    A `consolidated_metadata` of `null`, which zarr-python 3.0 and 3.1
    wrote, is a value the document wrote that is not an object. In a
    scope that claims the member, as `CORE` does, this is a problem,
    because the spec requires an object. Use
    `read_repaired_node_metadata_v3` to read such a store. A member the
    spec does not define is kept in `extra_fields`.
    """
    return cls(data, context=context)

from_key_value classmethod

from_key_value(
    mapping: Mapping[StoreKey, bytes],
    *,
    context: ZarrV3Context | None = None,
) -> ZarrV3GroupMetadata

Return the model of the group document at zarr.json in mapping, read in context.

Raises MetadataValidationError when the key is missing, its bytes are not JSON, or the document is not valid. Raises TypeError for a scope of another format before any bytes are decoded.

Source code in src/zarr_metadata/model/_group_v3.py
@classmethod
def from_key_value(
    cls, mapping: Mapping[StoreKey, bytes], *, context: ZarrV3Context | None = None
) -> ZarrV3GroupMetadata:
    """Return the model of the group document at `zarr.json` in `mapping`, read in `context`.

    Raises `MetadataValidationError` when the key is missing, its bytes
    are not JSON, or the document is not valid. Raises `TypeError` for
    a scope of another format before any bytes are decoded.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    return cls(load_store_json(mapping, ZARR_V3_GROUP_METADATA_STORE_KEY), context=context)

refined_in

refined_in(
    context: ZarrV3Context | None = None,
) -> ZarrV3GroupMetadata

Return this document read in context. context may claim names this scope left unclaimed, but must not contradict any claim.

Raises ScopeConflictError when context reads an extra field with a different definition, or with no definition, naming each one and its key. Raises MetadataValidationError when a definition that context claims rejects what was written under its name. The group does not read the documents its consolidated_metadata lists, so this does not check them: consolidated() on the result reads them in context, and ZarrV3ConsolidatedMetadata.refined_in checks that they read the same there.

Source code in src/zarr_metadata/model/_group_v3.py
def refined_in(self, context: ZarrV3Context | None = None) -> ZarrV3GroupMetadata:
    """Return this document read in `context`. `context` may claim names this scope left unclaimed, but must not contradict any claim.

    Raises `ScopeConflictError` when `context` reads an extra field with
    a different definition, or with no definition, naming each one and
    its key. Raises `MetadataValidationError` when a definition that
    `context` claims rejects what was written under its name. The group
    does not read the documents its `consolidated_metadata` lists, so
    this does not check them: `consolidated()` on the result reads them
    in `context`, and `ZarrV3ConsolidatedMetadata.refined_in` checks
    that they read the same there.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    found = disagreements(context, claims_of(self._reading.fields()))
    if len(found.conflicts) != 0:
        raise ScopeConflictError(located_conflicts(self._reading.fields(), found.conflicts))
    return self.with_context(context)

refines

refines(other: ZarrV3GroupMetadata) -> bool

Whether this group holds everything other holds: the same attributes, and the same extra fields, each refining its counterpart in the order refines defines for fields. False when other is not a v3 group.

Source code in src/zarr_metadata/model/_group_v3.py
def refines(self, other: ZarrV3GroupMetadata) -> bool:
    """Whether this group holds everything `other` holds: the same attributes, and the same extra fields, each refining its counterpart in the order `refines` defines for fields. False when `other` is not a v3 group."""
    if type(other) is not type(self):
        return False
    if json_text(self._members.attributes) != json_text(other._members.attributes):
        return False
    mine, theirs = self.extra_fields, other.extra_fields
    if mine.keys() != theirs.keys():
        return False
    return all(refines_field(mine[key], theirs[key]) for key in mine)

to_json

The document as written, refined. The result shares no objects with the model.

Source code in src/zarr_metadata/model/_group_v3.py
def to_json(self) -> ZarrV3GroupMetadataJSON:
    """The document as written, refined. The result shares no objects with the model."""
    return cast("ZarrV3GroupMetadataJSON", copied(self._document))

to_key_value

to_key_value(
    *, indent: int | str | None = None
) -> Mapping[ZarrV3GroupMetadataStoreKey, bytes]

The document as a store holds it: JSON bytes at zarr.json, indented by indent.

NaN, Infinity and -Infinity in attributes, or in the documents the consolidated metadata lists, are written as bare tokens, as zarr-python writes them. A strict JSON parser rejects these tokens.

Source code in src/zarr_metadata/model/_group_v3.py
def to_key_value(
    self, *, indent: int | str | None = None
) -> Mapping[ZarrV3GroupMetadataStoreKey, bytes]:
    """The document as a store holds it: JSON bytes at `zarr.json`, indented by `indent`.

    `NaN`, `Infinity` and `-Infinity` in `attributes`, or in the
    documents the consolidated metadata lists, are written as bare
    tokens, as zarr-python writes them. A strict JSON parser rejects
    these tokens.
    """
    return {ZARR_V3_GROUP_METADATA_STORE_KEY: dump_store_json(self._document, indent=indent)}

update

update(
    **members: Unpack[ZarrV3GroupMetadataUpdate],
) -> ZarrV3GroupMetadata

Return this model with members (as JSON) replacing the document's members, read in this model's own scope. A member set to UNSET is removed. A member the spec does not define is given in extra_fields.

A ZarrV3ConsolidatedMetadata given as consolidated_metadata is read in this model's scope and written as its document. Raises TypeError for a member the update does not take, and MetadataValidationError when the resulting document has a problem, such as a listed model that conflicts with this scope.

Source code in src/zarr_metadata/model/_group_v3.py
def update(self, **members: Unpack[ZarrV3GroupMetadataUpdate]) -> ZarrV3GroupMetadata:
    """Return this model with `members` (as JSON) replacing the document's members, read in this model's own scope. A member set to `UNSET` is removed. A member the spec does not define is given in `extra_fields`.

    A `ZarrV3ConsolidatedMetadata` given as `consolidated_metadata` is
    read in this model's scope and written as its document. Raises
    `TypeError` for a member the update does not take, and
    `MetadataValidationError` when the resulting document has a
    problem, such as a listed model that conflicts with this scope.
    """
    changes = member_changes(
        members, ZarrV3GroupMetadataUpdate, GroupExtraFieldDefinition.spec_members
    )
    document: dict[str, object] = {**self._document, **_written(changes, self._context)}
    for key, value in changes.items():
        if value is UNSET:
            del document[key]
    return type(self)(document, context=self._context)

with_context

with_context(
    context: ZarrV3Context | None = None,
) -> ZarrV3GroupMetadata

Return this document read in context, whatever that changes. Raises MetadataValidationError when the document has a problem in context. The existing reading is reused when context reads every claim identically.

Source code in src/zarr_metadata/model/_group_v3.py
def with_context(self, context: ZarrV3Context | None = None) -> ZarrV3GroupMetadata:
    """Return this document read in `context`, whatever that changes. Raises `MetadataValidationError` when the document has a problem in `context`. The existing reading is reused when `context` reads every claim identically."""
    context = scoped(context, CORE_AND_EXTENSIONS)
    if disagreements(context, claims_of(self._reading.fields())).agrees:
        return self._of(self._document, context, self._reading, self._members)
    return type(self)(self._document, context=context)

ZarrV3GroupMetadataReading dataclass

A v3 group document as a scope read it: each extra field, every problem, and the model when there are no problems.

Source code in src/zarr_metadata/model/_group_v3.py
@dataclass(frozen=True, slots=True)
class ZarrV3GroupMetadataReading:
    """A v3 group document as a scope read it: each extra field, every problem, and the model when there are no problems."""

    problems: tuple[ValidationProblem, ...] = ()
    """Every reason the document is not valid."""
    metadata: ZarrV3GroupMetadata | None = None
    """The document's model when there is no problem; None otherwise."""
    extra_fields: Mapping[str, ResolvedField[GroupExtraFieldDefinition[Any]]] = dataclasses.field(
        default_factory=no_extra_fields
    )
    """Each member the spec does not define, as the scope read it, by key: an `UnclaimedField` when no definition in scope claims the key."""

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Each field in the document, as the scope read it, with its location: the extra fields, each at its key."""
        for key, extra in self.extra_fields.items():
            yield from fields_of(extra, (key,))

    def __reduce__(self) -> str | tuple[object, ...]:
        # Same as the other readings: pickle through the model, when there
        # is one. A read-only view does not pickle, so the extra fields go
        # as a dict.
        if self.metadata is not None:
            return (reading_of, (self.metadata,))
        return (_group_reading, (self.problems, dict(self.extra_fields)))

extra_fields class-attribute instance-attribute

extra_fields: Mapping[
    str, ResolvedField[GroupExtraFieldDefinition[Any]]
] = dataclasses.field(default_factory=no_extra_fields)

Each member the spec does not define, as the scope read it, by key: an UnclaimedField when no definition in scope claims the key.

metadata class-attribute instance-attribute

metadata: ZarrV3GroupMetadata | None = None

The document's model when there is no problem; None otherwise.

problems class-attribute instance-attribute

problems: tuple[ValidationProblem, ...] = ()

Every reason the document is not valid.

__init__

__init__(
    problems: tuple[ValidationProblem, ...] = (),
    metadata: ZarrV3GroupMetadata | None = None,
    extra_fields: Mapping[
        str, ResolvedField[GroupExtraFieldDefinition[Any]]
    ] = no_extra_fields(),
) -> None

__reduce__

__reduce__() -> str | tuple[object, ...]
Source code in src/zarr_metadata/model/_group_v3.py
def __reduce__(self) -> str | tuple[object, ...]:
    # Same as the other readings: pickle through the model, when there
    # is one. A read-only view does not pickle, so the extra fields go
    # as a dict.
    if self.metadata is not None:
        return (reading_of, (self.metadata,))
    return (_group_reading, (self.problems, dict(self.extra_fields)))

fields

Each field in the document, as the scope read it, with its location: the extra fields, each at its key.

Source code in src/zarr_metadata/model/_group_v3.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Each field in the document, as the scope read it, with its location: the extra fields, each at its key."""
    for key, extra in self.extra_fields.items():
        yield from fields_of(extra, (key,))

ZarrV3GroupMetadataUpdate

Bases: TypedDict

The members ZarrV3GroupMetadata.update replaces: each in the form a document writes it, or UNSET to remove it.

consolidated_metadata is typed as the reference implementation writes it, or as any JSON object, such as another group's consolidated_metadata, since a group checks only its envelope. It can also be a ZarrV3ConsolidatedMetadata: its models are read in the group's scope, as a listing of node models is read, and its document is written. A group does not read the documents its consolidated metadata lists, so build that model to check them before they are written. Any other member the spec does not define goes in extra_fields, by key: each one given is set, or removed when given as UNSET, and the others are kept.

Source code in src/zarr_metadata/model/_group_v3.py
class ZarrV3GroupMetadataUpdate(TypedDict, total=False, closed=True):
    """The members `ZarrV3GroupMetadata.update` replaces: each in the form a document writes it, or `UNSET` to remove it.

    `consolidated_metadata` is typed as the reference implementation
    writes it, or as any JSON object, such as another group's
    `consolidated_metadata`, since a group checks only its envelope. It
    can also be a `ZarrV3ConsolidatedMetadata`: its models are read in the
    group's scope, as a listing of node models is read, and its document
    is written. A group does not read the documents its consolidated
    metadata lists, so build that model to check them before they are
    written. Any other member the spec does not define goes in
    `extra_fields`, by key: each one given is set, or removed when given as
    `UNSET`, and the others are kept.
    """

    attributes: Mapping[str, JSONValue] | UNSET
    consolidated_metadata: (
        ZarrV3ConsolidatedMetadataJSON
        | Mapping[str, JSONValue]
        | ZarrV3ConsolidatedMetadata
        | UNSET
    )
    extra_fields: Mapping[str, ZarrV3ExtensionField | UNSET]

attributes instance-attribute

attributes: Mapping[str, JSONValue] | UNSET

consolidated_metadata instance-attribute

extra_fields instance-attribute

ZarrV3RepairedNodeMetadataReading dataclass

A v3 zarr.json read after its known writer bugs were undone: the strict reading of the repaired document, and the repairs.

Source code in src/zarr_metadata/model/_repair.py
@dataclass(frozen=True, slots=True)
class ZarrV3RepairedNodeMetadataReading:
    """A v3 `zarr.json` read after its known writer bugs were undone: the strict reading of the repaired document, and the repairs."""

    reading: ZarrV3NodeMetadataReading
    """The repaired document, as `read_node_metadata_v3` reads it. The problems are those of the repaired document. The model is present when there are no problems."""
    repairs: tuple[Repair, ...]
    """The changes that produced the document that was read."""

reading instance-attribute

The repaired document, as read_node_metadata_v3 reads it. The problems are those of the repaired document. The model is present when there are no problems.

repairs instance-attribute

repairs: tuple[Repair, ...]

The changes that produced the document that was read.

__init__

__init__(
    reading: ZarrV3NodeMetadataReading,
    repairs: tuple[Repair, ...],
) -> None

ZarrV3UnknownNodeMetadataReading dataclass

A v3 document with no node type the spec defines (its node_type is missing, or is neither "array" nor "group"), or a value that is not an object. Only its zarr_format is read, and the problems report it.

This way, a document in another format is reported as such: zarr-python 2's draft of v3 wrote a root zarr.json whose zarr_format is a URL, and a v2 document names format 2.

Source code in src/zarr_metadata/model/_group_v3.py
@dataclass(frozen=True, slots=True)
class ZarrV3UnknownNodeMetadataReading:
    """A v3 document with no node type the spec defines (its `node_type` is missing, or is neither `"array"` nor `"group"`), or a value that is not an object. Only its `zarr_format` is read, and the problems report it.

    This way, a document in another format is reported as such:
    zarr-python 2's draft of v3 wrote a root `zarr.json` whose
    `zarr_format` is a URL, and a v2 document names format 2.
    """

    problems: tuple[ValidationProblem, ...]
    """Why the document is not a node."""

    @property
    def metadata(self) -> None:
        """Its model: always None, since without a node type there is no model to choose."""
        return None

    def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
        """Its fields as read: none, since no field is read."""
        return iter(())

metadata property

metadata: None

Its model: always None, since without a node type there is no model to choose.

problems instance-attribute

problems: tuple[ValidationProblem, ...]

Why the document is not a node.

__init__

__init__(problems: tuple[ValidationProblem, ...]) -> None

fields

Its fields as read: none, since no field is read.

Source code in src/zarr_metadata/model/_group_v3.py
def fields(self) -> Iterator[tuple[Loc, ResolvedField[Any]]]:
    """Its fields as read: none, since no field is read."""
    return iter(())

is_node_name_v3

is_node_name_v3(value: object) -> TypeGuard[NodeName]

Whether value is a v3 node name, meaning validate_node_name_v3 finds no problems.

Source code in src/zarr_metadata/v3/_hierarchy.py
def is_node_name_v3(value: object) -> TypeGuard[NodeName]:
    """Whether `value` is a v3 node name, meaning `validate_node_name_v3` finds no problems."""
    return len(validate_node_name_v3(value)) == 0

is_node_path_v3

is_node_path_v3(value: object) -> TypeGuard[NodePath]

Whether value is a v3 node path, meaning validate_node_path_v3 finds no problems.

Source code in src/zarr_metadata/v3/_hierarchy.py
def is_node_path_v3(value: object) -> TypeGuard[NodePath]:
    """Whether `value` is a v3 node path, meaning `validate_node_path_v3` finds no problems."""
    return len(validate_node_path_v3(value)) == 0

node_metadata_from_json_v3

node_metadata_from_json_v3(
    data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3NodeMetadata

Return the model of data, a v3 zarr.json read in context, as the node type its node_type declares.

The result is what ZarrV3ArrayMetadata.from_json or ZarrV3GroupMetadata.from_json returns. This works like pydantic's TypeAdapter validating a discriminated union. Raises MetadataValidationError with every problem read_node_metadata_v3 finds, including a node_type that is neither array nor group.

Source code in src/zarr_metadata/model/_group_v3.py
def node_metadata_from_json_v3(
    data: object, *, context: ZarrV3Context | None = None
) -> ZarrV3NodeMetadata:
    """Return the model of `data`, a v3 `zarr.json` read in `context`, as the node type its `node_type` declares.

    The result is what `ZarrV3ArrayMetadata.from_json` or
    `ZarrV3GroupMetadata.from_json` returns. This works like pydantic's
    `TypeAdapter` validating a discriminated union. Raises
    `MetadataValidationError` with every problem `read_node_metadata_v3`
    finds, including a `node_type` that is neither array nor group.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    reading = read_node_metadata_v3(data, context=context)
    if reading.metadata is None:
        raise MetadataValidationError(reading.problems)
    return reading.metadata

node_metadata_from_key_value_v2

node_metadata_from_key_value_v2(
    mapping: Mapping[StoreKey, bytes],
    *,
    context: ZarrV2Context | None = None,
) -> ZarrV2NodeMetadata

Return the model of the v2 node whose files are in mapping, a store's keys and bytes: each file is decoded, then read as read_node_metadata_v2 reads it. Raises MetadataValidationError with every problem, including a file whose bytes are not JSON, and TypeError for a scope of another format before any bytes are decoded.

Source code in src/zarr_metadata/model/_group_v2.py
def node_metadata_from_key_value_v2(
    mapping: Mapping[StoreKey, bytes], *, context: ZarrV2Context | None = None
) -> ZarrV2NodeMetadata:
    """Return the model of the v2 node whose files are in `mapping`, a store's keys and bytes: each file is decoded, then read as `read_node_metadata_v2` reads it. Raises `MetadataValidationError` with every problem, including a file whose bytes are not JSON, and `TypeError` for a scope of another format before any bytes are decoded."""
    context = scoped(context, CORE_V2)
    files, problems = decoded_node_files_v2(mapping)
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    reading = read_node_metadata_v2(files, context=context)
    if reading.metadata is None:
        raise MetadataValidationError(reading.problems)
    return reading.metadata

node_metadata_from_key_value_v3

node_metadata_from_key_value_v3(
    mapping: Mapping[StoreKey, bytes],
    *,
    context: ZarrV3Context | None = None,
) -> ZarrV3NodeMetadata

Return the model of the document at zarr.json in mapping, read in context as the node type its node_type declares, as node_metadata_from_json_v3 reads it.

Raises MetadataValidationError when the key is missing, its bytes are not JSON, or the document is not a valid array or group.

Source code in src/zarr_metadata/model/_group_v3.py
def node_metadata_from_key_value_v3(
    mapping: Mapping[StoreKey, bytes], *, context: ZarrV3Context | None = None
) -> ZarrV3NodeMetadata:
    """Return the model of the document at `zarr.json` in `mapping`, read in `context` as the node type its `node_type` declares, as `node_metadata_from_json_v3` reads it.

    Raises `MetadataValidationError` when the key is missing, its bytes
    are not JSON, or the document is not a valid array or group.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    # Array documents and group documents are both stored at `zarr.json`.
    document = load_store_json(mapping, ZARR_V3_GROUP_METADATA_STORE_KEY)
    return node_metadata_from_json_v3(document, context=context)

parse_node_name_v3

parse_node_name_v3(value: object) -> NodeName

Return value as a NodeName, or raise MetadataValidationError with every reason it is not one.

Source code in src/zarr_metadata/v3/_hierarchy.py
def parse_node_name_v3(value: object) -> NodeName:
    """Return `value` as a `NodeName`, or raise `MetadataValidationError` with every reason it is not one."""
    if not is_node_name_v3(value):
        raise MetadataValidationError(validate_node_name_v3(value))
    return value

parse_node_path_v3

parse_node_path_v3(value: object) -> NodePath

Return value as a NodePath, or raise MetadataValidationError with every reason it is not one.

Source code in src/zarr_metadata/v3/_hierarchy.py
def parse_node_path_v3(value: object) -> NodePath:
    """Return `value` as a `NodePath`, or raise `MetadataValidationError` with every reason it is not one."""
    if not is_node_path_v3(value):
        raise MetadataValidationError(validate_node_path_v3(value))
    return value

read_array_metadata_v2

read_array_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ArrayMetadataReading

Read value, a v2 array document, in context (CORE_V2 when none is given), whatever it holds.

The result holds everything the read finds: the dtype, the compressor and each filter as the scope read them (an AcceptedField read by the definition that claims the typestr or id, an UnclaimedField, or a RefusedField), every problem, and, when there are no problems, the document's model. A dtype or codec that the scope refuses is a problem. One that the scope does not claim is not a problem. fill_value is checked against the dtype the scope read.

Source code in src/zarr_metadata/model/_array_v2.py
def read_array_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ArrayMetadataReading:
    """Read `value`, a v2 array document, in `context` (`CORE_V2` when none is given), whatever it holds.

    The result holds everything the read finds: the dtype, the compressor
    and each filter as the scope read them (an `AcceptedField` read by
    the definition that claims the typestr or id, an `UnclaimedField`, or
    a `RefusedField`), every problem, and, when there are no problems,
    the document's model. A dtype or codec that the scope refuses is a
    problem. One that the scope does not claim is not a problem.
    `fill_value` is checked against the dtype the scope read.
    """
    context = scoped(context, CORE_V2)
    reading, members = read_array_v2(value, context)
    if members is None:
        return reading
    model = ZarrV2ArrayMetadata._of(members.document, context, reading, members)  # pyright: ignore[reportPrivateUsage]
    return model.reading

read_array_metadata_v3

read_array_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ArrayMetadataReading

Read value, a v3 array document, in context, whatever it holds.

The result holds everything the read finds: each extension point as context read it (an AcceptedField read by the definition that claims its name, an UnclaimedField, or a RefusedField), the chunks the codecs receive, each codec with the chunk it receives, every problem, and, when there are no problems, the document's model, which holds the same reading.

The read checks the structure, and reads each extension point through the definition that claims its name in context. Examples of problems: a gzip level out of range, or a key that a codec's configuration does not declare. The fill value is checked against the data type as context read it (for example, an int8 fill value of 300). The chunk grid is checked against the shape (for example, a regular grid with chunk lengths for two dimensions, over an array with three). The codecs are read as a pipeline, in order, each checked against the chunk it receives (for example, a transpose whose order has a different number of axes, or a shard that its inner chunks do not divide). A shard's inner and index codecs are checked too. A name that nothing in context claims is not checked, and neither is a fill value of that data type. After a codec with such a name, the next codec receives a chunk with unknown shape and data type. Unknown top-level keys are allowed (they map to extra_fields). A reader must understand each one that does not declare must_understand: false; the model reports these as must_understand_fields.

To apply a policy over the fields (for example, allow only the core spec's fields), iterate over the reading's fields(). A value that is not an object has no fields.

Source code in src/zarr_metadata/model/_array_v3.py
def read_array_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ArrayMetadataReading:
    """Read `value`, a v3 array document, in `context`, whatever it holds.

    The result holds everything the read finds: each extension point as
    `context` read it (an `AcceptedField` read by the definition that
    claims its name, an `UnclaimedField`, or a `RefusedField`), the chunks
    the codecs receive, each codec with the chunk it receives, every
    problem, and, when there are no problems, the document's model, which
    holds the same reading.

    The read checks the structure, and reads each extension point through
    the definition that claims its name in `context`. Examples of
    problems: a gzip `level` out of range, or a key that a codec's
    configuration does not declare. The fill value is checked against the
    data type as `context` read it (for example, an `int8` fill value of
    300). The chunk grid is checked against the shape (for example, a
    regular grid with chunk lengths for two dimensions, over an array with
    three). The codecs are read as a pipeline, in order, each checked
    against the chunk it receives (for example, a `transpose` whose
    `order` has a different number of axes, or a shard that its inner
    chunks do not divide). A shard's inner and index codecs are checked
    too. A name that nothing in `context` claims is not checked, and
    neither is a fill value of that data type. After a codec with such a
    name, the next codec receives a chunk with unknown shape and data
    type. Unknown top-level keys are allowed (they map to
    `extra_fields`). A reader must understand each one that does not
    declare `must_understand: false`; the model reports these as
    `must_understand_fields`.

    To apply a policy over the fields (for example, allow only the core
    spec's fields), iterate over the reading's `fields()`. A value that
    is not an object has no fields.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    reading, members = read_array_v3(value, context)
    if members is None:
        return reading
    model = ZarrV3ArrayMetadata._of(members.document, context, reading, members)  # pyright: ignore[reportPrivateUsage]
    return model.reading

read_consolidated_metadata_v2

read_consolidated_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ConsolidatedMetadataReading

Read value, a v2 .zmetadata document, in context (CORE_V2 when none is given), whatever it holds: every problem the read finds, and the model when there are none. This is the form of ZarrV2ConsolidatedMetadata(value, context) that returns problems instead of raising them.

Source code in src/zarr_metadata/model/_group_v2.py
def read_consolidated_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2ConsolidatedMetadataReading:
    """Read `value`, a v2 `.zmetadata` document, in `context` (`CORE_V2` when none is given), whatever it holds: every problem the read finds, and the model when there are none. This is the form of `ZarrV2ConsolidatedMetadata(value, context)` that returns problems instead of raising them."""
    context = scoped(context, CORE_V2)
    document, nodes, files, problems = _read_consolidated_v2(value, context)
    if len(problems) != 0:
        return ZarrV2ConsolidatedMetadataReading(problems=problems)
    model = ZarrV2ConsolidatedMetadata._of(document, context, nodes, files)  # pyright: ignore[reportPrivateUsage]
    return model.reading

read_consolidated_metadata_v3

read_consolidated_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ConsolidatedMetadataReading

Read value, a group's consolidated_metadata member, in context (CORE_AND_EXTENSIONS when none is given), whatever it holds: every problem the read finds, and the model when there are none. This is the form of ZarrV3ConsolidatedMetadata(value, context) that returns problems instead of raising them.

Source code in src/zarr_metadata/model/_group_v3.py
def read_consolidated_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3ConsolidatedMetadataReading:
    """Read `value`, a group's `consolidated_metadata` member, in `context` (`CORE_AND_EXTENSIONS` when none is given), whatever it holds: every problem the read finds, and the model when there are none. This is the form of `ZarrV3ConsolidatedMetadata(value, context)` that returns problems instead of raising them."""
    context = scoped(context, CORE_AND_EXTENSIONS)
    readings, members, document, problems = _read_consolidated_v3(
        value, context, (ZARR_V3_CONSOLIDATED_METADATA_KEY,)
    )
    if len(problems) != 0 or document is None:
        return ZarrV3ConsolidatedMetadataReading(problems=problems)
    model = ZarrV3ConsolidatedMetadata._of(  # pyright: ignore[reportPrivateUsage]
        document, context, _nested_models(context, readings, members)
    )
    return model.reading

read_group_metadata_v2

read_group_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2GroupMetadataReading

Read value, a v2 group document in the merged form (.zgroup plus attributes from .zattrs), in context (CORE_V2 when none is given), whatever it holds: every problem, and the model when there are none. A group has no fields for a scope to read; context is taken for consistency with every other v2 reader.

Source code in src/zarr_metadata/model/_group_v2.py
def read_group_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2GroupMetadataReading:
    """Read `value`, a v2 group document in the merged form (`.zgroup` plus `attributes` from `.zattrs`), in `context` (`CORE_V2` when none is given), whatever it holds: every problem, and the model when there are none. A group has no fields for a scope to read; `context` is taken for consistency with every other v2 reader."""
    context = scoped(context, CORE_V2)
    problems = _group_problems_v2(value)
    if len(problems) != 0:
        return ZarrV2GroupMetadataReading(problems=problems)
    return ZarrV2GroupMetadata(value, context=context).reading

read_group_metadata_v3

read_group_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3GroupMetadataReading

Read value, a v3 group document, in context, whatever it holds: every problem, and the model when there are none.

Unknown top-level keys are allowed (they map to extra_fields). A reader must understand each one that does not declare must_understand: false; the model reports these as must_understand_fields. A consolidated_metadata member, if present and claimed by the scope, as CORE claims it, is checked against the envelope the core spec fixes. The documents it lists are not read; read_consolidated_metadata_v3 reads them. This is the form of ZarrV3GroupMetadata(value, context) that returns problems instead of raising them.

Source code in src/zarr_metadata/model/_group_v3.py
def read_group_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3GroupMetadataReading:
    """Read `value`, a v3 group document, in `context`, whatever it holds: every problem, and the model when there are none.

    Unknown top-level keys are allowed (they map to `extra_fields`). A
    reader must understand each one that does not declare
    `must_understand: false`; the model reports these as
    `must_understand_fields`. A `consolidated_metadata` member, if
    present and claimed by the scope, as `CORE` claims it, is checked
    against the envelope the core spec fixes. The documents it lists are
    not read; `read_consolidated_metadata_v3` reads them. This is the
    form of `ZarrV3GroupMetadata(value, context)` that returns problems
    instead of raising them.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    reading, members = read_group_v3(value, context)
    if members is None or members.document is None:
        return reading
    return ZarrV3GroupMetadata._of(members.document, context, reading, members).reading  # pyright: ignore[reportPrivateUsage]

read_node_metadata_v2

read_node_metadata_v2(
    files: Mapping[str, object],
    *,
    context: ZarrV2Context | None = None,
) -> ZarrV2NodeMetadataReading

Read the v2 node described by files, in context (CORE_V2 when none is given), as the node type its files declare.

files maps each of the node's files to its decoded JSON: .zarray or .zgroup, and .zattrs when there is one. They can come from a store, decoded (as node_metadata_from_key_value_v2 does), or from the entries of a .zmetadata, which hold the same JSON. A v2 document does not name its node type; the file does. A .zarray makes the node an array, read as read_array_metadata_v2 reads it, and a .zgroup makes it a group, read as read_group_metadata_v2 reads it, with the attributes at .zattrs in either case. files with neither, or with both, give a ZarrV2UnknownNodeMetadataReading with the problem. A .zarray or .zgroup that writes attributes itself gives the reading of its node type with no model, and with that problem followed by the problems of the rest of the file. This is the v2 counterpart of read_node_metadata_v3, which reads one zarr.json and its node_type.

Source code in src/zarr_metadata/model/_group_v2.py
def read_node_metadata_v2(
    files: Mapping[str, object], *, context: ZarrV2Context | None = None
) -> ZarrV2NodeMetadataReading:
    """Read the v2 node described by `files`, in `context` (`CORE_V2` when none is given), as the node type its files declare.

    `files` maps each of the node's files to its decoded JSON: `.zarray`
    or `.zgroup`, and `.zattrs` when there is one. They can come from a
    store, decoded (as `node_metadata_from_key_value_v2` does), or from the
    entries of a `.zmetadata`, which hold the same JSON. A v2 document
    does not name its node type; the file does. A `.zarray` makes the node
    an array, read as `read_array_metadata_v2` reads it, and a `.zgroup`
    makes it a group, read as `read_group_metadata_v2` reads it, with the
    attributes at `.zattrs` in either case. `files` with neither, or with
    both, give a `ZarrV2UnknownNodeMetadataReading` with the problem. A
    `.zarray` or `.zgroup` that writes `attributes` itself gives the
    reading of its node type with no model, and with that problem
    followed by the problems of the rest of the file. This is the
    v2 counterpart of `read_node_metadata_v3`, which reads one `zarr.json`
    and its `node_type`.
    """
    context = scoped(context, CORE_V2)
    is_array = ZARR_V2_ARRAY_METADATA_STORE_KEY in files
    is_group = ZARR_V2_GROUP_METADATA_STORE_KEY in files
    if is_array and is_group:
        both = ValidationProblem(
            (ZARR_V2_GROUP_METADATA_STORE_KEY,),
            f"a node is an array or a group, not both: a {ZARR_V2_ARRAY_METADATA_STORE_KEY} is beside it",
            "invalid_value",
        )
        return ZarrV2UnknownNodeMetadataReading((both,))
    if is_array:
        document, problems = node_document_v2(
            files, ZARR_V2_ARRAY_METADATA_STORE_KEY, ARRAY_FILE_ATTRIBUTES
        )
        reading = read_array_metadata_v2(document, context=context)
        if len(problems) != 0:
            return dataclasses.replace(
                reading, problems=(*problems, *reading.problems), metadata=None
            )
        return reading
    if is_group:
        document, problems = node_document_v2(
            files, ZARR_V2_GROUP_METADATA_STORE_KEY, _GROUP_FILE_ATTRIBUTES
        )
        reading = read_group_metadata_v2(document, context=context)
        if len(problems) != 0:
            return ZarrV2GroupMetadataReading(problems=(*problems, *reading.problems))
        return reading
    neither = ValidationProblem(
        (),
        f"expected a {ZARR_V2_ARRAY_METADATA_STORE_KEY} or a {ZARR_V2_GROUP_METADATA_STORE_KEY}",
        "missing_key",
    )
    return ZarrV2UnknownNodeMetadataReading((neither,))

read_node_metadata_v3

read_node_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3NodeMetadataReading

Read value, a v3 zarr.json, in context as the node type its node_type declares.

The node type is the tag of a union, like pydantic's discriminator and zod's discriminated union. An array is read by read_array_metadata_v3, and a group by read_group_metadata_v3. A document that declares neither, or a value that is not an object, gives a ZarrV3UnknownNodeMetadataReading with the problems, including any problem with its zarr_format. So no caller has to read node_type from unvalidated JSON, and a document in another format is reported as not v3.

Source code in src/zarr_metadata/model/_group_v3.py
def read_node_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3NodeMetadataReading:
    """Read `value`, a v3 `zarr.json`, in `context` as the node type its `node_type` declares.

    The node type is the tag of a union, like pydantic's discriminator
    and zod's discriminated union. An array is read by
    `read_array_metadata_v3`, and a group by `read_group_metadata_v3`. A
    document that declares neither, or a value that is not an object,
    gives a `ZarrV3UnknownNodeMetadataReading` with the problems,
    including any problem with its `zarr_format`. So no caller has to
    read `node_type` from unvalidated JSON, and a document in another
    format is reported as not v3.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    node_type, problems = _node_type(value)
    if node_type == "array":
        return read_array_metadata_v3(value, context=context)
    if node_type == "group":
        return read_group_metadata_v3(value, context=context)
    return ZarrV3UnknownNodeMetadataReading(problems)

read_repaired_consolidated_metadata_v2

read_repaired_consolidated_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2RepairedConsolidatedMetadataReading

Read value, a v2 .zmetadata, in context as read_consolidated_metadata_v2 does, after repair_consolidated_metadata_v2 has undone each known writer bug in it.

Use this to read stores that other writers made. A caller requests repairs by calling this function instead of read_consolidated_metadata_v2. Content that no repair applies to is read as it is, and reported the same way read_consolidated_metadata_v2 reports it.

Source code in src/zarr_metadata/model/_repair.py
def read_repaired_consolidated_metadata_v2(
    value: object, *, context: ZarrV2Context | None = None
) -> ZarrV2RepairedConsolidatedMetadataReading:
    """Read `value`, a v2 `.zmetadata`, in `context` as `read_consolidated_metadata_v2` does, after `repair_consolidated_metadata_v2` has undone each known writer bug in it.

    Use this to read stores that other writers made. A caller requests
    repairs by calling this function instead of
    `read_consolidated_metadata_v2`. Content that no repair applies to is
    read as it is, and reported the same way
    `read_consolidated_metadata_v2` reports it.
    """
    context = scoped(context, CORE_V2)
    repaired, repairs = repair_consolidated_metadata_v2(value)
    return ZarrV2RepairedConsolidatedMetadataReading(
        read_consolidated_metadata_v2(repaired, context=context), repairs
    )

read_repaired_node_metadata_v2

read_repaired_node_metadata_v2(
    files: Mapping[str, object],
    *,
    context: ZarrV2Context | None = None,
) -> ZarrV2RepairedNodeMetadataReading

Read files, a v2 node's files, in context as read_node_metadata_v2 does, after repair_node_metadata_v2 has undone each known writer bug in them.

Use this to read stores that other writers made. A caller requests repairs by calling this function instead of read_node_metadata_v2. Content that no repair applies to is read as it is, and reported the same way read_node_metadata_v2 reports it.

Source code in src/zarr_metadata/model/_repair.py
def read_repaired_node_metadata_v2(
    files: Mapping[str, object], *, context: ZarrV2Context | None = None
) -> ZarrV2RepairedNodeMetadataReading:
    """Read `files`, a v2 node's files, in `context` as `read_node_metadata_v2` does, after `repair_node_metadata_v2` has undone each known writer bug in them.

    Use this to read stores that other writers made. A caller requests
    repairs by calling this function instead of `read_node_metadata_v2`.
    Content that no repair applies to is read as it is, and reported the
    same way `read_node_metadata_v2` reports it.
    """
    context = scoped(context, CORE_V2)
    repaired, repairs = repair_node_metadata_v2(files)
    return ZarrV2RepairedNodeMetadataReading(
        read_node_metadata_v2(repaired, context=context), repairs
    )

read_repaired_node_metadata_v3

read_repaired_node_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3RepairedNodeMetadataReading

Read value, a v3 zarr.json, in context as read_node_metadata_v3 does, after repair_node_metadata_v3 has undone each known writer bug in it.

Use this to read stores that other writers made. A caller requests repairs by calling this function instead of read_node_metadata_v3. Content that no repair applies to is read as it is, and reported the same way read_node_metadata_v3 reports it.

Source code in src/zarr_metadata/model/_repair.py
def read_repaired_node_metadata_v3(
    value: object, *, context: ZarrV3Context | None = None
) -> ZarrV3RepairedNodeMetadataReading:
    """Read `value`, a v3 `zarr.json`, in `context` as `read_node_metadata_v3` does, after `repair_node_metadata_v3` has undone each known writer bug in it.

    Use this to read stores that other writers made. A caller requests
    repairs by calling this function instead of `read_node_metadata_v3`.
    Content that no repair applies to is read as it is, and reported the
    same way `read_node_metadata_v3` reports it.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    repaired, repairs = repair_node_metadata_v3(value)
    return ZarrV3RepairedNodeMetadataReading(
        read_node_metadata_v3(repaired, context=context), repairs
    )

repair_consolidated_metadata_v2

repair_consolidated_metadata_v2(
    value: object,
) -> tuple[object, tuple[Repair, ...]]

Undo each known writer bug in value, a v2 .zmetadata, and return the result with the list of changes.

zarr-python 3.x writes a consolidated_metadata member into each .zgroup entry below the root. This function removes it. The root's entry is left alone, since no writer puts the member there. Each .zarray entry is repaired as repair_node_metadata_v2 repairs a .zarray. Content that no repair applies to is left as it is, and value is not modified. A document with none of the bugs is returned as it is, with no repairs.

Source code in src/zarr_metadata/model/_repair.py
def repair_consolidated_metadata_v2(value: object) -> tuple[object, tuple[Repair, ...]]:
    """Undo each known writer bug in `value`, a v2 `.zmetadata`, and return the result with the list of changes.

    zarr-python 3.x writes a `consolidated_metadata` member into each
    `.zgroup` entry below the root. This function removes it. The root's
    entry is left alone, since no writer puts the member there. Each
    `.zarray` entry is repaired as `repair_node_metadata_v2` repairs a
    `.zarray`. Content that no repair applies to is left as it is, and
    `value` is not modified. A document with none of the bugs is returned
    as it is, with no repairs.
    """
    if not is_json_object(value):
        return value, ()
    document = value
    entries = document.get("metadata")
    if not is_object(entries):
        return value, ()
    repairs: list[Repair] = []
    held: dict[object, object] = {}
    for key, entry in entries.items():
        if isinstance(key, str):
            path, _, name = key.rpartition("/")
            # Below the root only: no writer puts the member in the root's .zgroup.
            if name == ZARR_V2_GROUP_METADATA_STORE_KEY and path.strip("/") != "":
                entry = _without_consolidated_metadata(entry, ("metadata", key), repairs)
            if name == ZARR_V2_ARRAY_METADATA_STORE_KEY:
                entry = _repaired_zarray(entry, ("metadata", key), repairs)
        held[key] = entry
    if len(repairs) == 0:
        return value, ()
    return {**document, "metadata": held}, tuple(repairs)

repair_node_metadata_v2

repair_node_metadata_v2(
    files: Mapping[str, object],
) -> tuple[Mapping[str, object], tuple[Repair, ...]]

Undo each known writer bug in files, a v2 node's files as read_node_metadata_v2 takes them, and return the result with the list of changes.

zarr-python 3.0.0 to 3.0.6 wrote a non-finite fill value in a .zarray as a bare token, NaN, which Python's json module reads as a float. This function writes it as the string the v2 spec uses, "NaN", and does the same for each component of a complex fill value. Each repair is located in files, under the name of the file. Content that no repair applies to is left as it is, and files is not modified. Files with none of the bugs are returned as they are, with no repairs.

Source code in src/zarr_metadata/model/_repair.py
def repair_node_metadata_v2(
    files: Mapping[str, object],
) -> tuple[Mapping[str, object], tuple[Repair, ...]]:
    """Undo each known writer bug in `files`, a v2 node's files as `read_node_metadata_v2` takes them, and return the result with the list of changes.

    zarr-python 3.0.0 to 3.0.6 wrote a non-finite fill value in a
    `.zarray` as a bare token, `NaN`, which Python's `json` module reads as
    a float. This function writes it as the string the v2 spec uses,
    `"NaN"`, and does the same for each component of a complex fill value.
    Each repair is located in `files`, under the name of the file. Content
    that no repair applies to is left as it is, and `files` is not
    modified. Files with none of the bugs are returned as they are, with
    no repairs.
    """
    if ZARR_V2_ARRAY_METADATA_STORE_KEY not in files:
        return files, ()
    repairs: list[Repair] = []
    zarray = _repaired_zarray(
        files[ZARR_V2_ARRAY_METADATA_STORE_KEY], (ZARR_V2_ARRAY_METADATA_STORE_KEY,), repairs
    )
    if len(repairs) == 0:
        return files, ()
    return {**files, ZARR_V2_ARRAY_METADATA_STORE_KEY: zarray}, tuple(repairs)

repair_node_metadata_v3

repair_node_metadata_v3(
    value: object,
) -> tuple[object, tuple[Repair, ...]]

Undo each known writer bug in value, a v3 zarr.json, and return the result with the list of changes.

Each document in the consolidated metadata is repaired too. A group does not read those documents, but ZarrV3ConsolidatedMetadata reads them from the repaired group's member. Content that no repair applies to is left as it is. value is not modified: a repaired document is a new object, which shares its unchanged parts with value. A document with none of the bugs is returned as it is, with no repairs.

Source code in src/zarr_metadata/model/_repair.py
def repair_node_metadata_v3(value: object) -> tuple[object, tuple[Repair, ...]]:
    """Undo each known writer bug in `value`, a v3 `zarr.json`, and return the result with the list of changes.

    Each document in the consolidated metadata is repaired too. A group
    does not read those documents, but `ZarrV3ConsolidatedMetadata` reads
    them from the repaired group's member. Content that no repair applies
    to is left as it is. `value` is not modified: a repaired document is a
    new object, which shares its unchanged parts with `value`. A document
    with none of the bugs is returned as it is, with no repairs.
    """
    return _repaired(value, ())

validate_node_name_v3

validate_node_name_v3(
    value: object,
) -> tuple[ValidationProblem, ...]

Every reason value is not a v3 node name, reported in one invalid_value problem, or an invalid_type problem if value is not a string.

Source code in src/zarr_metadata/v3/_hierarchy.py
def validate_node_name_v3(value: object) -> tuple[ValidationProblem, ...]:
    """Every reason `value` is not a v3 node name, reported in one `invalid_value` problem, or an `invalid_type` problem if `value` is not a string."""
    if not isinstance(value, str):
        return with_input(_string_problems(value, "a node name"), value)
    faults = name_faults(value)
    if len(faults) == 0:
        return ()
    message = f"expected a node name, got {shown(value)}, which {said(faults)}"
    return with_input((ValidationProblem((), message, "invalid_value"),), value)

validate_node_path_v3

validate_node_path_v3(
    value: object,
) -> tuple[ValidationProblem, ...]

Every reason value is not a v3 node path, reported in one invalid_value problem, or an invalid_type problem if value is not a string.

Source code in src/zarr_metadata/v3/_hierarchy.py
def validate_node_path_v3(value: object) -> tuple[ValidationProblem, ...]:
    """Every reason `value` is not a v3 node path, reported in one `invalid_value` problem, or an `invalid_type` problem if `value` is not a string."""
    if not isinstance(value, str):
        return with_input(_string_problems(value, "a node path"), value)
    faults = path_faults(value)
    if len(faults) == 0:
        return ()
    message = f"expected a node path, got {shown(value)}, which {said(faults)}"
    return with_input((ValidationProblem((), message, "invalid_value"),), value)