Skip to content

zarr_metadata.v3.codec

zarr_metadata.v3.codec

Zarr v3 codec spec types.

Each codec defined by the spec or by zarr-extensions has its own submodule (blosc, bytes, cast_value, crc32c, gzip, scale_offset, sharding_indexed, transpose, zstd).

The <X>CodecMetadata aliases re-exported here are the canonical type for each codec's permitted JSON shapes (object form plus, where the spec allows, a bare-string short-hand form). For the underlying <X>CodecObject, <X>CodecConfiguration, etc., import directly from the leaf submodule.

For the field-level "any codec entry" alias (used in array metadata's codecs list and in sharding's inner pipelines), import ZarrV3MetadataFieldJSON from zarr_metadata.v3.

The kind submodule sorts the known codec names into the spec's three pipeline kinds (array -> array, array -> bytes, bytes -> bytes).

See https://zarr-specs.readthedocs.io/en/latest/v3/codecs/index.html

zarr_metadata.v3.codec.blosc

Blosc codec types.

See https://zarr-specs.readthedocs.io/en/latest/v3/codecs/blosc/index.html

BLOSC_CNAME module-attribute

BLOSC_CNAME: Final = (
    "lz4",
    "lz4hc",
    "blosclz",
    "snappy",
    "zlib",
    "zstd",
)

Tuple of permitted values for the cname field of the blosc codec.

BLOSC_CODEC_NAME module-attribute

BLOSC_CODEC_NAME: Final = 'blosc'

The name field value of the blosc codec.

BLOSC_NO_SHUFFLE module-attribute

BLOSC_NO_SHUFFLE: Final = 'noshuffle'

The shuffle value under which typesize carries no information.

The spec requires typesize "unless shuffle is "noshuffle", in which case the value is ignored", so this is the one value that changes whether another member is required.

BLOSC_SHUFFLE module-attribute

BLOSC_SHUFFLE: Final = (
    "noshuffle",
    "shuffle",
    "bitshuffle",
)

Tuple of permitted values for the shuffle field of the blosc codec.

BloscCName module-attribute

BloscCName = Literal[
    "lz4", "lz4hc", "blosclz", "snappy", "zlib", "zstd"
]

Literal type of blosc compressor identifiers.

BloscCodecMetadata module-attribute

BloscCodecMetadata = BloscCodecObject

Permitted JSON shape for blosc codec metadata.

The configuration has multiple required keys (cname, clevel, shuffle, blocksize), so only the object form is valid; the short-hand-name form is not permitted by the spec for this codec. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/blosc/index.rst#L57-L98 (configuration parameters) https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1562-L1564 (short-hand names only "if no configuration metadata is required")

BloscCodecName module-attribute

BloscCodecName = Literal['blosc']

Literal type of the name field of the blosc codec.

BloscShuffle module-attribute

BloscShuffle = Literal["noshuffle", "shuffle", "bitshuffle"]

Literal type of blosc shuffle mode names.

__all__ module-attribute

__all__ = [
    "BLOSC_CNAME",
    "BLOSC_CODEC_NAME",
    "BLOSC_NO_SHUFFLE",
    "BLOSC_SHUFFLE",
    "BloscCName",
    "BloscCodec",
    "BloscCodecConfiguration",
    "BloscCodecMetadata",
    "BloscCodecName",
    "BloscCodecObject",
    "BloscShuffle",
    "canonical_configuration",
]

BloscCodec dataclass

Bases: CodecEntity

The blosc codec, coerced from its metadata.

Everything blosc knows about itself: the shape its metadata takes, the values the spec allows in it, and the simplest spelling of an equivalent document.

Source code in src/zarr_metadata/v3/codec/blosc.py
@dataclass(frozen=True)
class BloscCodec(CodecEntity):
    """The `blosc` codec, coerced from its metadata.

    Everything blosc knows about itself: the shape its metadata takes, the
    values the spec allows in it, and the simplest spelling of an
    equivalent document.
    """

    cname: BloscCName = "zstd"
    clevel: int = 5
    shuffle: BloscShuffle = "noshuffle"
    blocksize: int = 0
    typesize: int | UNSET = UNSET

    identifier: ClassVar[str] = BLOSC_CODEC_NAME
    variable_size: ClassVar[bool] = True
    kind: ClassVar[CodecKind] = "bytes_bytes"

    # Every member is required but `typesize`, which only means something
    # when shuffling; `problems` is where that conditional lives.
    configuration_required: ClassVar[bool] = True

    member_types: ClassVar[MemberTypes] = {
        "cname": (True, one_of(BLOSC_CNAME)),
        "clevel": (True, is_int),
        "shuffle": (True, one_of(BLOSC_SHUFFLE)),
        "blocksize": (True, is_int),
        "typesize": (False, is_int),
    }

    def problems(self) -> tuple[ValidationProblem, ...]:
        """The value constraints the spec places on a blosc configuration."""
        found: list[ValidationProblem] = []
        if not 0 <= self.clevel <= 9:
            found.extend(
                problem(
                    ("clevel",),
                    f"expected an integer in [0, 9], got {self.clevel}",
                    "invalid_value",
                )
            )
        if self.blocksize < 0:
            found.extend(
                problem(
                    ("blocksize",),
                    f"expected a non-negative integer, got {self.blocksize}",
                    "invalid_value",
                )
            )
        # Only where it means something: under `noshuffle` the spec says
        # "the value is ignored" and `configuration` drops it, so judging
        # it would let `to_json` turn an invalid codec into a valid
        # document.
        if self.typesize is not UNSET and self.shuffle != BLOSC_NO_SHUFFLE and self.typesize < 1:
            found.extend(
                problem(
                    ("typesize",),
                    f"expected a positive integer, got {self.typesize}",
                    "invalid_value",
                )
            )
        if self.shuffle != BLOSC_NO_SHUFFLE and self.typesize is UNSET:
            found.extend(
                problem(
                    ("typesize",),
                    f"typesize is required when shuffle is {self.shuffle!r}",
                    "missing_key",
                )
            )
        return tuple(found)

    @classmethod
    def from_configuration(cls, **configuration: Unpack[BloscCodecConfiguration]) -> Self:
        """This codec from its configuration members.

        The configuration TypedDict unpacked *is* this constructor's
        signature, so a caller with a well-typed configuration builds a
        well-typed codec, and a type checker says so at the call site.
        """
        return cls(**configuration)

    def canonical(self) -> Self:
        """Without a `typesize` that `noshuffle` renders meaningless.

        The spec says of that case that "the value is ignored", so two
        documents differing only there describe the same codec.
        """
        if self.shuffle != BLOSC_NO_SHUFFLE or self.typesize is UNSET:
            return self
        return replace(self, typesize=UNSET)

    def to_json(self) -> BloscCodecObject:
        return cast("BloscCodecObject", super().to_json())

blocksize class-attribute instance-attribute

blocksize: int = 0

clevel class-attribute instance-attribute

clevel: int = 5

cname class-attribute instance-attribute

cname: BloscCName = 'zstd'

configuration_required class-attribute

configuration_required: bool = True

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

identifier class-attribute

identifier: str = BLOSC_CODEC_NAME

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'bytes_bytes'

member_types class-attribute

member_types: MemberTypes = {
    "cname": (True, one_of(BLOSC_CNAME)),
    "clevel": (True, is_int),
    "shuffle": (True, one_of(BLOSC_SHUFFLE)),
    "blocksize": (True, is_int),
    "typesize": (False, is_int),
}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

shuffle class-attribute instance-attribute

shuffle: BloscShuffle = 'noshuffle'

typesize class-attribute instance-attribute

typesize: int | UNSET = UNSET

variable_size class-attribute

variable_size: bool = True

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    cname: BloscCName = "zstd",
    clevel: int = 5,
    shuffle: BloscShuffle = "noshuffle",
    blocksize: int = 0,
    typesize: int | UNSET = UNSET,
    *,
    must_understand: bool = True,
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

Without a typesize that noshuffle renders meaningless.

The spec says of that case that "the value is ignored", so two documents differing only there describe the same codec.

Source code in src/zarr_metadata/v3/codec/blosc.py
def canonical(self) -> Self:
    """Without a `typesize` that `noshuffle` renders meaningless.

    The spec says of that case that "the value is ignored", so two
    documents differing only there describe the same codec.
    """
    if self.shuffle != BLOSC_NO_SHUFFLE or self.typesize is UNSET:
        return self
    return replace(self, typesize=UNSET)

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def coerce(cls, value: object, context: Context) -> Coerced[Self]:
    """`value` as this entity, or the reasons it is not one.

    `context` is the scope this reading is happening in; most entities
    have no use for it and ignore it.
    """
    name, configuration, must_understand = named_configuration(value)
    if name is None or not cls.accepts(name):
        return None, problem((), f"expected the {cls.identifier!r} entity")
    if configuration is None:
        if cls.configuration_required:
            return None, problem(
                ("configuration",),
                f"{cls.identifier!r} requires a configuration",
                "missing_key",
            )
        configuration = cast("Mapping[str, object]", {})
    members, found, unreadable = coerce_members(configuration, cls.member_types)
    if len(unreadable) != 0:
        # The entity cannot be built, but the members that *did* read
        # can still be judged -- one bad member should not hide the
        # value problems of the ones beside it. Anything the partial
        # reading says about an unreadable member is its default
        # talking, so those are dropped.
        partial = cls(must_understand=must_understand, **members)  # type: ignore[arg-type]
        found = (
            *found,
            # `within`, because a partial reading reports relative to
            # the configuration and `coerce`'s caller does not insert
            # that segment -- `coerce_members` problems already carry it.
            *within(
                (),
                [
                    entry
                    for entry in partial.problems()
                    if entry.loc[:1] not in {(key,) for key in unreadable}
                ],
            ),
        )
        return None, found
    return cls(must_understand=must_understand, **members), found  # type: ignore[arg-type]

configuration

configuration() -> dict[str, object]

This entity's configuration, as the document would write it.

Faithful to every member the entity holds: to_json is serialization, not canonicalization, so nothing is simplified here. Override only to render a member that is not already JSON, such as a contained entity.

Absent optional members are left out, which is what makes the bare-name spelling reachable. Absence is UNSET, never None: this package holds None to mean a JSON null the document actually wrote, and scale_offset is a real case where null and absent are different documents.

Source code in src/zarr_metadata/v3/_entity.py
def configuration(self) -> dict[str, object]:
    """This entity's configuration, as the document would write it.

    Faithful to every member the entity holds: `to_json` is
    serialization, not canonicalization, so nothing is simplified
    here. Override only to render a member that is not already JSON,
    such as a contained entity.

    Absent optional members are left out, which is what makes the
    bare-name spelling reachable. Absence is `UNSET`, never `None`:
    this package holds `None` to mean a JSON `null` the document
    actually wrote, and `scale_offset` is a real case where `null`
    and absent are different documents.
    """
    return {
        key: value
        for key in type(self).member_types
        if (value := getattr(self, key)) is not UNSET
    }

from_configuration classmethod

from_configuration(
    **configuration: Unpack[BloscCodecConfiguration],
) -> Self

This codec from its configuration members.

The configuration TypedDict unpacked is this constructor's signature, so a caller with a well-typed configuration builds a well-typed codec, and a type checker says so at the call site.

Source code in src/zarr_metadata/v3/codec/blosc.py
@classmethod
def from_configuration(cls, **configuration: Unpack[BloscCodecConfiguration]) -> Self:
    """This codec from its configuration members.

    The configuration TypedDict unpacked *is* this constructor's
    signature, so a caller with a well-typed configuration builds a
    well-typed codec, and a type checker says so at the call site.
    """
    return cls(**configuration)

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

Why this codec cannot be applied to the array that reaches it.

incoming is None once the chain can no longer say what reaches here, and the default answer to that is nothing: declining beats guessing. Locations are relative to this codec's configuration, as problems' are; an empty one lands on the codec itself.

Source code in src/zarr_metadata/v3/_entity.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """Why this codec cannot be applied to the array that reaches it.

    `incoming` is None once the chain can no longer say what reaches
    here, and the default answer to that is nothing: declining beats
    guessing. Locations are relative to this codec's `configuration`,
    as `problems`' are; an empty one lands on the codec itself.
    """
    return ()

problems

problems() -> tuple[ValidationProblem, ...]

The value constraints the spec places on a blosc configuration.

Source code in src/zarr_metadata/v3/codec/blosc.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """The value constraints the spec places on a blosc configuration."""
    found: list[ValidationProblem] = []
    if not 0 <= self.clevel <= 9:
        found.extend(
            problem(
                ("clevel",),
                f"expected an integer in [0, 9], got {self.clevel}",
                "invalid_value",
            )
        )
    if self.blocksize < 0:
        found.extend(
            problem(
                ("blocksize",),
                f"expected a non-negative integer, got {self.blocksize}",
                "invalid_value",
            )
        )
    # Only where it means something: under `noshuffle` the spec says
    # "the value is ignored" and `configuration` drops it, so judging
    # it would let `to_json` turn an invalid codec into a valid
    # document.
    if self.typesize is not UNSET and self.shuffle != BLOSC_NO_SHUFFLE and self.typesize < 1:
        found.extend(
            problem(
                ("typesize",),
                f"expected a positive integer, got {self.typesize}",
                "invalid_value",
            )
        )
    if self.shuffle != BLOSC_NO_SHUFFLE and self.typesize is UNSET:
        found.extend(
            problem(
                ("typesize",),
                f"typesize is required when shuffle is {self.shuffle!r}",
                "missing_key",
            )
        )
    return tuple(found)

to_json

to_json() -> BloscCodecObject

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/blosc.py
def to_json(self) -> BloscCodecObject:
    return cast("BloscCodecObject", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

What the next codec in the chain sees, or None if undeterminable.

Only an array-to-array codec has anything to say: the two later kinds end shape propagation by construction, one by consuming the array and the other by never having had it.

The default is None, so a modelled codec that forgets to say how it transforms the array stops propagation rather than silently claiming to leave it alone. Failing closed here costs a judgment; failing open would invent one.

Source code in src/zarr_metadata/v3/_entity.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """What the next codec in the chain sees, or None if undeterminable.

    Only an array-to-array codec has anything to say: the two later
    kinds end shape propagation by construction, one by consuming the
    array and the other by never having had it.

    The default is None, so a modelled codec that forgets to say how
    it transforms the array stops propagation rather than silently
    claiming to leave it alone. Failing closed here costs a judgment;
    failing open would invent one.
    """
    return None

BloscCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 blosc codec.

Source code in src/zarr_metadata/v3/codec/blosc.py
class BloscCodecConfiguration(TypedDict, closed=True):
    """Configuration for the Zarr v3 `blosc` codec."""

    cname: BloscCName
    clevel: int
    shuffle: BloscShuffle
    blocksize: int
    typesize: NotRequired[int]

blocksize instance-attribute

blocksize: int

clevel instance-attribute

clevel: int

cname instance-attribute

cname: BloscCName

shuffle instance-attribute

shuffle: BloscShuffle

typesize instance-attribute

typesize: NotRequired[int]

BloscCodecObject

Bases: TypedDict

blosc codec metadata in object form.

Source code in src/zarr_metadata/v3/codec/blosc.py
class BloscCodecObject(TypedDict, closed=True):
    """`blosc` codec metadata in object form."""

    name: BloscCodecName
    configuration: BloscCodecConfiguration
    must_understand: NotRequired[bool]

configuration instance-attribute

configuration: BloscCodecConfiguration

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

canonical_configuration

canonical_configuration(
    configuration: Mapping[str, object],
) -> Mapping[str, object]

A blosc configuration in its simplest equivalent form.

Under shuffle: "noshuffle" the spec says of typesize that "the value is ignored", so whatever it holds carries no meaning and two documents differing only there describe the same codec. Dropping it makes that equality visible.

Assumes a configuration the shape validator has already accepted.

Source code in src/zarr_metadata/v3/codec/blosc.py
def canonical_configuration(configuration: Mapping[str, object]) -> Mapping[str, object]:
    """A blosc configuration in its simplest equivalent form.

    Under `shuffle: "noshuffle"` the spec says of `typesize` that "the
    value is ignored", so whatever it holds carries no meaning and two
    documents differing only there describe the same codec. Dropping it
    makes that equality visible.

    Assumes a configuration the shape validator has already accepted.
    """
    if configuration.get("shuffle") != BLOSC_NO_SHUFFLE or "typesize" not in configuration:
        return configuration
    return {key: value for key, value in configuration.items() if key != "typesize"}

zarr_metadata.v3.codec.bytes

Bytes codec types.

See https://zarr-specs.readthedocs.io/en/latest/v3/codecs/bytes/index.html

BYTES_CODEC_NAME module-attribute

BYTES_CODEC_NAME: Final = 'bytes'

The name field value of the bytes codec.

BytesCodecMetadata module-attribute

BytesCodecMetadata = BytesCodecObject | BytesCodecName

Permitted JSON shapes for bytes codec metadata.

The configuration has no required keys (endian is conditionally required at runtime based on data type), so the spec's short-hand-name form is permitted in addition to the object form, and the object form may itself omit configuration entirely. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/bytes/index.rst#L64-L69 ("endian: Required for data types for which endianness is applicable") https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1562-L1564

BytesCodecName module-attribute

BytesCodecName = Literal['bytes']

Literal type of the name field of the bytes codec.

ENDIANNESS module-attribute

ENDIANNESS: Final = ('little', 'big')

Tuple of permitted values for the endian field of the bytes codec.

Endianness module-attribute

Endianness = Literal['little', 'big']

Literal type of byte order of multi-byte numeric data.

__all__ module-attribute

__all__ = [
    "BYTES_CODEC_NAME",
    "ENDIANNESS",
    "BytesCodec",
    "BytesCodecConfiguration",
    "BytesCodecMetadata",
    "BytesCodecName",
    "BytesCodecObject",
    "Endianness",
]

BytesCodec dataclass

Bases: CodecEntity

The bytes codec, coerced from its metadata.

endian is optional and absent means something: a one-byte data type has no byte order to state, and the spec lets such an array omit it.

Source code in src/zarr_metadata/v3/codec/bytes.py
@dataclass(frozen=True)
class BytesCodec(CodecEntity):
    """The `bytes` codec, coerced from its metadata.

    `endian` is optional and absent means something: a one-byte data type
    has no byte order to state, and the spec lets such an array omit it.
    """

    endian: Endianness | UNSET = UNSET

    identifier: ClassVar[str] = BYTES_CODEC_NAME
    kind: ClassVar[CodecKind] = "array_bytes"

    member_types: ClassVar[MemberTypes] = {"endian": (False, one_of(ENDIANNESS))}

    def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
        """The data type reaching here must have a raw byte representation.

        A variable-length type has no fixed one, so this codec cannot
        encode it. A multi-byte one has several orderings, so `endian` is
        required -- and the message names the type, because inside a
        shard's `index_codecs` the array is the shard index, whose
        `uint64` type appears nowhere in the document.
        """
        data_type = incoming.data_type if incoming is not None else None
        if not isinstance(data_type, DataTypeEntity):
            return ()
        storage = data_type.storage_class()
        name = data_type.name
        if storage == "variable_length":
            return problem(
                (),
                f"bytes codec is not compatible with variable-length data_type {name!r}",
                "invalid_value",
            )
        if storage == "multi_byte" and self.endian is UNSET:
            return problem(
                ("endian",),
                f"endian is required for data type {name!r}, which contains multi-byte values",
                "missing_key",
            )
        return ()

    def to_json(self) -> BytesCodecObject | BytesCodecName:
        return cast("BytesCodecObject | BytesCodecName", super().to_json())

configuration_required class-attribute

configuration_required: bool = False

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

endian class-attribute instance-attribute

endian: Endianness | UNSET = UNSET

identifier class-attribute

identifier: str = BYTES_CODEC_NAME

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'array_bytes'

member_types class-attribute

member_types: MemberTypes = {
    "endian": (False, one_of(ENDIANNESS))
}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

variable_size class-attribute

variable_size: bool = False

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    endian: Endianness | UNSET = UNSET,
    *,
    must_understand: bool = True,
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

This entity in the simplest form that means the same thing.

A transformation, asked for by canonicalize_array_metadata_v3 and by nothing else. to_json does not apply it, because writing a document back is not the same as asking for it to be rewritten: a reader that reads and writes should not change bytes it was not asked to change.

Default: entities are already canonical. Override where two spellings of a member mean the same -- a rectilinear dimension's run-length encoding, a typesize that noshuffle ignores -- and where a contained entity has its own canonical form.

Source code in src/zarr_metadata/v3/_entity.py
def canonical(self) -> Self:
    """This entity in the simplest form that means the same thing.

    A *transformation*, asked for by `canonicalize_array_metadata_v3`
    and by nothing else. `to_json` does not apply it, because writing
    a document back is not the same as asking for it to be rewritten:
    a reader that reads and writes should not change bytes it was not
    asked to change.

    Default: entities are already canonical. Override where two
    spellings of a member mean the same -- a rectilinear dimension's
    run-length encoding, a `typesize` that `noshuffle` ignores -- and
    where a contained entity has its own canonical form.
    """
    return self

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def coerce(cls, value: object, context: Context) -> Coerced[Self]:
    """`value` as this entity, or the reasons it is not one.

    `context` is the scope this reading is happening in; most entities
    have no use for it and ignore it.
    """
    name, configuration, must_understand = named_configuration(value)
    if name is None or not cls.accepts(name):
        return None, problem((), f"expected the {cls.identifier!r} entity")
    if configuration is None:
        if cls.configuration_required:
            return None, problem(
                ("configuration",),
                f"{cls.identifier!r} requires a configuration",
                "missing_key",
            )
        configuration = cast("Mapping[str, object]", {})
    members, found, unreadable = coerce_members(configuration, cls.member_types)
    if len(unreadable) != 0:
        # The entity cannot be built, but the members that *did* read
        # can still be judged -- one bad member should not hide the
        # value problems of the ones beside it. Anything the partial
        # reading says about an unreadable member is its default
        # talking, so those are dropped.
        partial = cls(must_understand=must_understand, **members)  # type: ignore[arg-type]
        found = (
            *found,
            # `within`, because a partial reading reports relative to
            # the configuration and `coerce`'s caller does not insert
            # that segment -- `coerce_members` problems already carry it.
            *within(
                (),
                [
                    entry
                    for entry in partial.problems()
                    if entry.loc[:1] not in {(key,) for key in unreadable}
                ],
            ),
        )
        return None, found
    return cls(must_understand=must_understand, **members), found  # type: ignore[arg-type]

configuration

configuration() -> dict[str, object]

This entity's configuration, as the document would write it.

Faithful to every member the entity holds: to_json is serialization, not canonicalization, so nothing is simplified here. Override only to render a member that is not already JSON, such as a contained entity.

Absent optional members are left out, which is what makes the bare-name spelling reachable. Absence is UNSET, never None: this package holds None to mean a JSON null the document actually wrote, and scale_offset is a real case where null and absent are different documents.

Source code in src/zarr_metadata/v3/_entity.py
def configuration(self) -> dict[str, object]:
    """This entity's configuration, as the document would write it.

    Faithful to every member the entity holds: `to_json` is
    serialization, not canonicalization, so nothing is simplified
    here. Override only to render a member that is not already JSON,
    such as a contained entity.

    Absent optional members are left out, which is what makes the
    bare-name spelling reachable. Absence is `UNSET`, never `None`:
    this package holds `None` to mean a JSON `null` the document
    actually wrote, and `scale_offset` is a real case where `null`
    and absent are different documents.
    """
    return {
        key: value
        for key in type(self).member_types
        if (value := getattr(self, key)) is not UNSET
    }

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

The data type reaching here must have a raw byte representation.

A variable-length type has no fixed one, so this codec cannot encode it. A multi-byte one has several orderings, so endian is required -- and the message names the type, because inside a shard's index_codecs the array is the shard index, whose uint64 type appears nowhere in the document.

Source code in src/zarr_metadata/v3/codec/bytes.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """The data type reaching here must have a raw byte representation.

    A variable-length type has no fixed one, so this codec cannot
    encode it. A multi-byte one has several orderings, so `endian` is
    required -- and the message names the type, because inside a
    shard's `index_codecs` the array is the shard index, whose
    `uint64` type appears nowhere in the document.
    """
    data_type = incoming.data_type if incoming is not None else None
    if not isinstance(data_type, DataTypeEntity):
        return ()
    storage = data_type.storage_class()
    name = data_type.name
    if storage == "variable_length":
        return problem(
            (),
            f"bytes codec is not compatible with variable-length data_type {name!r}",
            "invalid_value",
        )
    if storage == "multi_byte" and self.endian is UNSET:
        return problem(
            ("endian",),
            f"endian is required for data type {name!r}, which contains multi-byte values",
            "missing_key",
        )
    return ()

problems

problems() -> tuple[ValidationProblem, ...]

Every value of this entity the spec disallows.

Locations are relative to the entity's configuration. Default: an entity whose type admits only valid values has nothing to add.

Source code in src/zarr_metadata/v3/_entity.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """Every value of this entity the spec disallows.

    Locations are relative to the entity's `configuration`. Default:
    an entity whose type admits only valid values has nothing to add.
    """
    return ()

to_json

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/bytes.py
def to_json(self) -> BytesCodecObject | BytesCodecName:
    return cast("BytesCodecObject | BytesCodecName", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

What the next codec in the chain sees, or None if undeterminable.

Only an array-to-array codec has anything to say: the two later kinds end shape propagation by construction, one by consuming the array and the other by never having had it.

The default is None, so a modelled codec that forgets to say how it transforms the array stops propagation rather than silently claiming to leave it alone. Failing closed here costs a judgment; failing open would invent one.

Source code in src/zarr_metadata/v3/_entity.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """What the next codec in the chain sees, or None if undeterminable.

    Only an array-to-array codec has anything to say: the two later
    kinds end shape propagation by construction, one by consuming the
    array and the other by never having had it.

    The default is None, so a modelled codec that forgets to say how
    it transforms the array stops propagation rather than silently
    claiming to leave it alone. Failing closed here costs a judgment;
    failing open would invent one.
    """
    return None

BytesCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 bytes codec.

The endian field is required for multi-byte data types.

Source code in src/zarr_metadata/v3/codec/bytes.py
class BytesCodecConfiguration(TypedDict, closed=True):
    """
    Configuration for the Zarr v3 `bytes` codec.

    The `endian` field is required for multi-byte data types.
    """

    endian: NotRequired[Endianness]

endian instance-attribute

BytesCodecObject

Bases: TypedDict

bytes codec metadata in object form.

configuration is itself optional — when no configuration fields are set, the entire configuration key may be omitted. This matches the bare-string short-hand form (BytesCodecName) at the canonical data level; both encodings describe a bytes codec with default settings.

Source code in src/zarr_metadata/v3/codec/bytes.py
class BytesCodecObject(TypedDict, closed=True):
    """`bytes` codec metadata in object form.

    `configuration` is itself optional — when no configuration fields are
    set, the entire `configuration` key may be omitted. This matches the
    bare-string short-hand form (`BytesCodecName`) at the canonical data
    level; both encodings describe a `bytes` codec with default settings.
    """

    name: BytesCodecName
    configuration: NotRequired[BytesCodecConfiguration]
    must_understand: NotRequired[bool]

configuration instance-attribute

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

zarr_metadata.v3.codec.cast_value

Cast-value codec types.

See https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/cast_value/README.md

CAST_OUT_OF_RANGE_MODE module-attribute

CAST_OUT_OF_RANGE_MODE: Final = ('clamp', 'wrap')

Tuple of permitted values for the out_of_range field of the cast_value codec.

CAST_ROUNDING_MODE module-attribute

CAST_ROUNDING_MODE: Final = (
    "nearest-even",
    "towards-zero",
    "towards-positive",
    "towards-negative",
    "nearest-away",
)

Tuple of permitted values for the rounding field of the cast_value codec.

CAST_VALUE_CODEC_NAME module-attribute

CAST_VALUE_CODEC_NAME: Final = 'cast_value'

The name field value of the cast_value codec.

CastOutOfRangeMode module-attribute

CastOutOfRangeMode = Literal['clamp', 'wrap']

Literal type of permitted values for the out_of_range configuration field.

If absent, out-of-range values are an encoding/decoding error.

CastRoundingMode module-attribute

CastRoundingMode = Literal[
    "nearest-even",
    "towards-zero",
    "towards-positive",
    "towards-negative",
    "nearest-away",
]

Literal type of permitted values for the rounding configuration field.

Defaults to "nearest-even" if absent.

CastValueCodecMetadata module-attribute

CastValueCodecMetadata = CastValueCodecObject

Permitted JSON shape for cast_value codec metadata.

configuration.data_type is required, so only the object form is valid; the short-hand-name form is not permitted by the spec for this codec. https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/cast_value/README.md#L33-L36 and #L46-L48 (required fields) https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1562-L1564 (short-hand names only "if no configuration metadata is required")

CastValueCodecName module-attribute

CastValueCodecName = Literal['cast_value']

Literal type of the name field of the cast_value codec.

SCALAR_MAP_KEYS module-attribute

SCALAR_MAP_KEYS: Final = ('encode', 'decode')

The two directions a scalar_map can override, both optional.

ScalarMapEntry module-attribute

ScalarMapEntry = tuple[JSONValue, JSONValue]

A single [input, output] mapping in a scalar_map direction.

Each scalar is JSON-encoded per its data type's fill-value rules (so e.g. "NaN" and "+Infinity" are permitted).

__all__ module-attribute

__all__ = [
    "CAST_OUT_OF_RANGE_MODE",
    "CAST_ROUNDING_MODE",
    "CAST_VALUE_CODEC_NAME",
    "SCALAR_MAP_KEYS",
    "CastOutOfRangeMode",
    "CastRoundingMode",
    "CastValueCodec",
    "CastValueCodecConfiguration",
    "CastValueCodecMetadata",
    "CastValueCodecName",
    "CastValueCodecObject",
    "ScalarMap",
    "ScalarMapEntry",
]

CastValueCodec dataclass

Bases: CodecEntity

The cast_value codec, coerced from its metadata.

Holds the data type it casts to, so like sharding_indexed it is read in a scope rather than on its own.

out_of_range: "wrap" is defined only for integral targets with a two's complement representation, which is a fact each data type states about itself.

Source code in src/zarr_metadata/v3/codec/cast_value.py
@dataclass(frozen=True)
class CastValueCodec(CodecEntity):
    """The `cast_value` codec, coerced from its metadata.

    Holds the data type it casts to, so like `sharding_indexed` it is
    read in a scope rather than on its own.

    `out_of_range: "wrap"` is defined only for integral targets with a
    two's complement representation, which is a fact each data type
    states about itself.
    """

    data_type: DataTypeEntity | Opaque = _UNREAD
    rounding: CastRoundingMode | UNSET = UNSET
    out_of_range: CastOutOfRangeMode | UNSET = UNSET
    scalar_map: ScalarMap | UNSET = UNSET

    identifier: ClassVar[str] = CAST_VALUE_CODEC_NAME
    kind: ClassVar[CodecKind] = "array_array"

    configuration_required: ClassVar[bool] = True
    member_types: ClassVar[MemberTypes] = {
        "data_type": (True, _is_data_type_field),
        "rounding": (False, one_of(CAST_ROUNDING_MODE)),
        "out_of_range": (False, one_of(CAST_OUT_OF_RANGE_MODE)),
        "scalar_map": (False, _is_scalar_map),
    }

    @classmethod
    def coerce(cls, value: object, context: "Context") -> Coerced[Self]:
        codec, problems = super().coerce(value, context)
        if codec is None:
            return None, problems
        data_type, found = context.coerce(
            DATA_TYPE, codec.data_type, ("configuration", "data_type")
        )
        return replace(codec, data_type=data_type), (*problems, *found)

    def problems(self) -> tuple[ValidationProblem, ...]:
        """The target's own problems, and whether it can be wrapped."""
        if not isinstance(self.data_type, DataTypeEntity):
            # Out of scope, so it may well be an integral extension type;
            # judging the wrap here would be guessing.
            return ()
        found = list(within(("data_type",), self.data_type.problems()))
        if self.out_of_range == "wrap" and not type(self.data_type).twos_complement:
            found.extend(
                problem(
                    ("out_of_range",),
                    "out_of_range 'wrap' requires a two's complement integer data_type, "
                    f"got {self.data_type.name!r}",
                    "invalid_value",
                )
            )
        return tuple(found)

    def canonical(self) -> Self:
        """The target data type in its own canonical form."""
        if not isinstance(self.data_type, DataTypeEntity):
            return self
        return replace(self, data_type=self.data_type.canonical())

    def configuration(self) -> dict[str, object]:
        """The target data type in its canonical spelling."""
        members = super().configuration()
        data_type = self.data_type
        if isinstance(data_type, DataTypeEntity):
            members["data_type"] = data_type.to_json()
        else:
            members["data_type"] = data_type.json
        return members

    def transition(self, incoming: ArrayParts) -> ArrayParts | None:
        """The same parts, holding the type this codec casts to."""
        data_type = self.data_type
        return incoming.with_data_type(data_type if isinstance(data_type, DataTypeEntity) else None)

    def to_json(self) -> CastValueCodecObject:
        return cast("CastValueCodecObject", super().to_json())

configuration_required class-attribute

configuration_required: bool = True

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

data_type class-attribute instance-attribute

data_type: DataTypeEntity | Opaque = _UNREAD

identifier class-attribute

identifier: str = CAST_VALUE_CODEC_NAME

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'array_array'

member_types class-attribute

member_types: MemberTypes = {
    "data_type": (True, _is_data_type_field),
    "rounding": (False, one_of(CAST_ROUNDING_MODE)),
    "out_of_range": (False, one_of(CAST_OUT_OF_RANGE_MODE)),
    "scalar_map": (False, _is_scalar_map),
}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

out_of_range class-attribute instance-attribute

out_of_range: CastOutOfRangeMode | UNSET = UNSET

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

rounding class-attribute instance-attribute

scalar_map class-attribute instance-attribute

scalar_map: ScalarMap | UNSET = UNSET

variable_size class-attribute

variable_size: bool = False

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    data_type: DataTypeEntity | Opaque = _UNREAD,
    rounding: CastRoundingMode | UNSET = UNSET,
    out_of_range: CastOutOfRangeMode | UNSET = UNSET,
    scalar_map: ScalarMap | UNSET = UNSET,
    *,
    must_understand: bool = True,
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

The target data type in its own canonical form.

Source code in src/zarr_metadata/v3/codec/cast_value.py
def canonical(self) -> Self:
    """The target data type in its own canonical form."""
    if not isinstance(self.data_type, DataTypeEntity):
        return self
    return replace(self, data_type=self.data_type.canonical())

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/codec/cast_value.py
@classmethod
def coerce(cls, value: object, context: "Context") -> Coerced[Self]:
    codec, problems = super().coerce(value, context)
    if codec is None:
        return None, problems
    data_type, found = context.coerce(
        DATA_TYPE, codec.data_type, ("configuration", "data_type")
    )
    return replace(codec, data_type=data_type), (*problems, *found)

configuration

configuration() -> dict[str, object]

The target data type in its canonical spelling.

Source code in src/zarr_metadata/v3/codec/cast_value.py
def configuration(self) -> dict[str, object]:
    """The target data type in its canonical spelling."""
    members = super().configuration()
    data_type = self.data_type
    if isinstance(data_type, DataTypeEntity):
        members["data_type"] = data_type.to_json()
    else:
        members["data_type"] = data_type.json
    return members

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

Why this codec cannot be applied to the array that reaches it.

incoming is None once the chain can no longer say what reaches here, and the default answer to that is nothing: declining beats guessing. Locations are relative to this codec's configuration, as problems' are; an empty one lands on the codec itself.

Source code in src/zarr_metadata/v3/_entity.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """Why this codec cannot be applied to the array that reaches it.

    `incoming` is None once the chain can no longer say what reaches
    here, and the default answer to that is nothing: declining beats
    guessing. Locations are relative to this codec's `configuration`,
    as `problems`' are; an empty one lands on the codec itself.
    """
    return ()

problems

problems() -> tuple[ValidationProblem, ...]

The target's own problems, and whether it can be wrapped.

Source code in src/zarr_metadata/v3/codec/cast_value.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """The target's own problems, and whether it can be wrapped."""
    if not isinstance(self.data_type, DataTypeEntity):
        # Out of scope, so it may well be an integral extension type;
        # judging the wrap here would be guessing.
        return ()
    found = list(within(("data_type",), self.data_type.problems()))
    if self.out_of_range == "wrap" and not type(self.data_type).twos_complement:
        found.extend(
            problem(
                ("out_of_range",),
                "out_of_range 'wrap' requires a two's complement integer data_type, "
                f"got {self.data_type.name!r}",
                "invalid_value",
            )
        )
    return tuple(found)

to_json

to_json() -> CastValueCodecObject

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/cast_value.py
def to_json(self) -> CastValueCodecObject:
    return cast("CastValueCodecObject", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

The same parts, holding the type this codec casts to.

Source code in src/zarr_metadata/v3/codec/cast_value.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """The same parts, holding the type this codec casts to."""
    data_type = self.data_type
    return incoming.with_data_type(data_type if isinstance(data_type, DataTypeEntity) else None)

CastValueCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 cast_value codec.

data_type is the target data type that input values are cast to. It is the same shape as the top-level array data_type field: either a bare-string primitive name or a {name, configuration} envelope.

Source code in src/zarr_metadata/v3/codec/cast_value.py
class CastValueCodecConfiguration(TypedDict, closed=True):
    """
    Configuration for the Zarr v3 `cast_value` codec.

    `data_type` is the target data type that input values are cast to. It
    is the same shape as the top-level array `data_type` field: either a
    bare-string primitive name or a `{name, configuration}` envelope.
    """

    data_type: ZarrV3MetadataFieldJSON
    rounding: NotRequired[CastRoundingMode]
    out_of_range: NotRequired[CastOutOfRangeMode]
    scalar_map: NotRequired[ScalarMap]

data_type instance-attribute

out_of_range instance-attribute

rounding instance-attribute

scalar_map instance-attribute

scalar_map: NotRequired[ScalarMap]

CastValueCodecObject

Bases: TypedDict

cast_value codec metadata in object form.

Source code in src/zarr_metadata/v3/codec/cast_value.py
class CastValueCodecObject(TypedDict, closed=True):
    """`cast_value` codec metadata in object form."""

    name: CastValueCodecName
    configuration: CastValueCodecConfiguration
    must_understand: NotRequired[bool]

configuration instance-attribute

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

ScalarMap

Bases: TypedDict

Optional encode/decode scalar overrides for the cast_value codec.

Source code in src/zarr_metadata/v3/codec/cast_value.py
class ScalarMap(TypedDict, closed=True):
    """Optional encode/decode scalar overrides for the cast_value codec."""

    encode: NotRequired[tuple[ScalarMapEntry, ...]]
    decode: NotRequired[tuple[ScalarMapEntry, ...]]

decode instance-attribute

encode instance-attribute

zarr_metadata.v3.codec.crc32c

CRC32C codec types.

See https://zarr-specs.readthedocs.io/en/latest/v3/codecs/crc32c/index.html

The CRC32C codec has no configuration fields, so the configuration key is absent from the metadata.

CRC32C_CODEC_NAME module-attribute

CRC32C_CODEC_NAME: Final = 'crc32c'

The name field value of the crc32c codec.

Crc32cCodecMetadata module-attribute

Crc32cCodecMetadata = Crc32cCodecObject | Crc32cCodecName

Permitted JSON shapes for crc32c codec metadata.

The spec's Extension definition allows extensions with no required configuration to be encoded as a bare short-hand name. CRC32C has no configuration, so both forms are valid. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1562-L1564

Crc32cCodecName module-attribute

Crc32cCodecName = Literal['crc32c']

Literal type of the name field of the crc32c codec.

__all__ module-attribute

__all__ = [
    "CRC32C_CODEC_NAME",
    "Crc32cCodec",
    "Crc32cCodecMetadata",
    "Crc32cCodecName",
    "Crc32cCodecObject",
]

Crc32cCodec dataclass

Bases: CodecEntity

The crc32c codec, coerced from its metadata.

The name says everything: a checksum has nothing to configure.

Source code in src/zarr_metadata/v3/codec/crc32c.py
@dataclass(frozen=True)
class Crc32cCodec(CodecEntity):
    """The `crc32c` codec, coerced from its metadata.

    The name says everything: a checksum has nothing to configure.
    """

    identifier: ClassVar[str] = CRC32C_CODEC_NAME
    kind: ClassVar[CodecKind] = "bytes_bytes"

    def to_json(self) -> Crc32cCodecObject | Crc32cCodecName:
        return cast("Crc32cCodecObject | Crc32cCodecName", super().to_json())

configuration_required class-attribute

configuration_required: bool = False

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

identifier class-attribute

identifier: str = CRC32C_CODEC_NAME

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'bytes_bytes'

member_types class-attribute

member_types: MemberTypes = MappingProxyType({})

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

variable_size class-attribute

variable_size: bool = False

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(*, must_understand: bool = True) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

This entity in the simplest form that means the same thing.

A transformation, asked for by canonicalize_array_metadata_v3 and by nothing else. to_json does not apply it, because writing a document back is not the same as asking for it to be rewritten: a reader that reads and writes should not change bytes it was not asked to change.

Default: entities are already canonical. Override where two spellings of a member mean the same -- a rectilinear dimension's run-length encoding, a typesize that noshuffle ignores -- and where a contained entity has its own canonical form.

Source code in src/zarr_metadata/v3/_entity.py
def canonical(self) -> Self:
    """This entity in the simplest form that means the same thing.

    A *transformation*, asked for by `canonicalize_array_metadata_v3`
    and by nothing else. `to_json` does not apply it, because writing
    a document back is not the same as asking for it to be rewritten:
    a reader that reads and writes should not change bytes it was not
    asked to change.

    Default: entities are already canonical. Override where two
    spellings of a member mean the same -- a rectilinear dimension's
    run-length encoding, a `typesize` that `noshuffle` ignores -- and
    where a contained entity has its own canonical form.
    """
    return self

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def coerce(cls, value: object, context: Context) -> Coerced[Self]:
    """`value` as this entity, or the reasons it is not one.

    `context` is the scope this reading is happening in; most entities
    have no use for it and ignore it.
    """
    name, configuration, must_understand = named_configuration(value)
    if name is None or not cls.accepts(name):
        return None, problem((), f"expected the {cls.identifier!r} entity")
    if configuration is None:
        if cls.configuration_required:
            return None, problem(
                ("configuration",),
                f"{cls.identifier!r} requires a configuration",
                "missing_key",
            )
        configuration = cast("Mapping[str, object]", {})
    members, found, unreadable = coerce_members(configuration, cls.member_types)
    if len(unreadable) != 0:
        # The entity cannot be built, but the members that *did* read
        # can still be judged -- one bad member should not hide the
        # value problems of the ones beside it. Anything the partial
        # reading says about an unreadable member is its default
        # talking, so those are dropped.
        partial = cls(must_understand=must_understand, **members)  # type: ignore[arg-type]
        found = (
            *found,
            # `within`, because a partial reading reports relative to
            # the configuration and `coerce`'s caller does not insert
            # that segment -- `coerce_members` problems already carry it.
            *within(
                (),
                [
                    entry
                    for entry in partial.problems()
                    if entry.loc[:1] not in {(key,) for key in unreadable}
                ],
            ),
        )
        return None, found
    return cls(must_understand=must_understand, **members), found  # type: ignore[arg-type]

configuration

configuration() -> dict[str, object]

This entity's configuration, as the document would write it.

Faithful to every member the entity holds: to_json is serialization, not canonicalization, so nothing is simplified here. Override only to render a member that is not already JSON, such as a contained entity.

Absent optional members are left out, which is what makes the bare-name spelling reachable. Absence is UNSET, never None: this package holds None to mean a JSON null the document actually wrote, and scale_offset is a real case where null and absent are different documents.

Source code in src/zarr_metadata/v3/_entity.py
def configuration(self) -> dict[str, object]:
    """This entity's configuration, as the document would write it.

    Faithful to every member the entity holds: `to_json` is
    serialization, not canonicalization, so nothing is simplified
    here. Override only to render a member that is not already JSON,
    such as a contained entity.

    Absent optional members are left out, which is what makes the
    bare-name spelling reachable. Absence is `UNSET`, never `None`:
    this package holds `None` to mean a JSON `null` the document
    actually wrote, and `scale_offset` is a real case where `null`
    and absent are different documents.
    """
    return {
        key: value
        for key in type(self).member_types
        if (value := getattr(self, key)) is not UNSET
    }

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

Why this codec cannot be applied to the array that reaches it.

incoming is None once the chain can no longer say what reaches here, and the default answer to that is nothing: declining beats guessing. Locations are relative to this codec's configuration, as problems' are; an empty one lands on the codec itself.

Source code in src/zarr_metadata/v3/_entity.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """Why this codec cannot be applied to the array that reaches it.

    `incoming` is None once the chain can no longer say what reaches
    here, and the default answer to that is nothing: declining beats
    guessing. Locations are relative to this codec's `configuration`,
    as `problems`' are; an empty one lands on the codec itself.
    """
    return ()

problems

problems() -> tuple[ValidationProblem, ...]

Every value of this entity the spec disallows.

Locations are relative to the entity's configuration. Default: an entity whose type admits only valid values has nothing to add.

Source code in src/zarr_metadata/v3/_entity.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """Every value of this entity the spec disallows.

    Locations are relative to the entity's `configuration`. Default:
    an entity whose type admits only valid values has nothing to add.
    """
    return ()

to_json

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/crc32c.py
def to_json(self) -> Crc32cCodecObject | Crc32cCodecName:
    return cast("Crc32cCodecObject | Crc32cCodecName", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

What the next codec in the chain sees, or None if undeterminable.

Only an array-to-array codec has anything to say: the two later kinds end shape propagation by construction, one by consuming the array and the other by never having had it.

The default is None, so a modelled codec that forgets to say how it transforms the array stops propagation rather than silently claiming to leave it alone. Failing closed here costs a judgment; failing open would invent one.

Source code in src/zarr_metadata/v3/_entity.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """What the next codec in the chain sees, or None if undeterminable.

    Only an array-to-array codec has anything to say: the two later
    kinds end shape propagation by construction, one by consuming the
    array and the other by never having had it.

    The default is None, so a modelled codec that forgets to say how
    it transforms the array stops propagation rather than silently
    claiming to leave it alone. Failing closed here costs a judgment;
    failing open would invent one.
    """
    return None

Crc32cCodecObject

Bases: TypedDict

crc32c codec metadata in object form.

Per spec the codec has no configuration fields. configuration is optional and, if present, should be an empty mapping. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/crc32c/index.rst#L63-L66

Source code in src/zarr_metadata/v3/codec/crc32c.py
class Crc32cCodecObject(TypedDict, closed=True):
    """`crc32c` codec metadata in object form.

    Per spec the codec has no configuration fields. `configuration` is
    optional and, if present, should be an empty mapping.
      https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/crc32c/index.rst#L63-L66
    """

    name: Crc32cCodecName
    configuration: NotRequired[Empty]
    must_understand: NotRequired[bool]

configuration instance-attribute

configuration: NotRequired[Empty]

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

Empty

Bases: TypedDict

An empty mapping

Source code in src/zarr_metadata/v3/codec/crc32c.py
class Empty(TypedDict, closed=True):
    """An empty mapping"""

zarr_metadata.v3.codec.gzip

Gzip codec types.

See https://zarr-specs.readthedocs.io/en/latest/v3/codecs/gzip/index.html

GZIP_CODEC_NAME module-attribute

GZIP_CODEC_NAME: Final = 'gzip'

The name field value of the gzip codec.

GzipCodecMetadata module-attribute

GzipCodecMetadata = GzipCodecObject

Permitted JSON shape for gzip codec metadata.

configuration.level is required (it determines the codec's output bytes and is therefore part of the metadata's reproducibility contract), so only the object form is valid; the short-hand-name form is not permitted.

GzipCodecName module-attribute

GzipCodecName = Literal['gzip']

Literal type of the name field of the gzip codec.

__all__ module-attribute

__all__ = [
    "GZIP_CODEC_NAME",
    "GzipCodec",
    "GzipCodecConfiguration",
    "GzipCodecMetadata",
    "GzipCodecName",
    "GzipCodecObject",
]

GzipCodec dataclass

Bases: CodecEntity

The gzip codec, coerced from its metadata.

Source code in src/zarr_metadata/v3/codec/gzip.py
@dataclass(frozen=True)
class GzipCodec(CodecEntity):
    """The `gzip` codec, coerced from its metadata."""

    level: int = 5

    identifier: ClassVar[str] = GZIP_CODEC_NAME
    variable_size: ClassVar[bool] = True
    kind: ClassVar[CodecKind] = "bytes_bytes"

    configuration_required: ClassVar[bool] = True
    member_types: ClassVar[MemberTypes] = {"level": (True, is_int)}

    def problems(self) -> tuple[ValidationProblem, ...]:
        """gzip compression levels run 0 to 9."""
        if not 0 <= self.level <= 9:
            return problem(
                ("level",), f"expected an integer in [0, 9], got {self.level}", "invalid_value"
            )
        return ()

    def to_json(self) -> GzipCodecObject:
        return cast("GzipCodecObject", super().to_json())

configuration_required class-attribute

configuration_required: bool = True

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

identifier class-attribute

identifier: str = GZIP_CODEC_NAME

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'bytes_bytes'

level class-attribute instance-attribute

level: int = 5

member_types class-attribute

member_types: MemberTypes = {'level': (True, is_int)}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

variable_size class-attribute

variable_size: bool = True

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    level: int = 5, *, must_understand: bool = True
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

This entity in the simplest form that means the same thing.

A transformation, asked for by canonicalize_array_metadata_v3 and by nothing else. to_json does not apply it, because writing a document back is not the same as asking for it to be rewritten: a reader that reads and writes should not change bytes it was not asked to change.

Default: entities are already canonical. Override where two spellings of a member mean the same -- a rectilinear dimension's run-length encoding, a typesize that noshuffle ignores -- and where a contained entity has its own canonical form.

Source code in src/zarr_metadata/v3/_entity.py
def canonical(self) -> Self:
    """This entity in the simplest form that means the same thing.

    A *transformation*, asked for by `canonicalize_array_metadata_v3`
    and by nothing else. `to_json` does not apply it, because writing
    a document back is not the same as asking for it to be rewritten:
    a reader that reads and writes should not change bytes it was not
    asked to change.

    Default: entities are already canonical. Override where two
    spellings of a member mean the same -- a rectilinear dimension's
    run-length encoding, a `typesize` that `noshuffle` ignores -- and
    where a contained entity has its own canonical form.
    """
    return self

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def coerce(cls, value: object, context: Context) -> Coerced[Self]:
    """`value` as this entity, or the reasons it is not one.

    `context` is the scope this reading is happening in; most entities
    have no use for it and ignore it.
    """
    name, configuration, must_understand = named_configuration(value)
    if name is None or not cls.accepts(name):
        return None, problem((), f"expected the {cls.identifier!r} entity")
    if configuration is None:
        if cls.configuration_required:
            return None, problem(
                ("configuration",),
                f"{cls.identifier!r} requires a configuration",
                "missing_key",
            )
        configuration = cast("Mapping[str, object]", {})
    members, found, unreadable = coerce_members(configuration, cls.member_types)
    if len(unreadable) != 0:
        # The entity cannot be built, but the members that *did* read
        # can still be judged -- one bad member should not hide the
        # value problems of the ones beside it. Anything the partial
        # reading says about an unreadable member is its default
        # talking, so those are dropped.
        partial = cls(must_understand=must_understand, **members)  # type: ignore[arg-type]
        found = (
            *found,
            # `within`, because a partial reading reports relative to
            # the configuration and `coerce`'s caller does not insert
            # that segment -- `coerce_members` problems already carry it.
            *within(
                (),
                [
                    entry
                    for entry in partial.problems()
                    if entry.loc[:1] not in {(key,) for key in unreadable}
                ],
            ),
        )
        return None, found
    return cls(must_understand=must_understand, **members), found  # type: ignore[arg-type]

configuration

configuration() -> dict[str, object]

This entity's configuration, as the document would write it.

Faithful to every member the entity holds: to_json is serialization, not canonicalization, so nothing is simplified here. Override only to render a member that is not already JSON, such as a contained entity.

Absent optional members are left out, which is what makes the bare-name spelling reachable. Absence is UNSET, never None: this package holds None to mean a JSON null the document actually wrote, and scale_offset is a real case where null and absent are different documents.

Source code in src/zarr_metadata/v3/_entity.py
def configuration(self) -> dict[str, object]:
    """This entity's configuration, as the document would write it.

    Faithful to every member the entity holds: `to_json` is
    serialization, not canonicalization, so nothing is simplified
    here. Override only to render a member that is not already JSON,
    such as a contained entity.

    Absent optional members are left out, which is what makes the
    bare-name spelling reachable. Absence is `UNSET`, never `None`:
    this package holds `None` to mean a JSON `null` the document
    actually wrote, and `scale_offset` is a real case where `null`
    and absent are different documents.
    """
    return {
        key: value
        for key in type(self).member_types
        if (value := getattr(self, key)) is not UNSET
    }

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

Why this codec cannot be applied to the array that reaches it.

incoming is None once the chain can no longer say what reaches here, and the default answer to that is nothing: declining beats guessing. Locations are relative to this codec's configuration, as problems' are; an empty one lands on the codec itself.

Source code in src/zarr_metadata/v3/_entity.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """Why this codec cannot be applied to the array that reaches it.

    `incoming` is None once the chain can no longer say what reaches
    here, and the default answer to that is nothing: declining beats
    guessing. Locations are relative to this codec's `configuration`,
    as `problems`' are; an empty one lands on the codec itself.
    """
    return ()

problems

problems() -> tuple[ValidationProblem, ...]

gzip compression levels run 0 to 9.

Source code in src/zarr_metadata/v3/codec/gzip.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """gzip compression levels run 0 to 9."""
    if not 0 <= self.level <= 9:
        return problem(
            ("level",), f"expected an integer in [0, 9], got {self.level}", "invalid_value"
        )
    return ()

to_json

to_json() -> GzipCodecObject

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/gzip.py
def to_json(self) -> GzipCodecObject:
    return cast("GzipCodecObject", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

What the next codec in the chain sees, or None if undeterminable.

Only an array-to-array codec has anything to say: the two later kinds end shape propagation by construction, one by consuming the array and the other by never having had it.

The default is None, so a modelled codec that forgets to say how it transforms the array stops propagation rather than silently claiming to leave it alone. Failing closed here costs a judgment; failing open would invent one.

Source code in src/zarr_metadata/v3/_entity.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """What the next codec in the chain sees, or None if undeterminable.

    Only an array-to-array codec has anything to say: the two later
    kinds end shape propagation by construction, one by consuming the
    array and the other by never having had it.

    The default is None, so a modelled codec that forgets to say how
    it transforms the array stops propagation rather than silently
    claiming to leave it alone. Failing closed here costs a judgment;
    failing open would invent one.
    """
    return None

GzipCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 gzip codec.

level is an integer in the range 0-9; 0 disables compression and 9 is slowest with the best compression ratio. The codec's compressed output depends on level, so metadata that omits it cannot reproducibly identify the chunk bytes produced by a writer — level is required for the metadata to fulfill its reproducibility role, even though the spec text does not mark it required with RFC 2119 keywords. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/gzip/index.rst#L57-L66

Source code in src/zarr_metadata/v3/codec/gzip.py
class GzipCodecConfiguration(TypedDict, closed=True):
    """
    Configuration for the Zarr v3 `gzip` codec.

    `level` is an integer in the range 0-9; 0 disables compression and 9
    is slowest with the best compression ratio. The codec's compressed
    output depends on `level`, so metadata that omits it cannot
    reproducibly identify the chunk bytes produced by a writer — `level`
    is required for the metadata to fulfill its reproducibility role,
    even though the spec text does not mark it required with RFC 2119
    keywords.
      https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/gzip/index.rst#L57-L66
    """

    level: int

level instance-attribute

level: int

GzipCodecObject

Bases: TypedDict

gzip codec metadata in object form.

Source code in src/zarr_metadata/v3/codec/gzip.py
class GzipCodecObject(TypedDict, closed=True):
    """`gzip` codec metadata in object form."""

    name: GzipCodecName
    configuration: GzipCodecConfiguration
    must_understand: NotRequired[bool]

configuration instance-attribute

configuration: GzipCodecConfiguration

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

zarr_metadata.v3.codec.scale_offset

Scale-offset codec types.

See https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/scale_offset/README.md

SCALE_OFFSET_CODEC_NAME module-attribute

SCALE_OFFSET_CODEC_NAME: Final = 'scale_offset'

The name field value of the scale_offset codec.

ScaleOffsetCodecMetadata module-attribute

ScaleOffsetCodecMetadata = (
    ScaleOffsetCodecObject | ScaleOffsetCodecName
)

Permitted JSON shapes for scale_offset codec metadata.

The configuration has no required keys (both offset and scale are optional, and the configuration itself is optional), so the short-hand-name form is permitted in addition to the object form.

ScaleOffsetCodecName module-attribute

ScaleOffsetCodecName = Literal['scale_offset']

Literal type of the name field of the scale_offset codec.

__all__ module-attribute

__all__ = [
    "SCALE_OFFSET_CODEC_NAME",
    "ScaleOffsetCodec",
    "ScaleOffsetCodecConfiguration",
    "ScaleOffsetCodecMetadata",
    "ScaleOffsetCodecName",
    "ScaleOffsetCodecObject",
]

ScaleOffsetCodec dataclass

Bases: CodecEntity

The scale_offset codec, coerced from its metadata.

Both members are optional and any JSON scalar is well-typed here; what a given value means depends on the data type it is applied to, which is a question for the rules layer.

Source code in src/zarr_metadata/v3/codec/scale_offset.py
@dataclass(frozen=True)
class ScaleOffsetCodec(CodecEntity):
    """The `scale_offset` codec, coerced from its metadata.

    Both members are optional and any JSON scalar is well-typed here; what
    a given value means depends on the data type it is applied to, which
    is a question for the rules layer.
    """

    offset: JSONValue | UNSET = UNSET
    scale: JSONValue | UNSET = UNSET

    identifier: ClassVar[str] = SCALE_OFFSET_CODEC_NAME
    kind: ClassVar[CodecKind] = "array_array"

    member_types: ClassVar[MemberTypes] = {
        "offset": (False, is_json_value),
        "scale": (False, is_json_value),
    }

    def transition(self, incoming: ArrayParts) -> ArrayParts | None:
        """The same array, element for element.

        The registry entry removed the `astype` field, so this codec no
        longer changes the element type -- only the values.
        """
        return incoming

    def problems(self) -> tuple[ValidationProblem, ...]:
        """Each value is a scalar of the array's type, so neither is null.

        The registry says each is "JSON-encoded per the input array's
        fill-value rules", and no data type admits `null` as a fill value.
        Which scalar it should be needs the data type, so that part is the
        document's question, not this codec's.
        """
        return tuple(
            found
            for member in ("offset", "scale")
            if getattr(self, member) is None
            for found in problem((member,), "expected a scalar, got null", "invalid_value")
        )

    def to_json(self) -> ScaleOffsetCodecObject | ScaleOffsetCodecName:
        return cast("ScaleOffsetCodecObject | ScaleOffsetCodecName", super().to_json())

configuration_required class-attribute

configuration_required: bool = False

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

identifier class-attribute

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'array_array'

member_types class-attribute

member_types: MemberTypes = {
    "offset": (False, is_json_value),
    "scale": (False, is_json_value),
}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

offset class-attribute instance-attribute

offset: JSONValue | UNSET = UNSET

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

scale class-attribute instance-attribute

scale: JSONValue | UNSET = UNSET

variable_size class-attribute

variable_size: bool = False

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    offset: JSONValue | UNSET = UNSET,
    scale: JSONValue | UNSET = UNSET,
    *,
    must_understand: bool = True,
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

This entity in the simplest form that means the same thing.

A transformation, asked for by canonicalize_array_metadata_v3 and by nothing else. to_json does not apply it, because writing a document back is not the same as asking for it to be rewritten: a reader that reads and writes should not change bytes it was not asked to change.

Default: entities are already canonical. Override where two spellings of a member mean the same -- a rectilinear dimension's run-length encoding, a typesize that noshuffle ignores -- and where a contained entity has its own canonical form.

Source code in src/zarr_metadata/v3/_entity.py
def canonical(self) -> Self:
    """This entity in the simplest form that means the same thing.

    A *transformation*, asked for by `canonicalize_array_metadata_v3`
    and by nothing else. `to_json` does not apply it, because writing
    a document back is not the same as asking for it to be rewritten:
    a reader that reads and writes should not change bytes it was not
    asked to change.

    Default: entities are already canonical. Override where two
    spellings of a member mean the same -- a rectilinear dimension's
    run-length encoding, a `typesize` that `noshuffle` ignores -- and
    where a contained entity has its own canonical form.
    """
    return self

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def coerce(cls, value: object, context: Context) -> Coerced[Self]:
    """`value` as this entity, or the reasons it is not one.

    `context` is the scope this reading is happening in; most entities
    have no use for it and ignore it.
    """
    name, configuration, must_understand = named_configuration(value)
    if name is None or not cls.accepts(name):
        return None, problem((), f"expected the {cls.identifier!r} entity")
    if configuration is None:
        if cls.configuration_required:
            return None, problem(
                ("configuration",),
                f"{cls.identifier!r} requires a configuration",
                "missing_key",
            )
        configuration = cast("Mapping[str, object]", {})
    members, found, unreadable = coerce_members(configuration, cls.member_types)
    if len(unreadable) != 0:
        # The entity cannot be built, but the members that *did* read
        # can still be judged -- one bad member should not hide the
        # value problems of the ones beside it. Anything the partial
        # reading says about an unreadable member is its default
        # talking, so those are dropped.
        partial = cls(must_understand=must_understand, **members)  # type: ignore[arg-type]
        found = (
            *found,
            # `within`, because a partial reading reports relative to
            # the configuration and `coerce`'s caller does not insert
            # that segment -- `coerce_members` problems already carry it.
            *within(
                (),
                [
                    entry
                    for entry in partial.problems()
                    if entry.loc[:1] not in {(key,) for key in unreadable}
                ],
            ),
        )
        return None, found
    return cls(must_understand=must_understand, **members), found  # type: ignore[arg-type]

configuration

configuration() -> dict[str, object]

This entity's configuration, as the document would write it.

Faithful to every member the entity holds: to_json is serialization, not canonicalization, so nothing is simplified here. Override only to render a member that is not already JSON, such as a contained entity.

Absent optional members are left out, which is what makes the bare-name spelling reachable. Absence is UNSET, never None: this package holds None to mean a JSON null the document actually wrote, and scale_offset is a real case where null and absent are different documents.

Source code in src/zarr_metadata/v3/_entity.py
def configuration(self) -> dict[str, object]:
    """This entity's configuration, as the document would write it.

    Faithful to every member the entity holds: `to_json` is
    serialization, not canonicalization, so nothing is simplified
    here. Override only to render a member that is not already JSON,
    such as a contained entity.

    Absent optional members are left out, which is what makes the
    bare-name spelling reachable. Absence is `UNSET`, never `None`:
    this package holds `None` to mean a JSON `null` the document
    actually wrote, and `scale_offset` is a real case where `null`
    and absent are different documents.
    """
    return {
        key: value
        for key in type(self).member_types
        if (value := getattr(self, key)) is not UNSET
    }

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

Why this codec cannot be applied to the array that reaches it.

incoming is None once the chain can no longer say what reaches here, and the default answer to that is nothing: declining beats guessing. Locations are relative to this codec's configuration, as problems' are; an empty one lands on the codec itself.

Source code in src/zarr_metadata/v3/_entity.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """Why this codec cannot be applied to the array that reaches it.

    `incoming` is None once the chain can no longer say what reaches
    here, and the default answer to that is nothing: declining beats
    guessing. Locations are relative to this codec's `configuration`,
    as `problems`' are; an empty one lands on the codec itself.
    """
    return ()

problems

problems() -> tuple[ValidationProblem, ...]

Each value is a scalar of the array's type, so neither is null.

The registry says each is "JSON-encoded per the input array's fill-value rules", and no data type admits null as a fill value. Which scalar it should be needs the data type, so that part is the document's question, not this codec's.

Source code in src/zarr_metadata/v3/codec/scale_offset.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """Each value is a scalar of the array's type, so neither is null.

    The registry says each is "JSON-encoded per the input array's
    fill-value rules", and no data type admits `null` as a fill value.
    Which scalar it should be needs the data type, so that part is the
    document's question, not this codec's.
    """
    return tuple(
        found
        for member in ("offset", "scale")
        if getattr(self, member) is None
        for found in problem((member,), "expected a scalar, got null", "invalid_value")
    )

to_json

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/scale_offset.py
def to_json(self) -> ScaleOffsetCodecObject | ScaleOffsetCodecName:
    return cast("ScaleOffsetCodecObject | ScaleOffsetCodecName", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

The same array, element for element.

The registry entry removed the astype field, so this codec no longer changes the element type -- only the values.

Source code in src/zarr_metadata/v3/codec/scale_offset.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """The same array, element for element.

    The registry entry removed the `astype` field, so this codec no
    longer changes the element type -- only the values.
    """
    return incoming

ScaleOffsetCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 scale_offset codec.

Both fields are optional. A missing offset is the additive identity (e.g. 0 for numeric types); a missing scale is the multiplicative identity (e.g. 1). Each scalar is JSON-encoded per the input array's fill-value rules, so "NaN" and "+Infinity" style strings are permitted in addition to numbers.

Source code in src/zarr_metadata/v3/codec/scale_offset.py
class ScaleOffsetCodecConfiguration(TypedDict, closed=True):
    """
    Configuration for the Zarr v3 `scale_offset` codec.

    Both fields are optional. A missing `offset` is the additive identity
    (e.g. 0 for numeric types); a missing `scale` is the multiplicative
    identity (e.g. 1). Each scalar is JSON-encoded per the input array's
    fill-value rules, so `"NaN"` and `"+Infinity"` style strings are
    permitted in addition to numbers.
    """

    offset: NotRequired[JSONValue]
    scale: NotRequired[JSONValue]

offset instance-attribute

scale instance-attribute

ScaleOffsetCodecObject

Bases: TypedDict

scale_offset codec metadata in object form.

configuration is itself optional per spec — when both offset and scale are at their identity defaults, the codec is a no-op and the entire configuration field may be omitted. https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/scale_offset/README.md#L18 and #L35

Source code in src/zarr_metadata/v3/codec/scale_offset.py
class ScaleOffsetCodecObject(TypedDict, closed=True):
    """`scale_offset` codec metadata in object form.

    `configuration` is itself optional per spec — when both `offset` and
    `scale` are at their identity defaults, the codec is a no-op and the
    entire `configuration` field may be omitted.
      https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/scale_offset/README.md#L18 and #L35
    """

    name: ScaleOffsetCodecName
    configuration: NotRequired[ScaleOffsetCodecConfiguration]
    must_understand: NotRequired[bool]

configuration instance-attribute

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

zarr_metadata.v3.codec.sharding_indexed

Sharding-indexed codec types.

See https://zarr-specs.readthedocs.io/en/latest/v3/codecs/sharding-indexed/index.html

SHARDING_INDEXED_CODEC_NAME module-attribute

SHARDING_INDEXED_CODEC_NAME: Final = 'sharding_indexed'

The name field value of the sharding_indexed codec.

SHARDING_INDEX_LOCATION module-attribute

SHARDING_INDEX_LOCATION: Final = ('start', 'end')

Tuple of permitted values for the index_location field of the sharding_indexed codec.

ShardingIndexLocation module-attribute

ShardingIndexLocation = Literal['start', 'end']

Literal type of the position of the shard index within the encoded shard.

ShardingIndexedCodecMetadata module-attribute

ShardingIndexedCodecMetadata = ShardingIndexedCodecObject

Permitted JSON shape for sharding_indexed codec metadata.

The configuration has multiple required keys (chunk_shape, codecs, index_codecs), so only the object form is valid; the short-hand-name form is not permitted by the spec for this codec. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/sharding-indexed/index.rst#L141-L155 (required members) https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1562-L1564 (short-hand names only "if no configuration metadata is required")

ShardingIndexedCodecName module-attribute

ShardingIndexedCodecName = Literal['sharding_indexed']

Literal type of the name field of the sharding_indexed codec.

__all__ module-attribute

__all__ = [
    "SHARDING_INDEXED_CODEC_NAME",
    "SHARDING_INDEX_LOCATION",
    "ShardingIndexLocation",
    "ShardingIndexedCodec",
    "ShardingIndexedCodecConfiguration",
    "ShardingIndexedCodecMetadata",
    "ShardingIndexedCodecName",
    "ShardingIndexedCodecObject",
]

ShardingIndexedCodec dataclass

Bases: CodecEntity

The sharding_indexed codec, coerced from its metadata.

Holds two codec pipelines, so it is one of the few entities that needs the scope it is being read in: an entry of either pipeline is itself an entity, read the same way this one was.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
@dataclass(frozen=True)
class ShardingIndexedCodec(CodecEntity):
    """The `sharding_indexed` codec, coerced from its metadata.

    Holds two codec pipelines, so it is one of the few entities that
    needs the scope it is being read in: an entry of either pipeline is
    itself an entity, read the same way this one was.
    """

    chunk_shape: tuple[int, ...] = ()
    codecs: tuple[CodecEntity | Opaque, ...] = ()
    index_codecs: tuple[CodecEntity | Opaque, ...] = ()
    index_location: ShardingIndexLocation | UNSET = UNSET

    identifier: ClassVar[str] = SHARDING_INDEXED_CODEC_NAME
    variable_size: ClassVar[bool] = True
    kind: ClassVar[CodecKind] = "array_bytes"

    configuration_required: ClassVar[bool] = True
    member_types: ClassVar[MemberTypes] = {
        "chunk_shape": (True, sequence_of(is_int)),
        "codecs": (True, _is_field_tuple),
        "index_codecs": (True, _is_field_tuple),
        "index_location": (False, one_of(SHARDING_INDEX_LOCATION)),
    }

    @classmethod
    def coerce(cls, value: object, context: "Context") -> Coerced[Self]:
        shard, problems = super().coerce(value, context)
        if shard is None:
            return None, problems
        inner, from_inner = _coerce_pipeline(shard.codecs, context, ("configuration", "codecs"))
        index, from_index = _coerce_pipeline(
            shard.index_codecs, context, ("configuration", "index_codecs")
        )
        return (
            replace(shard, codecs=inner, index_codecs=index),
            (*problems, *from_inner, *from_index),
        )

    def problems(self) -> tuple[ValidationProblem, ...]:
        """This shard's own values, and those of the codecs it holds.

        Whether the two pipelines are well *formed* -- one array-to-bytes
        codec, in the right order -- spans the whole chain, so the rules
        layer asks that.
        """
        found: list[ValidationProblem] = [
            ValidationProblem(
                ("chunk_shape", position),
                f"expected a positive chunk extent, got {extent}",
                "invalid_value",
            )
            for position, extent in enumerate(self.chunk_shape)
            if extent < 1
        ]
        for member in ("codecs", "index_codecs"):
            for position, codec in enumerate(
                cast("tuple[CodecEntity | Opaque, ...]", getattr(self, member))
            ):
                if isinstance(codec, CodecEntity):
                    found.extend(within((member, position), codec.problems()))
        return tuple(found)

    def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
        """This shard against the array reaching it, and its two pipelines.

        One sharding configuration encodes every chunk, so its inner
        shape has to divide all of them. Under a rectilinear grid an axis
        has several lengths and the inner extent must divide each; an axis
        whose lengths are unknown declines while the others are judged.
        """
        found = list(self._inner_chunk_problems(incoming))
        # Both pipelines start from this codec's own configuration and
        # from the spec, so neither waits on what reached the codec. An
        # unreadable codec upstream costs the element type and the
        # enclosing extents; it does not make the inner chunk shape
        # unknown, and the index is a `uint64` array whatever precedes it.
        outer = incoming.grid if incoming is not None else UNKNOWN_GRID
        found.extend(
            chain_problems(
                self.codecs,
                ArrayParts(
                    ChunkGrid.regular(self.chunk_shape),
                    incoming.data_type if incoming is not None else None,
                ),
                ("codecs",),
            )
        )
        found.extend(
            chain_problems(
                self.index_codecs,
                ArrayParts(shard_index_grid(outer, self.chunk_shape), Uint64DataType()),
                ("index_codecs",),
            )
        )
        found.extend(
            ValidationProblem(
                ("index_codecs", index),
                f"{type(codec).identifier!r} produces variable-size output; "
                "index_codecs must be fixed-size",
                "invalid_value",
            )
            for index, codec in enumerate(self.index_codecs)
            if isinstance(codec, CodecEntity) and type(codec).variable_size
        )
        return tuple(found)

    def _inner_chunk_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
        """Whether the inner chunk divides every chunk this shard receives."""
        if incoming is None or incoming.grid.rank is None:
            return ()
        if len(self.chunk_shape) != incoming.grid.rank:
            return problem(
                ("chunk_shape",),
                f"chunk_shape has {len(self.chunk_shape)} entries but the incoming array "
                f"has {incoming.grid.rank} dimensions",
                "invalid_value",
            )
        found: list[ValidationProblem] = []
        for position, extent in enumerate(self.chunk_shape):
            lengths = incoming.grid.axis(position)
            if lengths is None or extent < 1:
                continue
            indivisible = sorted(length for length in lengths if length % extent != 0)
            if len(indivisible) != 0:
                found.extend(
                    problem(
                        ("chunk_shape", position),
                        f"inner chunk extent {extent} does not evenly divide the incoming "
                        f"extent {indivisible[0]}",
                        "invalid_value",
                    )
                )
        return tuple(found)

    def canonical(self) -> Self:
        """Each codec of each pipeline in its own canonical form."""
        return replace(
            self,
            codecs=_canonical_pipeline(self.codecs),
            index_codecs=_canonical_pipeline(self.index_codecs),
        )

    def configuration(self) -> dict[str, object]:
        """The two pipelines in their canonical spelling, entry by entry."""
        members = super().configuration()
        for member in ("codecs", "index_codecs"):
            members[member] = tuple(
                entry.to_json() if isinstance(entry, CodecEntity) else entry.json
                for entry in cast("tuple[CodecEntity | Opaque, ...]", members[member])
            )
        return members

    def to_json(self) -> ShardingIndexedCodecObject:
        return cast("ShardingIndexedCodecObject", super().to_json())

chunk_shape class-attribute instance-attribute

chunk_shape: tuple[int, ...] = ()

codecs class-attribute instance-attribute

codecs: tuple[CodecEntity | Opaque, ...] = ()

configuration_required class-attribute

configuration_required: bool = True

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

identifier class-attribute

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

index_codecs class-attribute instance-attribute

index_codecs: tuple[CodecEntity | Opaque, ...] = ()

index_location class-attribute instance-attribute

index_location: ShardingIndexLocation | UNSET = UNSET

kind class-attribute

kind: CodecKind = 'array_bytes'

member_types class-attribute

member_types: MemberTypes = {
    "chunk_shape": (True, sequence_of(is_int)),
    "codecs": (True, _is_field_tuple),
    "index_codecs": (True, _is_field_tuple),
    "index_location": (
        False,
        one_of(SHARDING_INDEX_LOCATION),
    ),
}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

variable_size class-attribute

variable_size: bool = True

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    chunk_shape: tuple[int, ...] = (),
    codecs: tuple[CodecEntity | Opaque, ...] = (),
    index_codecs: tuple[CodecEntity | Opaque, ...] = (),
    index_location: ShardingIndexLocation | UNSET = UNSET,
    *,
    must_understand: bool = True,
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

Each codec of each pipeline in its own canonical form.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
def canonical(self) -> Self:
    """Each codec of each pipeline in its own canonical form."""
    return replace(
        self,
        codecs=_canonical_pipeline(self.codecs),
        index_codecs=_canonical_pipeline(self.index_codecs),
    )

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
@classmethod
def coerce(cls, value: object, context: "Context") -> Coerced[Self]:
    shard, problems = super().coerce(value, context)
    if shard is None:
        return None, problems
    inner, from_inner = _coerce_pipeline(shard.codecs, context, ("configuration", "codecs"))
    index, from_index = _coerce_pipeline(
        shard.index_codecs, context, ("configuration", "index_codecs")
    )
    return (
        replace(shard, codecs=inner, index_codecs=index),
        (*problems, *from_inner, *from_index),
    )

configuration

configuration() -> dict[str, object]

The two pipelines in their canonical spelling, entry by entry.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
def configuration(self) -> dict[str, object]:
    """The two pipelines in their canonical spelling, entry by entry."""
    members = super().configuration()
    for member in ("codecs", "index_codecs"):
        members[member] = tuple(
            entry.to_json() if isinstance(entry, CodecEntity) else entry.json
            for entry in cast("tuple[CodecEntity | Opaque, ...]", members[member])
        )
    return members

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

This shard against the array reaching it, and its two pipelines.

One sharding configuration encodes every chunk, so its inner shape has to divide all of them. Under a rectilinear grid an axis has several lengths and the inner extent must divide each; an axis whose lengths are unknown declines while the others are judged.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """This shard against the array reaching it, and its two pipelines.

    One sharding configuration encodes every chunk, so its inner
    shape has to divide all of them. Under a rectilinear grid an axis
    has several lengths and the inner extent must divide each; an axis
    whose lengths are unknown declines while the others are judged.
    """
    found = list(self._inner_chunk_problems(incoming))
    # Both pipelines start from this codec's own configuration and
    # from the spec, so neither waits on what reached the codec. An
    # unreadable codec upstream costs the element type and the
    # enclosing extents; it does not make the inner chunk shape
    # unknown, and the index is a `uint64` array whatever precedes it.
    outer = incoming.grid if incoming is not None else UNKNOWN_GRID
    found.extend(
        chain_problems(
            self.codecs,
            ArrayParts(
                ChunkGrid.regular(self.chunk_shape),
                incoming.data_type if incoming is not None else None,
            ),
            ("codecs",),
        )
    )
    found.extend(
        chain_problems(
            self.index_codecs,
            ArrayParts(shard_index_grid(outer, self.chunk_shape), Uint64DataType()),
            ("index_codecs",),
        )
    )
    found.extend(
        ValidationProblem(
            ("index_codecs", index),
            f"{type(codec).identifier!r} produces variable-size output; "
            "index_codecs must be fixed-size",
            "invalid_value",
        )
        for index, codec in enumerate(self.index_codecs)
        if isinstance(codec, CodecEntity) and type(codec).variable_size
    )
    return tuple(found)

problems

problems() -> tuple[ValidationProblem, ...]

This shard's own values, and those of the codecs it holds.

Whether the two pipelines are well formed -- one array-to-bytes codec, in the right order -- spans the whole chain, so the rules layer asks that.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """This shard's own values, and those of the codecs it holds.

    Whether the two pipelines are well *formed* -- one array-to-bytes
    codec, in the right order -- spans the whole chain, so the rules
    layer asks that.
    """
    found: list[ValidationProblem] = [
        ValidationProblem(
            ("chunk_shape", position),
            f"expected a positive chunk extent, got {extent}",
            "invalid_value",
        )
        for position, extent in enumerate(self.chunk_shape)
        if extent < 1
    ]
    for member in ("codecs", "index_codecs"):
        for position, codec in enumerate(
            cast("tuple[CodecEntity | Opaque, ...]", getattr(self, member))
        ):
            if isinstance(codec, CodecEntity):
                found.extend(within((member, position), codec.problems()))
    return tuple(found)

to_json

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
def to_json(self) -> ShardingIndexedCodecObject:
    return cast("ShardingIndexedCodecObject", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

What the next codec in the chain sees, or None if undeterminable.

Only an array-to-array codec has anything to say: the two later kinds end shape propagation by construction, one by consuming the array and the other by never having had it.

The default is None, so a modelled codec that forgets to say how it transforms the array stops propagation rather than silently claiming to leave it alone. Failing closed here costs a judgment; failing open would invent one.

Source code in src/zarr_metadata/v3/_entity.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """What the next codec in the chain sees, or None if undeterminable.

    Only an array-to-array codec has anything to say: the two later
    kinds end shape propagation by construction, one by consuming the
    array and the other by never having had it.

    The default is None, so a modelled codec that forgets to say how
    it transforms the array stops propagation rather than silently
    claiming to leave it alone. Failing closed here costs a judgment;
    failing open would invent one.
    """
    return None

ShardingIndexedCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 sharding_indexed codec.

chunk_shape is the shape of inner chunks along each dimension; it must evenly divide the shard shape.

codecs is the codec pipeline applied to each inner chunk; exactly one array-to-bytes codec is required.

index_codecs is the codec pipeline applied to the shard index; it must be deterministic (no variable-size compression). https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/sharding-indexed/index.rst#L147-L155

index_location defaults to "end" per the spec. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/sharding-indexed/index.rst#L157-L161

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
class ShardingIndexedCodecConfiguration(TypedDict, closed=True):
    """
    Configuration for the Zarr v3 `sharding_indexed` codec.

    `chunk_shape` is the shape of inner chunks along each dimension;
    it must evenly divide the shard shape.

    `codecs` is the codec pipeline applied to each inner chunk; exactly
    one array-to-bytes codec is required.

    `index_codecs` is the codec pipeline applied to the shard index;
    it must be deterministic (no variable-size compression).
      https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/sharding-indexed/index.rst#L147-L155

    `index_location` defaults to `"end"` per the spec.
      https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/sharding-indexed/index.rst#L157-L161
    """

    chunk_shape: tuple[int, ...]
    codecs: tuple[ZarrV3MetadataFieldJSON, ...]
    index_codecs: tuple[ZarrV3MetadataFieldJSON, ...]
    index_location: NotRequired[ShardingIndexLocation]

chunk_shape instance-attribute

chunk_shape: tuple[int, ...]

codecs instance-attribute

index_codecs instance-attribute

index_codecs: tuple[ZarrV3MetadataFieldJSON, ...]

index_location instance-attribute

ShardingIndexedCodecObject

Bases: TypedDict

sharding_indexed codec metadata in object form.

Source code in src/zarr_metadata/v3/codec/sharding_indexed.py
class ShardingIndexedCodecObject(TypedDict, closed=True):
    """`sharding_indexed` codec metadata in object form."""

    name: ShardingIndexedCodecName
    configuration: ShardingIndexedCodecConfiguration
    must_understand: NotRequired[bool]

configuration instance-attribute

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

zarr_metadata.v3.codec.transpose

Transpose codec types.

See https://zarr-specs.readthedocs.io/en/latest/v3/codecs/transpose/index.html

TRANSPOSE_CODEC_NAME module-attribute

TRANSPOSE_CODEC_NAME: Final = 'transpose'

The name field value of the transpose codec.

TransposeCodecMetadata module-attribute

TransposeCodecMetadata = TransposeCodecObject

Permitted JSON shape for transpose codec metadata.

order is required, so only the object form is valid; the short-hand-name form is not permitted by the spec for this codec. https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/codecs/transpose/index.rst#L60-L66 ("order: Required") https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1562-L1564 (short-hand names only "if no configuration metadata is required")

TransposeCodecName module-attribute

TransposeCodecName = Literal['transpose']

Literal type of the name field of the transpose codec.

__all__ module-attribute

__all__ = [
    "TRANSPOSE_CODEC_NAME",
    "TransposeCodec",
    "TransposeCodecConfiguration",
    "TransposeCodecMetadata",
    "TransposeCodecName",
    "TransposeCodecObject",
]

TransposeCodec dataclass

Bases: CodecEntity

The transpose codec, coerced from its metadata.

Source code in src/zarr_metadata/v3/codec/transpose.py
@dataclass(frozen=True)
class TransposeCodec(CodecEntity):
    """The `transpose` codec, coerced from its metadata."""

    order: tuple[int, ...] = ()

    identifier: ClassVar[str] = TRANSPOSE_CODEC_NAME
    kind: ClassVar[CodecKind] = "array_array"

    configuration_required: ClassVar[bool] = True
    member_types: ClassVar[MemberTypes] = {"order": (True, sequence_of(is_int))}

    def problems(self) -> tuple[ValidationProblem, ...]:
        """`order` must permute its own axes.

        Whether it permutes the *array's* axes is a different question --
        it needs the array's rank -- and the rules layer asks that one.
        """
        if sorted(self.order) != list(range(len(self.order))):
            return problem(
                ("order",),
                f"expected a permutation of 0..{len(self.order) - 1}, got {self.order!r}",
                "invalid_value",
            )
        return ()

    def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
        """A transpose permutes the array it receives, so ranks must agree.

        Judged against what actually reaches this codec: inside a shard
        that is the inner chunk, and after another transpose it is that
        transpose's output.
        """
        rank = incoming.grid.rank if incoming is not None else None
        if rank is None or len(self.order) == rank:
            return ()
        return problem(
            ("order",),
            f"order has {len(self.order)} entries but the incoming array has {rank} dimensions",
            "invalid_value",
        )

    def transition(self, incoming: ArrayParts) -> ArrayParts | None:
        """The same array with its axes reordered.

        A transposed regular grid is still a regular grid, so the parts
        survive the trip; the grid metadata does not, because it is no
        longer the grid the document wrote.
        """
        return incoming.with_grid(incoming.grid.permuted(self.order))

    def to_json(self) -> TransposeCodecObject:
        return cast("TransposeCodecObject", super().to_json())

configuration_required class-attribute

configuration_required: bool = True

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

identifier class-attribute

identifier: str = TRANSPOSE_CODEC_NAME

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'array_array'

member_types class-attribute

member_types: MemberTypes = {
    "order": (True, sequence_of(is_int))
}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

order class-attribute instance-attribute

order: tuple[int, ...] = ()

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

variable_size class-attribute

variable_size: bool = False

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    order: tuple[int, ...] = (),
    *,
    must_understand: bool = True,
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

This entity in the simplest form that means the same thing.

A transformation, asked for by canonicalize_array_metadata_v3 and by nothing else. to_json does not apply it, because writing a document back is not the same as asking for it to be rewritten: a reader that reads and writes should not change bytes it was not asked to change.

Default: entities are already canonical. Override where two spellings of a member mean the same -- a rectilinear dimension's run-length encoding, a typesize that noshuffle ignores -- and where a contained entity has its own canonical form.

Source code in src/zarr_metadata/v3/_entity.py
def canonical(self) -> Self:
    """This entity in the simplest form that means the same thing.

    A *transformation*, asked for by `canonicalize_array_metadata_v3`
    and by nothing else. `to_json` does not apply it, because writing
    a document back is not the same as asking for it to be rewritten:
    a reader that reads and writes should not change bytes it was not
    asked to change.

    Default: entities are already canonical. Override where two
    spellings of a member mean the same -- a rectilinear dimension's
    run-length encoding, a `typesize` that `noshuffle` ignores -- and
    where a contained entity has its own canonical form.
    """
    return self

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def coerce(cls, value: object, context: Context) -> Coerced[Self]:
    """`value` as this entity, or the reasons it is not one.

    `context` is the scope this reading is happening in; most entities
    have no use for it and ignore it.
    """
    name, configuration, must_understand = named_configuration(value)
    if name is None or not cls.accepts(name):
        return None, problem((), f"expected the {cls.identifier!r} entity")
    if configuration is None:
        if cls.configuration_required:
            return None, problem(
                ("configuration",),
                f"{cls.identifier!r} requires a configuration",
                "missing_key",
            )
        configuration = cast("Mapping[str, object]", {})
    members, found, unreadable = coerce_members(configuration, cls.member_types)
    if len(unreadable) != 0:
        # The entity cannot be built, but the members that *did* read
        # can still be judged -- one bad member should not hide the
        # value problems of the ones beside it. Anything the partial
        # reading says about an unreadable member is its default
        # talking, so those are dropped.
        partial = cls(must_understand=must_understand, **members)  # type: ignore[arg-type]
        found = (
            *found,
            # `within`, because a partial reading reports relative to
            # the configuration and `coerce`'s caller does not insert
            # that segment -- `coerce_members` problems already carry it.
            *within(
                (),
                [
                    entry
                    for entry in partial.problems()
                    if entry.loc[:1] not in {(key,) for key in unreadable}
                ],
            ),
        )
        return None, found
    return cls(must_understand=must_understand, **members), found  # type: ignore[arg-type]

configuration

configuration() -> dict[str, object]

This entity's configuration, as the document would write it.

Faithful to every member the entity holds: to_json is serialization, not canonicalization, so nothing is simplified here. Override only to render a member that is not already JSON, such as a contained entity.

Absent optional members are left out, which is what makes the bare-name spelling reachable. Absence is UNSET, never None: this package holds None to mean a JSON null the document actually wrote, and scale_offset is a real case where null and absent are different documents.

Source code in src/zarr_metadata/v3/_entity.py
def configuration(self) -> dict[str, object]:
    """This entity's configuration, as the document would write it.

    Faithful to every member the entity holds: `to_json` is
    serialization, not canonicalization, so nothing is simplified
    here. Override only to render a member that is not already JSON,
    such as a contained entity.

    Absent optional members are left out, which is what makes the
    bare-name spelling reachable. Absence is `UNSET`, never `None`:
    this package holds `None` to mean a JSON `null` the document
    actually wrote, and `scale_offset` is a real case where `null`
    and absent are different documents.
    """
    return {
        key: value
        for key in type(self).member_types
        if (value := getattr(self, key)) is not UNSET
    }

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

A transpose permutes the array it receives, so ranks must agree.

Judged against what actually reaches this codec: inside a shard that is the inner chunk, and after another transpose it is that transpose's output.

Source code in src/zarr_metadata/v3/codec/transpose.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """A transpose permutes the array it receives, so ranks must agree.

    Judged against what actually reaches this codec: inside a shard
    that is the inner chunk, and after another transpose it is that
    transpose's output.
    """
    rank = incoming.grid.rank if incoming is not None else None
    if rank is None or len(self.order) == rank:
        return ()
    return problem(
        ("order",),
        f"order has {len(self.order)} entries but the incoming array has {rank} dimensions",
        "invalid_value",
    )

problems

problems() -> tuple[ValidationProblem, ...]

order must permute its own axes.

Whether it permutes the array's axes is a different question -- it needs the array's rank -- and the rules layer asks that one.

Source code in src/zarr_metadata/v3/codec/transpose.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """`order` must permute its own axes.

    Whether it permutes the *array's* axes is a different question --
    it needs the array's rank -- and the rules layer asks that one.
    """
    if sorted(self.order) != list(range(len(self.order))):
        return problem(
            ("order",),
            f"expected a permutation of 0..{len(self.order) - 1}, got {self.order!r}",
            "invalid_value",
        )
    return ()

to_json

to_json() -> TransposeCodecObject

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/transpose.py
def to_json(self) -> TransposeCodecObject:
    return cast("TransposeCodecObject", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

The same array with its axes reordered.

A transposed regular grid is still a regular grid, so the parts survive the trip; the grid metadata does not, because it is no longer the grid the document wrote.

Source code in src/zarr_metadata/v3/codec/transpose.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """The same array with its axes reordered.

    A transposed regular grid is still a regular grid, so the parts
    survive the trip; the grid metadata does not, because it is no
    longer the grid the document wrote.
    """
    return incoming.with_grid(incoming.grid.permuted(self.order))

TransposeCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 transpose codec.

order is a permutation of the dimension indices 0..n-1 that specifies the dimension reordering applied during encoding.

Source code in src/zarr_metadata/v3/codec/transpose.py
class TransposeCodecConfiguration(TypedDict, closed=True):
    """
    Configuration for the Zarr v3 `transpose` codec.

    `order` is a permutation of the dimension indices 0..n-1 that
    specifies the dimension reordering applied during encoding.
    """

    order: tuple[int, ...]

order instance-attribute

order: tuple[int, ...]

TransposeCodecObject

Bases: TypedDict

transpose codec metadata in object form.

Source code in src/zarr_metadata/v3/codec/transpose.py
class TransposeCodecObject(TypedDict, closed=True):
    """`transpose` codec metadata in object form."""

    name: TransposeCodecName
    configuration: TransposeCodecConfiguration
    must_understand: NotRequired[bool]

configuration instance-attribute

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute

zarr_metadata.v3.codec.zstd

Zstandard codec types.

See https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/zstd/README.md (the zarr-extensions registry entry; zarr-specs PR #256, which first proposed the codec, was never merged).

ZSTD_CODEC_NAME module-attribute

ZSTD_CODEC_NAME: Final = 'zstd'

The name field value of the zstd codec.

ZSTD_MAX_LEVEL module-attribute

ZSTD_MAX_LEVEL: Final = 22

The highest level zstd accepts: ZSTD_maxCLevel().

ZSTD_MIN_LEVEL module-attribute

ZSTD_MIN_LEVEL: Final = -131072

The lowest level zstd accepts: ZSTD_minCLevel(), -(1 << 17).

ZstdCodecMetadata module-attribute

ZstdCodecMetadata = ZstdCodecObject

Permitted JSON shape for zstd codec metadata.

level is required, so only the object form is valid; the short-hand-name form is not permitted by the spec for this codec. https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/zstd/README.md#L9-L19 https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1562-L1564 (short-hand names only "if no configuration metadata is required")

ZstdCodecName module-attribute

ZstdCodecName = Literal['zstd']

Literal type of the name field of the zstd codec.

__all__ module-attribute

__all__ = [
    "ZSTD_CODEC_NAME",
    "ZSTD_MAX_LEVEL",
    "ZSTD_MIN_LEVEL",
    "ZstdCodec",
    "ZstdCodecConfiguration",
    "ZstdCodecMetadata",
    "ZstdCodecName",
    "ZstdCodecObject",
]

ZstdCodec dataclass

Bases: CodecEntity

The zstd codec, coerced from its metadata.

Source code in src/zarr_metadata/v3/codec/zstd.py
@dataclass(frozen=True)
class ZstdCodec(CodecEntity):
    """The `zstd` codec, coerced from its metadata."""

    level: int = 0
    checksum: bool | UNSET = UNSET

    identifier: ClassVar[str] = ZSTD_CODEC_NAME
    variable_size: ClassVar[bool] = True
    kind: ClassVar[CodecKind] = "bytes_bytes"

    configuration_required: ClassVar[bool] = True
    member_types: ClassVar[MemberTypes] = {
        "level": (True, is_int),
        "checksum": (False, is_bool),
    }

    def problems(self) -> tuple[ValidationProblem, ...]:
        """zstd compression levels run -131072 to 22."""
        if not ZSTD_MIN_LEVEL <= self.level <= ZSTD_MAX_LEVEL:
            return problem(
                ("level",),
                f"expected an integer in [{ZSTD_MIN_LEVEL}, {ZSTD_MAX_LEVEL}], got {self.level}",
                "invalid_value",
            )
        return ()

    def to_json(self) -> ZstdCodecObject:
        return cast("ZstdCodecObject", super().to_json())

checksum class-attribute instance-attribute

checksum: bool | UNSET = UNSET

configuration_required class-attribute

configuration_required: bool = True

Whether the bare-name spelling says too little for this entity.

The spec permits a bare name "if no configuration metadata is required", so this is true exactly when some member is required.

identifier class-attribute

identifier: str = ZSTD_CODEC_NAME

The name this entity is registered under.

Usually the name the metadata carries. The raw-bytes data types are the exception: every r<N> spelling is one family, so the family gets an invented identifier that no real name can collide with.

kind class-attribute

kind: CodecKind = 'bytes_bytes'

level class-attribute instance-attribute

level: int = 0

member_types class-attribute

member_types: MemberTypes = {
    "level": (True, is_int),
    "checksum": (False, is_bool),
}

The configuration members, and the type each one takes.

The same keys as the configuration TypedDict, which is the same as the constructor signature; tests/v3/test_entities.py holds the three together.

must_understand class-attribute instance-attribute

must_understand: bool = field(default=True, kw_only=True)

name property

name: str

The name this entity carries, as a document would write it.

Usually the identifier. They differ for the raw-bytes family, whose identifier is invented and belongs in no message a reader sees -- so anything user-facing wants this, and anything looking something up wants identifier.

required_class_vars class-attribute

required_class_vars: tuple[str, ...] = (
    "identifier",
    "kind",
)

Every class variable a concrete entity of this kind must declare.

variable_size class-attribute

variable_size: bool = True

Whether this codec's output size depends on the bytes it is given.

A compressor's does, so a shard index encoded with one has no size derivable from metadata alone, and the shard cannot be read.

__init__

__init__(
    level: int = 0,
    checksum: bool | UNSET = UNSET,
    *,
    must_understand: bool = True,
) -> None

__init_subclass__

__init_subclass__(
    *, base: bool = False, **kwargs: object
) -> None

Refuse a subclass that forgot to say what it is.

identifier and the per-kind class variables carry no default, so a subclass omitting one type-checks cleanly and then raises AttributeError from whichever method is reached first. Saying so here makes it an import-time error in the extension's own module.

base=True for a class that exists to add a class variable rather than to be an entity -- CodecEntity, IntegerDataType.

Source code in src/zarr_metadata/v3/_entity.py
def __init_subclass__(cls, *, base: bool = False, **kwargs: object) -> None:
    """Refuse a subclass that forgot to say what it is.

    `identifier` and the per-kind class variables carry no default,
    so a subclass omitting one type-checks cleanly and then raises
    `AttributeError` from whichever method is reached first. Saying so
    here makes it an import-time error in the extension's own module.

    `base=True` for a class that exists to add a class variable
    rather than to be an entity -- `CodecEntity`, `IntegerDataType`.
    """
    super().__init_subclass__(**kwargs)
    if base:
        return
    missing = [name for name in cls.required_class_vars if not hasattr(cls, name)]
    if len(missing) != 0:
        msg = f"{cls.__name__} does not declare {', '.join(missing)}"
        raise TypeError(msg)
    # An optional member defaults to UNSET or `configuration` emits it
    # for every instance, so the bare-name spelling becomes
    # unreachable and a document gains a member it never wrote.
    invented = [
        key
        for key, (required, _) in cls.member_types.items()
        if not required and getattr(cls, key, UNSET) is not UNSET
    ]
    if len(invented) != 0:
        msg = (
            f"{cls.__name__} gives the optional member(s) "
            f"{', '.join(invented)} a default other than UNSET"
        )
        raise TypeError(msg)

accepts classmethod

accepts(name: str) -> bool

Whether name denotes this entity.

Constant for all but the raw-bytes family, where one class covers every r<N>.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def accepts(cls, name: str) -> bool:
    """Whether `name` denotes this entity.

    Constant for all but the raw-bytes family, where one class covers
    every `r<N>`.
    """
    return name == cls.identifier

canonical

canonical() -> Self

This entity in the simplest form that means the same thing.

A transformation, asked for by canonicalize_array_metadata_v3 and by nothing else. to_json does not apply it, because writing a document back is not the same as asking for it to be rewritten: a reader that reads and writes should not change bytes it was not asked to change.

Default: entities are already canonical. Override where two spellings of a member mean the same -- a rectilinear dimension's run-length encoding, a typesize that noshuffle ignores -- and where a contained entity has its own canonical form.

Source code in src/zarr_metadata/v3/_entity.py
def canonical(self) -> Self:
    """This entity in the simplest form that means the same thing.

    A *transformation*, asked for by `canonicalize_array_metadata_v3`
    and by nothing else. `to_json` does not apply it, because writing
    a document back is not the same as asking for it to be rewritten:
    a reader that reads and writes should not change bytes it was not
    asked to change.

    Default: entities are already canonical. Override where two
    spellings of a member mean the same -- a rectilinear dimension's
    run-length encoding, a `typesize` that `noshuffle` ignores -- and
    where a contained entity has its own canonical form.
    """
    return self

coerce classmethod

coerce(value: object, context: Context) -> Coerced[Self]

value as this entity, or the reasons it is not one.

context is the scope this reading is happening in; most entities have no use for it and ignore it.

Source code in src/zarr_metadata/v3/_entity.py
@classmethod
def coerce(cls, value: object, context: Context) -> Coerced[Self]:
    """`value` as this entity, or the reasons it is not one.

    `context` is the scope this reading is happening in; most entities
    have no use for it and ignore it.
    """
    name, configuration, must_understand = named_configuration(value)
    if name is None or not cls.accepts(name):
        return None, problem((), f"expected the {cls.identifier!r} entity")
    if configuration is None:
        if cls.configuration_required:
            return None, problem(
                ("configuration",),
                f"{cls.identifier!r} requires a configuration",
                "missing_key",
            )
        configuration = cast("Mapping[str, object]", {})
    members, found, unreadable = coerce_members(configuration, cls.member_types)
    if len(unreadable) != 0:
        # The entity cannot be built, but the members that *did* read
        # can still be judged -- one bad member should not hide the
        # value problems of the ones beside it. Anything the partial
        # reading says about an unreadable member is its default
        # talking, so those are dropped.
        partial = cls(must_understand=must_understand, **members)  # type: ignore[arg-type]
        found = (
            *found,
            # `within`, because a partial reading reports relative to
            # the configuration and `coerce`'s caller does not insert
            # that segment -- `coerce_members` problems already carry it.
            *within(
                (),
                [
                    entry
                    for entry in partial.problems()
                    if entry.loc[:1] not in {(key,) for key in unreadable}
                ],
            ),
        )
        return None, found
    return cls(must_understand=must_understand, **members), found  # type: ignore[arg-type]

configuration

configuration() -> dict[str, object]

This entity's configuration, as the document would write it.

Faithful to every member the entity holds: to_json is serialization, not canonicalization, so nothing is simplified here. Override only to render a member that is not already JSON, such as a contained entity.

Absent optional members are left out, which is what makes the bare-name spelling reachable. Absence is UNSET, never None: this package holds None to mean a JSON null the document actually wrote, and scale_offset is a real case where null and absent are different documents.

Source code in src/zarr_metadata/v3/_entity.py
def configuration(self) -> dict[str, object]:
    """This entity's configuration, as the document would write it.

    Faithful to every member the entity holds: `to_json` is
    serialization, not canonicalization, so nothing is simplified
    here. Override only to render a member that is not already JSON,
    such as a contained entity.

    Absent optional members are left out, which is what makes the
    bare-name spelling reachable. Absence is `UNSET`, never `None`:
    this package holds `None` to mean a JSON `null` the document
    actually wrote, and `scale_offset` is a real case where `null`
    and absent are different documents.
    """
    return {
        key: value
        for key in type(self).member_types
        if (value := getattr(self, key)) is not UNSET
    }

incoming_problems

incoming_problems(
    incoming: ArrayParts | None,
) -> tuple[ValidationProblem, ...]

Why this codec cannot be applied to the array that reaches it.

incoming is None once the chain can no longer say what reaches here, and the default answer to that is nothing: declining beats guessing. Locations are relative to this codec's configuration, as problems' are; an empty one lands on the codec itself.

Source code in src/zarr_metadata/v3/_entity.py
def incoming_problems(self, incoming: ArrayParts | None) -> tuple[ValidationProblem, ...]:
    """Why this codec cannot be applied to the array that reaches it.

    `incoming` is None once the chain can no longer say what reaches
    here, and the default answer to that is nothing: declining beats
    guessing. Locations are relative to this codec's `configuration`,
    as `problems`' are; an empty one lands on the codec itself.
    """
    return ()

problems

problems() -> tuple[ValidationProblem, ...]

zstd compression levels run -131072 to 22.

Source code in src/zarr_metadata/v3/codec/zstd.py
def problems(self) -> tuple[ValidationProblem, ...]:
    """zstd compression levels run -131072 to 22."""
    if not ZSTD_MIN_LEVEL <= self.level <= ZSTD_MAX_LEVEL:
        return problem(
            ("level",),
            f"expected an integer in [{ZSTD_MIN_LEVEL}, {ZSTD_MAX_LEVEL}], got {self.level}",
            "invalid_value",
        )
    return ()

to_json

to_json() -> ZstdCodecObject

This entity as a document would write it.

Faithful to every member: read a document, write it back, and the members come out as they went in. Ask canonical first if you want the simplest equivalent spelling.

What is not preserved is the envelope's spelling, because the entity does not model it: a bare name, {"name": x}, and {"name": x, "configuration": {}} all mean the same and all read to the same entity, so all three write back as the bare name. must_understand is omitted when true, which is its default; an explicit false is kept, because that one says something.

Subclasses narrow the return type to their own object TypedDict, which is the JSON form this dataclass models.

Source code in src/zarr_metadata/v3/codec/zstd.py
def to_json(self) -> ZstdCodecObject:
    return cast("ZstdCodecObject", super().to_json())

transition

transition(incoming: ArrayParts) -> ArrayParts | None

What the next codec in the chain sees, or None if undeterminable.

Only an array-to-array codec has anything to say: the two later kinds end shape propagation by construction, one by consuming the array and the other by never having had it.

The default is None, so a modelled codec that forgets to say how it transforms the array stops propagation rather than silently claiming to leave it alone. Failing closed here costs a judgment; failing open would invent one.

Source code in src/zarr_metadata/v3/_entity.py
def transition(self, incoming: ArrayParts) -> ArrayParts | None:
    """What the next codec in the chain sees, or None if undeterminable.

    Only an array-to-array codec has anything to say: the two later
    kinds end shape propagation by construction, one by consuming the
    array and the other by never having had it.

    The default is None, so a modelled codec that forgets to say how
    it transforms the array stops propagation rather than silently
    claiming to leave it alone. Failing closed here costs a judgment;
    failing open would invent one.
    """
    return None

ZstdCodecConfiguration

Bases: TypedDict

Configuration for the Zarr v3 zstd codec.

level is required; checksum is optional ("Should be omitted if false"). https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/zstd/README.md#L9-L19

Source code in src/zarr_metadata/v3/codec/zstd.py
class ZstdCodecConfiguration(TypedDict, closed=True):
    """
    Configuration for the Zarr v3 `zstd` codec.

    `level` is required; `checksum` is optional ("Should be omitted if
    false").
      https://github.com/zarr-developers/zarr-extensions/blob/4da7b37a84f76e660902f6d3de3eaef0e0febae6/codecs/zstd/README.md#L9-L19
    """

    level: int
    checksum: NotRequired[bool]

checksum instance-attribute

checksum: NotRequired[bool]

level instance-attribute

level: int

ZstdCodecObject

Bases: TypedDict

zstd codec metadata in object form.

Source code in src/zarr_metadata/v3/codec/zstd.py
class ZstdCodecObject(TypedDict, closed=True):
    """`zstd` codec metadata in object form."""

    name: ZstdCodecName
    configuration: ZstdCodecConfiguration
    must_understand: NotRequired[bool]

configuration instance-attribute

configuration: ZstdCodecConfiguration

must_understand instance-attribute

must_understand: NotRequired[bool]

name instance-attribute