zarr_metadata.v3.definition
zarr_metadata.v3.definition ¶
Metadata fields read against their definitions: the public door.
Every Zarr v3 extension point -- codecs, data types, chunk grids, chunk key encodings, storage transformers -- is a metadata field: a name, and a configuration whose JSON the extension defines. A definition says what that JSON is and what is allowed in it. It is a value, not a class to subclass:
name, the name the metadata carries;configuration, the TypedDict the configuration's JSON is, which is the one declaration of it: the type checker is compiled from it, and a checked configuration has it as its static type. It reads as the typing spec defines a TypedDict --total,Required,NotRequired,closedandextra_itemsmean what they mean to a type checker, whether or not its module postpones annotations -- and a member's type carries its bounds, as pydantic reads them: a gziplevelisAnnotated[int, Interval(ge=0, le=9)];rules, a function over that TypedDict yielding what the spec disallows that a type cannot say -- members read together -- located in the configuration.
What kind of metadata a definition defines is its type: CodecDefinition
(with the codec's kind, and its size: whether the size of what it gives
out is fixed by the size of what it is handed, "static", or depends on
the values, "dynamic"), DataTypeDefinition, ChunkGridDefinition,
ChunkKeyEncodingDefinition, StorageTransformerDefinition.
Reading JSON. Three steps, each feeding the next, and each usable on its own by a caller that holds nothing but JSON:
check(value, SomeTypedDict), fromzarr_metadata.typed_json, type-checks JSON against a TypedDict and needs nothing else: a value of the TypedDict or None, and every problem, each located. The value holds what the TypedDict admits and nothing else. A member typed with a field alias is checked as the JSON a metadata field is.definition.read_configuration(configuration)is the configuration as that definition reads it: type-checked, each nested field's envelope judged -- a stray member, amust_understandoffalse-- and then the rules, for one configuration:GZIP_CODEC.read_configuration({"level": 12}).-
resolve(field, CodecDefinition, CORE_AND_EXTENSIONS)reads a whole field in a scope: its envelope judged, its name related to a definition, its configuration judged, and each nested field read the same way. It returns what the scope made of the field, and every problem:AcceptedFieldby the definition that claims its name, with the configuration it checked and allowed and the fields it holds, each read the same way;UnclaimedField, a name nothing in scope claims, left unjudged, which is what keeps the format open; orRefusedField, whose problems say why. Each has the field's JSON, thenameit is written with, the kind it was read as,read_as, thedefinitionthat claims its name -- None forUnclaimedField-- and the fields it holds as the scope read them,nested. The two a model holds,AcceptedFieldandUnclaimedField, have aconfigurationandto_json(), the field as a document writes it; two of them are equal when they read the same, however each was spelled.ResolvedFieldis the three, formatch. A field is read as one of the five kinds, with or without type arguments;resolve(field, Definition, scope)is aTypeError, since nothing is filed under it. A whole v3 array document is read byread_array_metadata_v3, inzarr_metadata.model, whose reading gives each field with where it sits in the document.from zarr_metadata.v3.codec.gzip import GZIP_CODEC from zarr_metadata.v3.definition import CORE_AND_EXTENSIONS, CodecDefinition, resolve
resolved, problems = resolve({"name": "gzip", "configuration": {"level": 12}}, CodecDefinition, CORE_AND_EXTENSIONS) resolved # RefusedField(..., definition=CodecDefinition(name='gzip'), ...) problems[0].loc # ('configuration', 'level') problems[0].input # 12 dict(problems[0].ctx) # {'ge': 0, 'le': 9}
resolved, problems = resolve({"name": "gzip", "configuration": {"level": 5}}, CodecDefinition, CORE_AND_EXTENSIONS) resolved.definition is GZIP_CODEC # True resolved.configuration # {'level': 5}
Problems are values, not exceptions: ValidationProblem(loc, message,
kind), with kind one of invalid_type, invalid_value,
missing_key, unknown_key and invalid_json. A field with an
unknown key is still read -- the key reported, the configuration judged
without it -- so a consumer that tolerates one filters by kind and uses
what was read; the field is valid only when there is no problem at all.
Each problem carries what its message says as data, as pydantic's errors
and zod's issues do: input, what was found at loc -- the 12 above --
and ctx, what was expected, where that is more than a type: the bounds
{"ge": 0, "le": 9}, or the values of a closed set.
Writing an extension. A TypedDict, which says what the
configuration's JSON is, bounds and all; a function for the rules a type
cannot say; and a definition; then a scope that holds it. The TypedDict
is a typing_extensions.TypedDict: closed and extra_items are PEP
728's, which typing.TypedDict does not take on the versions this
package supports. The rules are handed the configuration and the fields
it holds as the scope read them: a field that is read keeps what it read
inside it as AcceptedField.nested, a Nested mapping by where each sits, so a
struct's rules reach its field types. read_configuration, which reads in no scope,
hands them none. A rule's message shows a value as the package's own
messages do, as JSON, with shown: null, [1, 2], "C". A rule
reports where a problem is; what is found there is the problem's
input without the rule saying so. Define each function at a module's
top level: a model holds the definitions that read its fields, so it
pickles, and compares equal once loaded, only when they do -- a lambda
or a closure does not pickle, and a functools.partial pickles but
compares unequal to itself loaded.
from collections.abc import Iterator
from typing import Annotated, NotRequired
from annotated_types import Ge
from typing_extensions import TypedDict
from zarr_metadata.v3.definition import (
CORE_AND_EXTENSIONS,
CodecDefinition,
Nested,
ValidationProblem,
)
class AcmeLz4Configuration(TypedDict, closed=True):
acceleration: Annotated[int, Ge(1)]
dictionary: NotRequired[str]
dictionary_size: NotRequired[Annotated[int, Ge(1)]]
def acme_lz4_rules(
configuration: AcmeLz4Configuration, nested: Nested
) -> Iterator[ValidationProblem]:
if "dictionary" in configuration and "dictionary_size" not in configuration:
yield ValidationProblem(
("dictionary_size",), "a dictionary needs its size", "missing_key"
)
ACME_LZ4 = CodecDefinition(
name="acme.lz4",
configuration=AcmeLz4Configuration,
kind="bytes_bytes",
size="dynamic",
rules=acme_lz4_rules,
)
SCOPE = CORE_AND_EXTENSIONS.extended_with(ACME_LZ4)
A scope reads whole documents as well as fields:
validate_array_metadata_v3(document, context=SCOPE), from
zarr_metadata.model, reads each extension point of a v3 array document
through the definitions in SCOPE, and so do the model's from_json and
from_key_value: a fill value is judged against the data type it names,
by that data type's definition, the chunk grid against the shape, by
the grid's definition, and the codecs as a pipeline, each by its
definition against the chunk it is handed.
The TypedDict says what a key it does not declare is: with
closed=True, a problem, as above; with extra_items=, a key holding
that type; with closed=False, anything at all. One that says none of
these is open by default, and would take a misspelled key without a
word, so a definition refuses it. Its members are the shapes JSON takes:
int, float, bool, str, None, JSONValue, a Literal,
tuple[T, ...] and tuple[T1, T2], a union, a TypedDict,
Mapping[str, V], a NewType and a type alias. A number's type may
carry bounds, as annotated-types spells them and pydantic reads them --
Gt, Ge, Lt, Le and Interval, one from each side -- at any
depth: tuple[Annotated[int, Ge(1)], ...] bounds each element. A value
out of them is a problem, invalid_value, whose message says what the
type admits, "expected an integer >= 1, got 0", and whose ctx holds
the bounds. The rules are asked only of a configuration within its
bounds, so a rule relies on them, as pydantic's after-validators and
zod's refinements do: until a value out of bounds is fixed, it is the
one problem reported of the configuration. Annotated may also carry a
note, a string or a Doc. Any other metadata -- a MinLen, a
Predicate, pydantic's Field -- is refused when the definition is
built, since a type the checker does not hold its values to would say
what is not so.
A member holding another metadata field is annotated with the field alias
of its kind -- a shard's codecs: tuple[CodecField, ...] -- and read in
the scope its field is read in. What is wrong with the field it holds is
that field's own, reported where it sits: the field holding it is still
read, as a document holding it would be. A member that takes codecs of
static size only is annotated StaticCodecField -- a shard's
index_codecs, since a reader finds the index by a size it knows before
reading it -- and a codec of dynamic size there is a problem at its
place, where the field is read in a scope; a name nothing claims is left
unjudged, its size unknown with the rest of it. ZarrV3MetadataFieldJSON
is the same JSON, but checks as JSON and nothing more, so a definition
refuses a member typed with it. An extension with nothing to configure
takes EmptyConfiguration, and is written with its name alone.
A data type also says what its fill value is: fill_value, the JSON
shape of one as an annotation the checker reads -- Int8FillValue,
whose type carries the range -- and fill_value_rules, a function
yielding what the spec disallows in a fill value of that shape that the
type cannot say: a hex string of another width, a number of byte values
the size does not take. The rules are handed the configuration, the fields it holds as the
scope read them, and the typed fill value, so a struct judges each
field's fill value by that field's own type.
fill_value_problems(data_type, value) judges a fill value against a data
type field the scope read; one nothing in scope claims leaves it unjudged.
A data type that says nothing of its fill value takes any JSON.
A fill value may be spelled more ways than one -- "NaN" and
"0x7fc00000" are one float32 -- so a data type says which spelling
is its value's own: fill_value_canonical, handed what the rules are
handed and a fill value they allow. Two fill values are one value of the
type exactly when their canonical spellings are written alike: the same
JSON, as json.dumps writes it, which == is not -- it takes -0.0,
a float32 of its own, for 0.0. canonical_fill_value(data_type,
value) spells one, and gives UNSET for a fill value with a problem; a
data type that says nothing of it spells each value as written.
A data type says how its values are stored, too: storage, a function
of its configuration and the fields it holds, giving a StorageClass --
in single bytes, in several bytes at a time, or each in as many as it
needs. A struct's is its fields'. storage_of(data_type) asks it of a
data type field the scope read: the bytes codec takes an endian for
numbers of several bytes, and a struct refuses a field whose values vary
in size. A data type that says nothing of it leaves it unknown.
A chunk grid says which arrays it fits: shape_rules, a function
yielding what the spec disallows in a grid of its configuration over an
array of a given shape -- a dimension with no chunk length, chunks that
fall short of one -- located in the configuration. A grid that says
nothing of the shape fits every one. It also says the lengths its chunks
take along each axis of an array it fits, chunk_lengths: a set per
axis, since a rectilinear grid's chunks differ. A reading holds both of
the grid it read: an entry for each dimension of the shape, None where
nothing says the lengths.
A codec is judged against what it is handed. The array hands its first
codec a Chunk: the lengths of its grid's chunks along each of the
array's dimensions, and its data type field, with None for what nothing
says. A codec handed an array says what the spec disallows in it handed
a chunk: chunk_rules, located in its configuration -- a transpose
whose order has another number of axes. An array -> array codec says
what it hands the next, whatever its chunk rules found: transition --
transpose permutes the axes. A reading reads the codec fields as a
pipeline: their order -- array -> array codecs, one array -> bytes codec,
bytes -> bytes codecs -- and then each against the chunk it is handed,
giving each codec's Stage with that chunk. A codec that holds pipelines of its own says what each is
handed: pipelines, by the member of its configuration that holds each
-- a shard's inner codecs its inner chunks, its index codecs the shard
index -- and each is read the same way, its stages kept as the codec's
Stage.inner.
Nothing is guessed: the codec after one the scope did not read, or after
one that says nothing of what it hands on, is handed a chunk nothing is
known of, Chunk(), which is refused nothing; a codec after that hands
on only what it says of its own accord.
Raw bits are the one data type whose name carries its configuration: a
document writes r and the size in bits, and r16 reads as r*, as the
specification's table writes raw bits, with {"bits": 16}. A reader that
reads raw bits its own way defines r*; a data type named r16 is
refused, since that name reads as r*. r* itself is notation, and a
document that writes it names nothing in any scope.
The simplest spelling. A field without problems has a simplest
equivalent spelling, which is what two fields are compared by: each nested
field in its own simplest spelling, then the definition's canonical --
blosc drops a typesize that noshuffle ignores, a rectilinear grid
run-length encodes its chunk shapes -- and the envelope in the fewest
words every reader takes: a data type with nothing to configure is its
bare name, any other field an object, {"name": ...}, as a Zarr v3.0
reader takes no short-hand name in codecs; raw bits write their size
back into the name, in decimal, so r008 is r8. A field with any
problem, an unknown key included, has none: a simpler spelling of it
would erase what its author wrote. What canonical gives is judged
again: one that does not hold is a ValueError, a fault in the
definition. canonical_fill_value spells a fill value the same way, as
the data type that read it spells one.
JSON Schema. node_metadata_json_schema_v3, in zarr_metadata.model,
writes a whole zarr.json as a JSON Schema, draft 2020-12, for a
validator in another language or an editor, its fields as the scope
reads them: each definition's field --
its name, its configuration as its TypedDict says, bounds and all, a
must_understand of true, and its bare name when it needs no
configuration -- and a name nothing in scope claims, with any
configuration. A field a configuration holds is written in the same
scope, and a member taking codecs of static size only takes those. The
rules are not in it, so a field it accepts may still have a problem;
one resolve reads without a problem, it accepts, as JSON: arrays as
lists, as a parser gives them. Each configuration
TypedDict, and each field alias, is written once, in $defs, under its
name; the fill value is held to its data type's.
Scopes as values. Two scopes are equal when they file the same
definitions, and equal scopes hash alike. Context.joined(*scopes) is
the least scope above each, or a ScopeConflictError naming each name
filed two ways; extended_with remains the way to take a name over on
purpose. A model's refined_in moves it to a scope that claims more and
contradicts nothing, and refines orders two models by information: a
name nothing claimed, read by a definition, is a gain; the reverse a
loss; one name read by two definitions a conflict.
A definition checks itself when it is built, and each of these is a
TypeError saying what is wrong: a configuration that is not a
TypedDict, says nothing of the keys it does not declare, or has a member
no checker reads, named down to the TypedDict that holds it; a name
that is not a string; a member declared as a function -- rules,
canonical, fill_value_rules, storage, shape_rules,
chunk_lengths, chunk_rules, transition, pipelines -- that is not
one; a data type's fill_value no checker reads, or one holding a
metadata field, which a value of the data type never is; a codec kind
that is not one of the three, or a size that is not "static" or
"dynamic"; a function no codec of its kind is asked -- chunk rules or
pipelines of a bytes -> bytes codec, which is handed bytes, or a
transition of a codec that hands on bytes; a data type named as raw
bits of one size are written. A scope refuses a definition of no kind.
Nothing happens at class creation.
CORE
module-attribute
¶
Only what the Zarr v3 specification defines.
CORE_AND_EXTENSIONS
module-attribute
¶
What the specification defines, plus what zarr-extensions registers.
ChunkGridField
module-attribute
¶
ChunkGridField = TypeAliasType(
"ChunkGridField", ZarrV3MetadataFieldJSON
)
A member holding a chunk grid: a document's chunk_grid.
ChunkKeyEncodingField
module-attribute
¶
ChunkKeyEncodingField = TypeAliasType(
"ChunkKeyEncodingField", ZarrV3MetadataFieldJSON
)
A member holding a chunk key encoding: a document's chunk_key_encoding.
ClaimKey
module-attribute
¶
A kind and the name a definition is filed under: what a scope answers claimant for.
Claims
module-attribute
¶
Claims: TypeAlias = Mapping[
ClaimKey, Definition[Any] | None
]
What a reading claims of each name a document writes: the definition that read it, or None where nothing claimed it.
CodecField
module-attribute
¶
CodecField = TypeAliasType(
"CodecField", ZarrV3MetadataFieldJSON
)
A member holding a codec: a document's codecs is tuple[CodecField, ...], and so is a shard's.
CodecKind
module-attribute
¶
CodecKind = Literal[
"array_array", "array_bytes", "bytes_bytes"
]
What a codec does to what it is handed: the three positions a pipeline orders.
CodecSize
module-attribute
¶
CodecSize = Literal['static', 'dynamic']
Whether the size of what a codec gives out is fixed by the size of what it is handed.
static: it is -- bytes writes each element in its width, crc32c adds
four bytes. dynamic: it depends on the values -- every compressor.
DataTypeField
module-attribute
¶
DataTypeField = TypeAliasType(
"DataTypeField", ZarrV3MetadataFieldJSON
)
A member holding a data type: a document's data_type, or a struct field's; read in the scope what holds it is read in.
JSONValue
module-attribute
¶
JSONValue = TypeAliasType(
"JSONValue",
int
| float
| bool
| str
| list["JSONValue"]
| tuple["JSONValue", ...]
| Mapping[str, "JSONValue"]
| None,
)
A recursive type alias for JSON-encodable values.
Defined via TypeAliasType (rather than a plain TypeAlias) so the
self-reference is a named recursion point that pydantic can resolve when
building a TypeAdapter; a bare recursive TypeAlias raises
PydanticUserError/RecursionError at validation time.
Lengths
module-attribute
¶
Per axis, every length chunks take along it -- a set, since a rectilinear grid's differ -- or None where unknown.
Loc
module-attribute
¶
Where in a document a value sits: the keys and indices down to it.
Nested
module-attribute
¶
Nested: TypeAlias = Mapping[Loc, ResolvedField[Any]]
The fields a configuration holds, each as the scope read it, by where it sits in the configuration.
ProblemKind
module-attribute
¶
ProblemKind = Literal[
"missing_key",
"invalid_type",
"invalid_value",
"invalid_json",
"unknown_key",
]
Machine-readable classification of a ValidationProblem.
missing_key: a required key (document key or store key) is absent.invalid_type: a value has the wrong structural type (e.g. a string where a mapping is required, a non-JSON-serializable object).invalid_value: a value has an acceptable type but an invalid content (e.g.zarr_format: 2in a v3 document,order: "Q").invalid_json: bytes that do not decode as JSON.unknown_key: a key an object's type does not declare, where the type says it is closed, as a closed TypedDict does. Whether a Zarr configuration is closed is rarely said (zarr-developers/zarr-specs#270 has been open since 2023), and many readers refuse such a key. It gets a kind of its own so that a caller who tolerates it can tell it from a wrong value, and so that it never masks the other findings about the same object.
ResolvedField
module-attribute
¶
ResolvedField = TypeAliasType(
"ResolvedField",
"AcceptedField[D] | UnclaimedField | RefusedField[D]",
type_params=(D,),
)
One metadata field as a scope read it: read by the definition that claims its name, claimed by nothing, or refused.
StaticCodecField
module-attribute
¶
StaticCodecField = TypeAliasType(
"StaticCodecField", ZarrV3MetadataFieldJSON
)
A member holding a codec of static size: a shard's index_codecs is one,
since a reader finds the index by a size it knows before reading it.
StorageClass
module-attribute
¶
StorageClass = Literal[
"single_byte", "multi_byte", "variable_length"
]
How a data type's values are stored: in single bytes, in several bytes at a time, or each in as many as it needs.
A number of several bytes is stored in a byte order, which the bytes
codec's endian says. A value made of single bytes -- a uint8, or a
struct of int8 fields -- has no byte order, and a value whose size
varies takes a codec of its own.
StorageTransformerField
module-attribute
¶
StorageTransformerField = TypeAliasType(
"StorageTransformerField", ZarrV3MetadataFieldJSON
)
A member holding a storage transformer: a document's storage_transformers is tuple[StorageTransformerField, ...].
AcceptedField
dataclass
¶
Bases: Generic[D]
A field a definition in scope read: the name it is written with, the definition, and the configuration it allowed.
A problem with the envelope around it -- a stray member, a
must_understand of false -- is reported with the field and leaves
it read; so is a problem of a field its configuration holds, which is
that field's own, as nested says. Two fields are equal when they
read the same, however each was spelled, as field_key compares
them: "bytes" and {"name": "bytes"} are one field, and so are a
blosc with and without the typesize that noshuffle ignores, which
the definition's canonical folds. Equal fields hash alike.
Source code in src/zarr_metadata/v3/_definition.py
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 | |
configuration
instance-attribute
¶
The configuration, type-checked and allowed by the rules; for raw bits, what the name carries.
Each field it holds is written as a document writes it, as that
field's to_json writes it, so the configuration says what was read
however it was spelled: a shard's "crc32c" and {"name": "crc32c"}
are one index codec.
json
instance-attribute
¶
json: JSONValue
The field as written, refined: arrays as tuples; it takes no part in equality.
name
instance-attribute
¶
name: str
The name it is written with: "r16", though its definition is filed under r*.
nested
class-attribute
instance-attribute
¶
nested: Nested = dataclasses.field(
default_factory=_nothing_nested
)
The fields the configuration holds, each as the scope read it, by where it sits in the configuration.
A struct's field types at ("fields", 0, "data_type"), a shard's
codecs at ("codecs", 0): what a definition's functions consult about
the fields inside its own.
read_as
class-attribute
instance-attribute
¶
read_as: type[Definition[Any]] = dataclasses.field(
init=False, repr=False
)
The kind of metadata it was read as: its definition's.
to_json ¶
to_json() -> JSONValue
The field as a document writes it, for every reader: its configuration as read, sharing nothing with the field.
The envelope takes the fewest words every reader takes: a data type
with nothing to configure is its bare name, as core data types have
been written since Zarr v3.0; any other field is an object,
{"name": ...}, since a Zarr v3.0 reader takes no bare name in
codecs
(https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L585-L592).
A name that carries its configuration, as raw bits' does, is
written alone.
Source code in src/zarr_metadata/v3/_definition.py
Chunk
dataclass
¶
What a codec is handed: chunks of some lengths along each axis, of a data type.
What nothing says is None: the lengths along an axis the grid does not
say, and every part of the chunk handed on by a codec that says nothing
of what it hands on. A data type field the scope did not read is held
as written, and says nothing of the values either. A codec's chunk
rules judge what is known and leave the rest, so a chunk nothing is
known of, Chunk(), is refused nothing.
Source code in src/zarr_metadata/v3/_definition.py
data_type
class-attribute
instance-attribute
¶
data_type: ResolvedField[DataTypeDefinition[Any]] | None = (
None
)
The data type field of the values, as a scope read it; None when no field says what they are: a document naming none, which its reading holds as UNSET, hands the pipeline a chunk of no known type.
ChunkGridDefinition
dataclass
¶
Bases: Definition[C]
A chunk grid, and the arrays it fits.
shape_rules is what the spec disallows in a grid of this
configuration over an array of a given shape: a dimension with no
chunk length, chunks that fall short of one. It is handed the
configuration, the fields it holds as the scope read them, and the
shape, and locates its problems in the configuration. A grid that
says nothing of the shape fits every one.
chunk_lengths is what the first codec of the array's pipeline is
handed: the lengths the grid's chunks take along each axis of an
array of a shape it fits -- one for each axis of a regular grid, every
length a rectilinear grid lists. It is asked only of a grid its shape
rules accept. A grid that says nothing of it leaves the lengths along
every axis unknown.
Source code in src/zarr_metadata/v3/_definition.py
chunk_lengths
class-attribute
instance-attribute
¶
The lengths its chunks take along each axis of an array of a shape it fits, None where unknown.
field_aliases
class-attribute
¶
field_aliases: tuple[TypeAliasType, ...] = (ChunkGridField,)
The field aliases a configuration member holding a field of this kind is annotated with: CodecField and StaticCodecField for a codec.
ChunkKeyEncodingDefinition
dataclass
¶
Bases: Definition[C]
A chunk key encoding.
Source code in src/zarr_metadata/v3/_definition.py
field_aliases
class-attribute
¶
field_aliases: tuple[TypeAliasType, ...] = (
ChunkKeyEncodingField,
)
The field aliases a configuration member holding a field of this kind is annotated with: CodecField and StaticCodecField for a codec.
CodecDefinition
dataclass
¶
Bases: Definition[C]
A codec: what it does to what it is handed, and whether the size of what it gives out is static.
A codec handed an array -- array -> array, array -> bytes -- says what
the spec disallows in it handed a Chunk: chunk_rules, handed the
configuration, the fields it holds as the scope read them, and the
chunk, and locating its problems in the configuration -- a bytes
codec without endian, handed a multi-byte data type. An array ->
array codec also says what it hands on: transition, the chunk the
next codec is handed, given the one it is handed -- transpose
permutes the axes, cast_value changes the data type. The two are the
spec's pair: a codec computes what it gives from the shape and data
type it is handed, and "If the decoded_representation_type is not
supported, this algorithm must fail with an error"
(https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L987-L994).
The transition is asked of every chunk the codec is handed, whatever
its chunk rules found, so it gives only what holds either way: a
transpose whose order has another number of axes hands on lengths
nothing is known of. A codec that says nothing of what it hands on
hands the next a chunk nothing is known of.
A codec that holds pipelines of its own says what each is handed:
pipelines, by the member of its configuration that holds each, the
chunk its first codec is handed, given the chunk the codec is handed
-- a shard's inner codecs are handed its inner chunks, and its index
codecs the shard index. Like the transition, it is asked whatever the
chunk rules found, and gives only what holds either way. A function
no codec of its kind is asked -- the chunk rules of a bytes -> bytes
codec, which is handed bytes -- is refused.
Source code in src/zarr_metadata/v3/_definition.py
chunk_rules
class-attribute
instance-attribute
¶
chunk_rules: Callable[
[C, Nested, Chunk], Iterable[ValidationProblem]
] = no_rules
What the spec disallows in this codec handed a chunk, located in the configuration.
field_aliases
class-attribute
¶
field_aliases: tuple[TypeAliasType, ...] = (
CodecField,
StaticCodecField,
)
The field aliases a configuration member holding a field of this kind is annotated with: CodecField and StaticCodecField for a codec.
pipelines
class-attribute
instance-attribute
¶
The pipelines it holds, by the member of its configuration that holds each, and the chunk each is handed.
Conflict
dataclass
¶
One place two readings of a name disagree: the key, what one claimed, what the other found, and where in a document when known.
Source code in src/zarr_metadata/v3/_scope.py
Context
dataclass
¶
The definitions in scope while metadata is read.
A value with no reading of its own: resolve reads a field in it,
and claimant is the one question it answers, which definition a
name belongs to. Built from definitions with Context.of, extended
with more by extended_with; two scopes are equal when they file the
same definitions, and equal scopes hash alike.
Source code in src/zarr_metadata/v3/_registry.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
claimant ¶
The definition of kind in scope that reads name, a name a document writes; None if none does.
The one filed under the name spelled reads it as: itself, but
for raw bits, r16 read by the definition of r*.
Source code in src/zarr_metadata/v3/_registry.py
definitions ¶
definitions() -> tuple[Definition[Any], ...]
disagreements ¶
disagreements(claims: Claims) -> Disagreements
Where this scope reads claims, a reading's, otherwise: what it would gain, and what it conflicts with, as Disagreements says.
A claim is keyed by the name its definition is filed under -- raw
bits under r* -- so it is looked up as filed, not as a document
writes it.
Source code in src/zarr_metadata/v3/_registry.py
extended_with ¶
extended_with(*definitions: Definition[Any]) -> Context
This scope, plus definitions of your own.
A name already filed under the same kind is taken over by what is
passed here, which is how a reader substitutes its own reading of a
codec the package already defines -- or of raw bits, by defining
r*.
Source code in src/zarr_metadata/v3/_registry.py
joined
classmethod
¶
The least scope that files everything each of contexts files: their join.
ScopeConflictError when two of them file different definitions
under one name of one kind; extended_with is for taking a name
over on purpose.
Source code in src/zarr_metadata/v3/_registry.py
of
classmethod
¶
of(*definitions: Definition[Any]) -> Context
A scope of exactly these definitions; a later one takes a name over from an earlier.
TypeError for a definition of no kind, which no position in a
document could hold.
Source code in src/zarr_metadata/v3/_registry.py
DataTypeDefinition
dataclass
¶
Bases: WithFillValue[C]
A data type, and the fill value an array of it takes.
fill_value is the JSON shape of a fill value -- Int8FillValue, an
annotation the checker reads as it reads a configuration's members,
its range among it -- and fill_value_rules is what the spec
disallows in a fill value of that shape that the type cannot say: a
hex string of another width.
The rules are handed the configuration, the fields it holds as the
scope read them (a struct's field types), and the typed fill value. A
data type that says nothing of its fill value takes any JSON.
fill_value_canonical spells a fill value that has no problem --
well typed, and allowed by the rules -- in the one spelling its value
has, so two fill values are one value of the type exactly when their
canonical spellings are written alike: "NaN" and "0x7fc00000" are
one float32, and 0.0 and -0.0 two. It is handed what the rules
are handed. A data type that says nothing of it spells each of its
values one way: as written.
storage says how its values are stored -- in single bytes, in
several bytes at a time, or each in as many as it needs -- which is
what the bytes codec asks of the data type it is handed: an
endian, for numbers of several bytes. A struct's is its fields', so
it is handed the fields the configuration holds as the scope read
them. A data type that says nothing of it leaves it unknown.
One named as a document writes raw bits of one size -- r16 -- is
refused: that name reads as r*, so nothing would ever read it with
this definition.
Source code in src/zarr_metadata/v3/_definition.py
field_aliases
class-attribute
¶
field_aliases: tuple[TypeAliasType, ...] = (DataTypeField,)
The field aliases a configuration member holding a field of this kind is annotated with: CodecField and StaticCodecField for a codec.
storage
class-attribute
instance-attribute
¶
storage: Callable[[C, Nested], StorageClass | None] = (
unknown_storage
)
How its values are stored, given the configuration and the fields it holds; None when unknown.
carrying_name ¶
The name that carries configuration for this definition, the inverse of spelled: r16 for r* with {"bits": 16}; None when its names carry nothing.
envelope_json
classmethod
¶
A field of name and configuration as a document of the format writes it, in the fewest words every reader takes: for v3, an object.
Source code in src/zarr_metadata/v3/_definition.py
spelled
classmethod
¶
How a name a document writes reads: the name its definition is filed under, and the configuration the name carries.
A name is filed as itself and carries nothing, (name, None). A
kind whose names carry configuration says otherwise: a v3 data
type r16 is filed under r* with {"bits": 16}. A name no
document writes, which only files a definition, is (None, None).
Source code in src/zarr_metadata/v3/_definition.py
Definition
dataclass
¶
Bases: Generic[C]
One extension's metadata, as JSON: its name, the TypedDict its configuration is, its rules.
configuration is the TypedDict, and so the one declaration of the
JSON: the checker is compiled from it, the static type of a checked
configuration is it, and a document's author writes to it. It reads
as the typing spec defines it -- total, Required, NotRequired,
closed and extra_items mean what they mean to a type checker --
and it says what a key it does not declare is: with closed=True, a
problem, reported and left out; with extra_items=, a key of that
type; with closed=False, anything. rules yields what the spec
disallows in a configuration of that type, as it finds each; it is
handed only a configuration that has passed the check, holding what
the TypedDict admits and nothing else, each member within the bounds
its type carries, and the fields it holds as the scope read them -- a
struct's field types -- which is nothing when no scope read it.
read_configuration is the two, for a caller holding JSON.
Each function is handed the configuration as a read-only view, no
dict: copy.deepcopy and json.dumps refuse it, and a function that
folds a spelling builds a new mapping, {**configuration} without the
member, rather than editing what it was handed.
canonical is where two spellings of the configuration that mean the
same thing are made one.
Built by hand, a definition refuses what it could not read with: a
configuration that is not a TypedDict, says nothing of the keys it
does not declare, or has a member no checker reads, which is named;
and a name or rules that are not what they say.
Source code in src/zarr_metadata/v3/_definition.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
canonical
class-attribute
instance-attribute
¶
canonical: Callable[[C], C] = unchanged
A well-typed, allowed configuration in its simplest equivalent spelling.
Only the definition's own members: a nested field is put in its own
canonical form by canonicalize, which knows where each one sits.
field_aliases
class-attribute
¶
field_aliases: tuple[TypeAliasType, ...] = ()
The field aliases a configuration member holding a field of this kind is annotated with: CodecField and StaticCodecField for a codec.
name
instance-attribute
¶
name: str
The name the metadata carries, which a scope files the definition under.
requires_configuration
property
¶
requires_configuration: bool
Whether a document must write a configuration: whether the TypedDict has a required key.
rules
class-attribute
instance-attribute
¶
rules: Callable[
[C, Nested], Iterable[ValidationProblem]
] = no_rules
What the spec disallows in a well-typed configuration and the fields it holds, located in it.
__init_subclass__ ¶
Files a subclass: kind=True declares a kind, as typing.Protocol and SQLAlchemy's __abstract__ mark a class and not its subclasses.
Source code in src/zarr_metadata/v3/_definition.py
carrying_name ¶
The name that carries configuration for this definition, the inverse of spelled: r16 for r* with {"bits": 16}; None when its names carry nothing.
Source code in src/zarr_metadata/v3/_definition.py
configuration_loc
classmethod
¶
Where the configuration of a field at loc sits: under configuration for v3; at the field for a format that writes the parameters beside the name.
Source code in src/zarr_metadata/v3/_definition.py
envelope_json
classmethod
¶
A field of name and configuration as a document of the format writes it, in the fewest words every reader takes: for v3, an object.
Source code in src/zarr_metadata/v3/_definition.py
envelope_problems
classmethod
¶
envelope_problems(value: object) -> Problems
Every reason value is not a field's envelope as the format writes one for this kind, what the configuration holds left unjudged.
Source code in src/zarr_metadata/v3/_definition.py
name_loc
classmethod
¶
Where the name of a field at loc, written as an object, sits: under name for v3.
name_problem
classmethod
¶
name_problem(
name: str, at: Loc
) -> ValidationProblem | None
The problem name, at at, is when no document of the format writes it for a field of this kind; None when one may: for Zarr v3, when the spec gives an extension such a name.
Source code in src/zarr_metadata/v3/_definition.py
named_configuration
classmethod
¶
value, a field as a document of the format writes it, split into (name, configuration, problems), as the module's named_configuration splits a v3 field.
Source code in src/zarr_metadata/v3/_definition.py
read_configuration ¶
value type-checked, then judged by the rules: the configuration if it holds, and every problem.
The rules are asked only of a configuration that type-checked,
its bounds kept, and whose nested fields are well formed, holding
what its TypedDict admits and nothing else, so a caller holding
JSON never reaches a rule with a member of the wrong type, out of
its bounds, or one the type says cannot be there. No scope reads the fields it holds, so the rules see
none of them read, and a rule about one -- a struct's field of a
type whose values vary in size -- finds nothing to judge: resolve
reads the field in a scope, and asks every rule.
Source code in src/zarr_metadata/v3/_definition.py
spelled
classmethod
¶
How a name a document writes reads: the name its definition is filed under, and the configuration the name carries.
A name is filed as itself and carries nothing, (name, None). A
kind whose names carry configuration says otherwise: a v3 data
type r16 is filed under r* with {"bits": 16}. A name no
document writes, which only files a definition, is (None, None).
Source code in src/zarr_metadata/v3/_definition.py
well_named
classmethod
¶
Whether a document of the format may write name for a field of this kind, as name_problem says.
Disagreements
dataclass
¶
Where a scope reads a reading's claims otherwise: the names it would gain a meaning for, and those it conflicts with, a lost meaning among them.
Source code in src/zarr_metadata/v3/_scope.py
EmptyConfiguration ¶
Bases: TypedDict
The configuration of a definition with nothing to configure: its field is written with its name alone.
MetadataValidationError ¶
Bases: ValueError
Raised when a value fails validation, by the entry points that raise rather than report.
Carries every problem found (not just the first) in .problems, as an
immutable tuple: a raised error is a finished report, and a caller
inspecting it must not be able to edit the record.
Source code in src/zarr_metadata/_json.py
RefusedField
dataclass
¶
Bases: Generic[D]
A field that could not be read -- not a field at all, not JSON, or refused by the definition that claims its name -- as its problems say.
Source code in src/zarr_metadata/v3/_definition.py
definition
class-attribute
instance-attribute
¶
The definition that claims its name and refused it; None when nothing in scope claims it, or it names none.
json
instance-attribute
¶
The field as written, refined: arrays as tuples; UNSET when it is not JSON, which no document holds.
nested
class-attribute
instance-attribute
¶
nested: Nested = dataclasses.field(
default_factory=_nothing_nested
)
The fields its configuration holds, each as the scope read it; empty when its configuration was not checked against its TypedDict.
ScopeConflictError ¶
Bases: ValueError
Raised where two scopes, or a scope and a reading, give one name two meanings, or one would lose a meaning the other has.
Carries every conflict in .conflicts, as MetadataValidationError
carries every problem.
Source code in src/zarr_metadata/v3/_scope.py
Stage
dataclass
¶
One codec of a pipeline, and the chunk it is handed.
Source code in src/zarr_metadata/v3/_pipeline.py
codec
instance-attribute
¶
codec: ResolvedField[CodecDefinition[Any]]
The codec, as the scope read it.
StorageTransformerDefinition
dataclass
¶
Bases: Definition[C]
A storage transformer.
Source code in src/zarr_metadata/v3/_definition.py
field_aliases
class-attribute
¶
field_aliases: tuple[TypeAliasType, ...] = (
StorageTransformerField,
)
The field aliases a configuration member holding a field of this kind is annotated with: CodecField and StaticCodecField for a codec.
UnclaimedField
dataclass
¶
A field nothing in scope claims: an extension the scope leaves unjudged, which is what keeps the format open.
Equal to another when it is written with the same name and
configuration, however each was spelled: nothing in scope interprets
its configuration, so it compares as JSON text, as field_key says.
Source code in src/zarr_metadata/v3/_definition.py
configuration
class-attribute
instance-attribute
¶
configuration: Mapping[str, JSONValue] = dataclasses.field(
init=False
)
The configuration as written, which nothing judged; empty when none is written.
json
instance-attribute
¶
json: JSONValue
The field as written, refined: arrays as tuples; it takes no part in equality.
nested
property
¶
nested: Nested
The fields its configuration holds as the scope read them: none, since nothing read its configuration.
read_as
instance-attribute
¶
read_as: type[Definition[Any]]
The kind of metadata it was read as: what a definition that claimed it would be.
ValidationProblem
dataclass
¶
A single problem found in a value: where it is, what is wrong, what kind of wrong, and the data the message is made of.
loc is the path from the root of what was judged to the offending
value, e.g. ("codecs", 0, "name") in a document, and an empty loc
refers to that root.
kind classifies the failure mode for programmatic dispatch; message
is the human-readable description.
input and ctx are what the message says, as data, as pydantic's
ErrorDetails and zod's issues carry theirs. input is the JSON found
at loc -- 12, for a gzip level of 12 -- and UNSET where nothing
is there, as zod has it for a key that is missing (pydantic gives the
object missing it), or where what is there is not JSON, which the
message shows. It is the object the caller handed in, as pydantic's
is, not a copy: a caller that changes its document afterwards changes
what input shows. ctx is what was expected, where that is more
than a type:
gt,ge,ltandle: the bounds the value's type carries, as pydantic names them --{"ge": 0, "le": 9}for a gziplevel, whose type isAnnotated[int, Interval(ge=0, le=9)]-- or a rule says.expected: the values of a closed set, as zod'svaluesholds them, in the order the message lists them -- aLiteral's,node_type's,zarr_format's.
Neither takes part in equality or the repr: a problem is the same
problem when it is found at the same place and says the same thing.
Every function that returns or raises problems fills input from the
value its caller handed it, so a rule says only where a problem is.
Source code in src/zarr_metadata/_json.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | |
canonical_fill_value ¶
canonical_fill_value(
data_type: ResolvedField[F], value: object
) -> JSONValue | UNSET
value, a fill value of data_type, a data type field a scope read, in the one spelling its value has; UNSET when it has a problem.
As the data type's fill_value_canonical spells it, so two fill
values of a data type are one value exactly when their canonical
spellings are written alike -- the same JSON, as json.dumps writes
it, which == is not: it takes -0.0 for 0.0. A fill value
fill_value_problems finds a problem with has no canonical spelling,
as canonical_of gives a field with a problem none: UNSET, since
None is the JSON null, a fill value of a data type the scope did
not read, which spells a fill value as written.
Source code in src/zarr_metadata/v3/_definition.py
fill_value_problems ¶
fill_value_problems(
data_type: ResolvedField[F],
value: object,
loc: Loc = (),
) -> Problems
What is wrong with value as a fill value of data_type, a data type field a scope read.
value is refined to JSON first: not JSON is the first verdict,
whatever the data type. It is then checked against the JSON shape the
data type's definition declares, and judged by its fill value rules, as
read_configuration reads a configuration: a key the shape does not declare is
reported and left out, and the rules still judge the rest. The rules
see the fields the configuration holds as the scope read them: a
struct judges each field's fill value by that field's own type. A data
type the scope did not read, out of scope or invalid, leaves a JSON fill
value unjudged. loc prefixes every problem.
Source code in src/zarr_metadata/v3/_definition.py
resolve ¶
resolve(
data: object,
kind: type[D],
context: Context,
loc: Loc = (),
) -> tuple[ResolvedField[D], Problems]
data, one metadata field, read as a kind in context: what the scope made of it, and every problem.
All three steps for one field. data is refined to JSON and its
envelope judged -- an extra member, a configuration that is not an
object, a must_understand that is not a boolean or is false, each
a problem. The name is related to a definition in context; the
configuration is checked against its TypedDict and judged by its
rules; each nested field the check met is read the same way, in the
same scope, and what is wrong with one is its own, reported where it
sits, as with a document's fields. What comes back is AcceptedField by the
definition that claims the name; UnclaimedField when nothing in scope
claims it, an unmodelled extension left unjudged, which is what keeps
the format open; or RefusedField, with the problems that say why. loc
prefixes every problem. kind is one of the five kinds --
CodecDefinition, DataTypeDefinition, ChunkGridDefinition,
ChunkKeyEncodingDefinition, StorageTransformerDefinition -- with
or without type arguments; anything else is a TypeError.
Source code in src/zarr_metadata/v3/_definition.py
shown ¶
value as a problem's message shows it: as the JSON a document writes, null and [1, 2], or by its repr when it is not JSON; what the interpreter will not write, an integer of too many digits or a value nested too deep, by saying so.
Source code in src/zarr_metadata/_json.py
storage_of ¶
storage_of(
data_type: ResolvedField[DataTypeDefinition[Any]],
) -> StorageClass | None
How the values of data_type, a data type field a scope read, are stored; None when unknown.
Unknown when the scope did not read it, or its definition does not
say. Its storage is the extension author's code: what it gives is
checked to be a storage class, and an error it raises says which data
type's storage raised it.