zarr_metadata.definition
zarr_metadata.definition ¶
Reading metadata fields against their definitions, in either Zarr format.
Every extension point of a Zarr document (a v3 codec, data type, chunk grid, chunk key encoding or storage transformer; a v2 dtype or numcodecs codec) is a metadata field: a name, and a configuration whose JSON the extension defines. A definition declares what that JSON is and what it may contain. A definition is a value, not a class to subclass. It has:
name, the name the metadata uses;configuration, the TypedDict for the configuration's JSON. This is the single declaration of the JSON: the type checker is compiled from it, and a checked configuration has it as its static type. It is interpreted 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. A member's type carries its bounds, in the same way pydantic reads them: a gziplevelisAnnotated[int, Interval(ge=0, le=9)];rules, a function over that TypedDict that yields the spec violations a type cannot express (such as constraints between members), located in the configuration.
The kind of metadata a definition defines is its type. The v3 kinds are
in zarr_metadata.v3.definition, and the v2 kinds are in
zarr_metadata.v2.definition. This module holds what both formats share:
Definition and WithFillValue, which the kinds derive from; resolve,
which reads a field in a scope; the three results of a reading; and
Context, the protocol of a scope.
Reading JSON. There are three steps. Each feeds the next, and each can be used on its own by a caller that has only JSON:
check(value, SomeTypedDict), fromzarr_metadata.typed_json, type-checks JSON against a TypedDict and needs nothing else. It returns a value of the TypedDict or None, and every problem, each with a location. The value contains only what the TypedDict admits. A member typed with a field alias is checked as metadata field JSON.definition.read_configuration(configuration)returns the configuration as that definition reads it. It type-checks the configuration, checks the envelope of each nested field (an extra member, amust_understandoffalse), and then runs the rules, for one configuration:GZIP_CODEC.read_configuration({"level": 12}).-
resolve(field, CodecDefinition, CORE_AND_EXTENSIONS)reads a whole field in a scope. It checks the envelope, maps the name to a definition, checks the configuration, and reads each nested field the same way. It returns the result of the reading and every problem. The result is one of three types.AcceptedFieldis a field read by the definition that claims its name, with the configuration it checked and allowed, and its nested fields, each read the same way.UnclaimedFieldis a name that nothing in scope claims. It is not checked, which is what keeps the format open.RefusedFieldis a field with problems that explain why it was rejected. Each type has the field's JSON, thenameit is written with, the kind it was read as (read_as), thedefinitionthat claims its name (None forUnclaimedField), and its nested fields as the scope read them (nested). The two types a model can hold,AcceptedFieldandUnclaimedField, have aconfigurationandto_json(), which returns the field as a document writes it. Two of them are equal when they read the same, however each was spelled.ResolvedFieldis the union of the three, for use withmatch.configuration_of(field, GZIP_CODEC)returns the configuration of a field thatGZIP_CODECaccepted, typed as its TypedDict (GzipCodecConfiguration), and None for any other field, including one thatGZIP_CODECclaimed and refused. A field is read as one of the kinds of its format, with or without type arguments.resolve(field, Definition, context)raisesTypeError, because nothing is filed underDefinition. A whole v3 array document is read byread_array_metadata_v3, inzarr_metadata.model. Its reading returns each field with its location in the document.from zarr_metadata.definition import resolve from zarr_metadata.v3.codec.gzip import GZIP_CODEC from zarr_metadata.v3.definition import CORE_AND_EXTENSIONS, CodecDefinition
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 is reported, and the configuration
is checked without it. So a consumer that tolerates unknown keys can
filter problems by kind and use the result. The field is valid only
when there are no problems at all. Each problem stores the content of
its message as data, as pydantic's errors and zod's issues do: input
is the value found at loc (the 12 above), and ctx is what was
expected, when that is more than a type: the bounds
{"ge": 0, "le": 9}, or the values of a closed set.
Writing an extension. Write a TypedDict that declares the
configuration's JSON, including bounds; a function for the rules a type
cannot express; and a definition. Then build a scope that contains the
definition. The TypedDict must be a typing_extensions.TypedDict,
because closed and extra_items come from PEP 728, and
typing.TypedDict does not accept them on the Python versions this
package supports. The rules receive the configuration and its nested
fields as the scope read them. A field that is read stores its nested
fields as AcceptedField.nested, a Nested mapping keyed by location,
so a struct's rules can access its field types. read_configuration
reads in no scope, so it passes no nested fields. A rule's message
should show a value as JSON, as the package's own messages do, with
zarr_metadata.problems.shown: null, [1, 2], "C". A rule reports the location of a
problem. The value at that location becomes the problem's input
automatically. Define each function at module top level. A model holds
the definitions that read its fields, so the model can be pickled, and
compares equal after loading, only if its definitions can. A lambda or
a closure cannot be pickled, and a functools.partial can be pickled
but does not compare equal to its loaded copy.
from collections.abc import Iterator
from typing import Annotated, NotRequired
from annotated_types import Ge
from typing_extensions import TypedDict
from zarr_metadata import ValidationProblem
from zarr_metadata.definition import Nested
from zarr_metadata.v3.definition import CORE_AND_EXTENSIONS, CodecDefinition
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,
)
CONTEXT = CORE_AND_EXTENSIONS.update(codecs=[*CORE_AND_EXTENSIONS.codecs.values(), ACME_LZ4])
The TypedDict declares how keys it does not declare are handled. With
closed=True, such a key is a problem, as above. With extra_items=,
such a key must hold that type. With closed=False, anything is
allowed. A TypedDict that declares none of these is open by default and
would accept a misspelled key silently, so a definition rejects it. Its
members must be JSON shapes: 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, written with annotated-types and read the way
pydantic reads them: Gt, Ge, Lt, Le and Interval, at most one
from each side, at any depth. For example,
tuple[Annotated[int, Ge(1)], ...] bounds each element. A value out of
bounds is an invalid_value problem. Its message says what the type
admits ("expected an integer >= 1, got 0"), and its ctx holds the
bounds. The rules run only on a configuration within its bounds, so a
rule can rely on them, as pydantic's after-validators and zod's
refinements do. Until a value out of bounds is fixed, it is the only
problem reported for the configuration. Annotated may also carry a
note, as a string or a Doc. Any other metadata (a MinLen, a
Predicate, pydantic's Field) is rejected when the definition is
built, because a type whose constraint the checker does not enforce
would give false information.
A data type also declares its fill value. fill_value is the JSON
shape of a fill value, as an annotation the checker reads (for example
Int8FillValue, whose type carries the range). fill_value_rules is a
function that yields the spec violations in a fill value of that shape
that the type cannot express, such as a hex string of the wrong width,
or a number of byte values that does not match the size. The rules
receive the configuration, the nested fields as the scope read them,
and the typed fill value, so a struct checks each field's fill value
against that field's own type.
fill_value_problems(data_type, value) checks a fill value against a
data type field the scope read. If nothing in scope claims the data
type, the fill value is not checked. A data type that does not declare
its fill value accepts any JSON.
A fill value can have more than one spelling ("NaN" and "0x7fc00000"
are the same float32), so a data type declares the canonical spelling
of each value with fill_value_canonical. It receives the same
arguments as the rules, plus a fill value the rules allow. Two fill
values are the same value of the type exactly when their canonical
spellings are written the same way: the same JSON, as json.dumps
writes it. == is not the same test, because it treats -0.0 (a
distinct float32) as equal to 0.0.
canonical_fill_value(data_type, value) returns the canonical spelling,
or UNSET for a fill value with a problem. A data type that does not
declare fill_value_canonical keeps each value as written.
The simplest spelling. A field without problems has a simplest
equivalent spelling, and fields are compared by it. Each nested field is
written in its own simplest spelling, then the definition's canonical
is applied (blosc drops a typesize that noshuffle ignores, and a
rectilinear grid run-length encodes its chunk shapes). The envelope
uses the shortest form every reader accepts: a data type with nothing to
configure is its bare name, and any other field is an object,
{"name": ...}, because a Zarr v3.0 reader does not accept a short-hand
name in codecs. Raw bits write their size back into the name, in
decimal, so r008 becomes r8. A field with any problem, including an
unknown key, has no simplest spelling, because a simpler spelling would
erase what its author wrote. The result of canonical is checked again,
and an invalid result raises ValueError, which indicates a bug in the
definition. canonical_fill_value writes a fill value in the same way,
using the data type that read it.
Scopes as values. A scope reads documents of one Zarr format.
ZarrV3Context, in zarr_metadata.v3.definition, and ZarrV2Context,
in zarr_metadata.v2.definition, are the scopes of each format. They
are separate classes, and Context is the protocol both implement: the
Zarr format a scope reads, and claimant, which says which definition
of a kind reads a name. That is all resolve needs, so any object that
provides these two can be a scope. A scope has one slot for each kind of
field its format has, named like the document member that holds such
fields: ZarrV3Context(codecs=[...], data_types=[...]) builds one, and
context.codecs["gzip"] reads one slot. A type checker rejects a
definition in the wrong slot, and a scope of one format given to a
reader of the other. The constructor and the reader also reject them at
run time, with TypeError. Two scopes are equal when they are of one
format and file the same definitions, and equal scopes have equal
hashes. context.union(*others) returns the smallest scope that
contains each of them, or raises a ScopeConflictError naming each name
filed with two different definitions. update replaces the
definitions in the slots it is given and keeps the others, and takes a
slot as definitions or as a mapping by name, so
context.update(codecs={**context.codecs, "gzip": MY_GZIP}) replaces
one definition on purpose. A model's refined_in moves it to a scope
that claims more names and contradicts nothing. refines orders two
models by how much they know. A name that was unclaimed and is now read
by a definition is a gain. The reverse is a loss. One name read by two
different definitions is a conflict.
A definition checks itself when it is built. Each of these raises a
TypeError that says what is wrong: a configuration that is not a
TypedDict, that does not declare how undeclared keys are handled, or
that has a member no checker reads (the error names the TypedDict that
contains 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 a function; a data type's fill_value that no
checker reads, or that contains a metadata field, which a value of the
data type never does; a codec kind that is not one of the three, or a
size that is not "static" or "dynamic"; a function that is never
called for a codec of its kind (chunk rules or pipelines of a
bytes -> bytes codec, which receives bytes, or a transition of a codec
that outputs bytes); a data type named the way raw bits of one size are
written; an extra field named after a member the spec defines, whose
finite is not a boolean, or whose configuration does not declare
must_understand. Building a definition files it nowhere: only a scope
built from it does.
ClaimKey
module-attribute
¶
A kind and the name a definition is filed under. A scope's claimant looks up definitions by this key.
Nested
module-attribute
¶
Nested: TypeAlias = Mapping[Loc, ResolvedField[Any]]
The fields in a configuration, each as the scope read it, keyed by location in the configuration.
ResolvedField
module-attribute
¶
ResolvedField = TypeAliasType(
"ResolvedField",
"AcceptedField[D] | UnclaimedField | RefusedField[D]",
type_params=(D,),
)
One metadata field as a scope read it: accepted by the definition that claims its name, unclaimed, or refused.
AcceptedField
dataclass
¶
Bases: Generic[D]
A field that a definition in scope read: the name it is written with, the definition, and the configuration the definition allowed.
A problem with the envelope (an extra member, a must_understand of
false) is reported with the field, and the field is still read. The
same applies to a problem in a nested field, which belongs to that
nested field, as nested shows. Two fields are equal when they read
the same, however each was spelled, as compared by field_key. For
example, "bytes" and {"name": "bytes"} are one field. So are a
blosc with and without the typesize that noshuffle ignores, because
the definition's canonical removes it. Equal fields have equal
hashes.
Source code in src/zarr_metadata/definition/_definition.py
864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 | |
configuration
instance-attribute
¶
The configuration, type-checked and allowed by the rules. For raw bits, this is the configuration encoded in the name.
Each nested field is written as that field's to_json writes it, so
the configuration shows what was read, however it was spelled. For
example, a shard's "crc32c" and {"name": "crc32c"} are the same
index codec.
json
instance-attribute
¶
json: JSONValue
The field as written, refined (arrays as tuples). It is not used for equality.
name
instance-attribute
¶
name: str
The name the field is written with, for example "r16", even though its definition is filed under r*.
nested
class-attribute
instance-attribute
¶
nested: Nested = dataclasses.field(
default_factory=_nothing_nested
)
The fields in the configuration, each as the scope read it, keyed by location in the configuration.
For example, a struct's field types are at ("fields", 0, "data_type")
and a shard's codecs are at ("codecs", 0). A definition's functions
use this to inspect the fields inside its own configuration.
read_as
class-attribute
instance-attribute
¶
read_as: type[Definition[Any]] = dataclasses.field(
init=False, repr=False
)
The kind of metadata the field was read as: the kind of its definition.
to_json ¶
to_json() -> JSONValue
The field as a document writes it, in a form every reader accepts. The configuration is as read, and the result shares no objects with the field.
The envelope uses the shortest form every reader accepts. A data
type with nothing to configure is written as its bare name, as core
data types have been written since Zarr v3.0. Any other field is an
object, {"name": ...}, because a Zarr v3.0 reader does not accept a
bare name in codecs
(https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L585-L592).
A name that encodes its configuration, as the raw bits name does, is
written alone.
Source code in src/zarr_metadata/definition/_definition.py
Conflict
dataclass
¶
One place where two readings of a name disagree: the key, the definition one reading claimed, the definition the other found, and the location in the document when known.
Source code in src/zarr_metadata/definition/_scope.py
Context ¶
Bases: Protocol
A scope: the definitions in scope while metadata of one Zarr format is read.
This is what reading a field needs from a scope: the Zarr format of the
documents it reads, and which definition of a kind claims a name.
ZarrV3Context and ZarrV2Context implement it, each with one slot
per kind of field its format has, and so does any other object that
provides these two. resolve reads a field in any Context.
Source code in src/zarr_metadata/definition/_definition.py
Definition
dataclass
¶
Bases: Generic[C]
One extension's metadata as JSON: its name, its configuration TypedDict, and its rules.
configuration is the TypedDict, and so it is the single declaration
of the JSON. The checker is compiled from it, it is the static type of
a checked configuration, and a document's author writes to it. It is
interpreted as the typing spec defines a TypedDict: total,
Required, NotRequired, closed and extra_items mean what they
mean to a type checker. It also declares how keys it does not declare
are handled. With closed=True, such a key is a problem, reported and
left out. With extra_items=, such a key must have that type. With
closed=False, anything is allowed. rules yields each violation of
the spec it finds in a configuration of that type. It receives only a
configuration that has passed the check: it contains only what the
TypedDict admits, each member is within the bounds its type declares,
and the nested fields are passed as the scope read them (for example, a
struct's field types). The nested fields are empty when no scope read
the configuration. read_configuration runs both steps, for a caller
that has JSON.
Each function receives the configuration as a read-only view, not a
dict. copy.deepcopy and json.dumps reject this view. A function
that simplifies a spelling must build a new mapping, such as
{**configuration} without the member, instead of editing its input.
canonical is where two spellings of the configuration that mean the
same thing are reduced to one.
A definition built by hand rejects inputs it could not read with: a
configuration that is not a TypedDict, that does not declare how
undeclared keys are handled, or that has a member no checker reads
(the error names the member); and a name or rules of the wrong type.
Source code in src/zarr_metadata/definition/_definition.py
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 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 360 361 362 363 364 365 366 367 | |
canonical
class-attribute
instance-attribute
¶
canonical: Callable[[C], C] = unchanged
A well-typed, allowed configuration in its simplest equivalent spelling.
This handles only the definition's own members. canonicalize puts
each nested field in its own canonical form, because it knows the
location of each one.
field_aliases
class-attribute
¶
field_aliases: tuple[TypeAliasType, ...] = ()
The field aliases used to annotate a configuration member that holds a field of this kind. For a codec: ZarrV3CodecField and ZarrV3StaticCodecField.
label
class-attribute
¶
label: str = 'definition'
The name of the kind in error messages, for example "codec".
name
instance-attribute
¶
name: str
The name the metadata uses. A scope files the definition under this name.
requires_configuration
property
¶
requires_configuration: bool
Whether a document must include a configuration, meaning the TypedDict has a required key.
rules
class-attribute
instance-attribute
¶
rules: Callable[
[C, Nested], Iterable[ValidationProblem]
] = no_rules
The spec violations in a well-typed configuration and its nested fields, located in the configuration.
__init_subclass__ ¶
__init_subclass__(
*,
kind: bool = False,
format: Literal[2, 3] | None = None,
**kwargs: object,
) -> None
Register a subclass. kind=True declares a kind, with the Zarr format of the documents that contain its fields. This marks the class but not its subclasses, as typing.Protocol and SQLAlchemy's __abstract__ do.
Source code in src/zarr_metadata/definition/_definition.py
read_configuration ¶
Type-check value, then check it with the rules. Return the configuration if it is valid, and every problem.
The rules run only on a configuration that passed the type check,
including its bounds, and whose nested fields are well formed. Such
a configuration contains only what its TypedDict admits. So a rule
never receives a member of the wrong type, a member out of bounds,
or a member the type does not allow. No scope reads the nested
fields here, so the rules see none of them as read. A rule about a
nested field (for example, a struct field whose type has values of
varying size) finds nothing to check. resolve reads the field in a
scope and runs every rule.
Source code in src/zarr_metadata/definition/_definition.py
EmptyConfiguration ¶
Bases: TypedDict
The configuration of a definition with nothing to configure. Its field is written with its name only.
RefusedField
dataclass
¶
Bases: Generic[D]
A field that could not be read: it is not a field at all, not JSON, or rejected by the definition that claims its name. Its problems explain why.
Source code in src/zarr_metadata/definition/_definition.py
definition
class-attribute
instance-attribute
¶
The definition that claims the name and rejected the field; None when nothing in scope claims it, or when the field has no name.
json
instance-attribute
¶
The field as written, refined (arrays as tuples). UNSET when it is not JSON, which no document can contain.
name
instance-attribute
¶
name: str | None
The name the field is written with; None when it has no name.
nested
class-attribute
instance-attribute
¶
nested: Nested = dataclasses.field(
default_factory=_nothing_nested
)
The nested fields, each as the scope read it; empty when the configuration was not checked against its TypedDict.
read_as
instance-attribute
¶
read_as: type[Definition[Any]]
The kind of metadata the field was read as.
ScopeConflictError ¶
Bases: ValueError
Raised when two scopes, or a scope and a reading, give one name two different meanings, or when one would lose a meaning the other has.
Every conflict is stored in .conflicts, in the same way that
MetadataValidationError stores every problem.
Source code in src/zarr_metadata/definition/_scope.py
UnclaimedField
dataclass
¶
A field that nothing in scope claims: an extension that the scope does not check. Accepting such fields is what keeps the format open.
It is equal to another unclaimed field when both are of the same kind,
have the same name, and a document writes the same JSON for both.
Nothing in scope interprets the field, so it is compared as JSON text,
as field_key defines.
Source code in src/zarr_metadata/definition/_definition.py
configuration
class-attribute
instance-attribute
¶
configuration: Mapping[str, JSONValue] = dataclasses.field(
init=False
)
The configuration as written, not checked; empty when no configuration is written.
definition
property
¶
The definition that read the field. Always None, because no definition read it.
json
instance-attribute
¶
json: JSONValue
The field as written, refined (arrays as tuples). It is not used for equality.
nested
property
¶
nested: Nested
The nested fields as the scope read them. Always empty, because nothing read the configuration.
read_as
instance-attribute
¶
read_as: type[Definition[Any]]
The kind of metadata the field was read as: the kind a claiming definition would have.
WithFillValue
dataclass
¶
Bases: Definition[C]
A definition of a type whose arrays have a fill value: the fill value's JSON shape, the rules for it, and its canonical spelling.
This is not a kind. The data type kind of each format derives from it,
so fill_value_problems can check a fill value for either format.
fill_value is the JSON shape of a fill value, as an annotation. The
checker reads it the same way it reads a configuration's members,
including any range. fill_value_rules yields the spec violations in
a fill value of that shape that the type cannot express. It receives
the configuration, the nested fields as the scope read them, and the
typed fill value. fill_value_canonical writes a fill value without
problems in the single canonical spelling of its value. So two fill
values are the same value of the type exactly when their canonical
spellings are written the same way.
Source code in src/zarr_metadata/definition/_definition.py
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 single canonical spelling of its value; UNSET when it has a problem.
The spelling comes from the data type's fill_value_canonical. Two
fill values of a data type are the same value exactly when their
canonical spellings are written the same way: the same JSON as
json.dumps writes it. == is not the same test, because it treats
-0.0 as equal to 0.0. A fill value with a problem found by
fill_value_problems has no canonical spelling, just as canonical_of
returns none for a field with a problem. The result is UNSET and not
None, because None is the JSON null, which is a valid fill value
for a data type the scope did not read (such a data type keeps a fill
value as written).
Source code in src/zarr_metadata/definition/_definition.py
canonical_of ¶
canonical_of(
resolved: ResolvedField[Any],
problems: Sequence[ValidationProblem],
) -> JSONValue | None
resolved (a field a scope read) in its simplest equivalent spelling, as canonicalize writes it; None when it has a problem.
problems are the field's problems, as returned by resolve, or as
with_problems returns them for each field of a reading. Only a field
with no problems has a simplest spelling. A simpler spelling of a
field with a problem would erase what its author wrote (such as a key
its TypedDict does not declare), or would write something invalid.
The result is built from what the scope read, without reading the
field again, and is plain JSON that shares nothing with the field.
Source code in src/zarr_metadata/definition/_definition.py
configuration_of ¶
configuration_of(
resolved: ResolvedField[Any],
definition: Definition[C],
) -> C | None
The configuration of resolved, typed as definition declares it, if definition accepted it.
AcceptedField stores its configuration as a read-only mapping. When
called with the definition that read the field, or one equal to it
(as with a pickled field), this returns a copy of it, a plain dict
typed as the TypedDict, which shares nothing with the field. Returns
None for any other field: one that another definition read, one that
no definition claims, and one that definition claimed and refused.
Source code in src/zarr_metadata/definition/_definition.py
fill_value_problems ¶
fill_value_problems(
data_type: ResolvedField[F],
value: object,
loc: Loc = (),
) -> Problems
The problems with value as a fill value of data_type (a data type field a scope read).
value is refined to JSON first. If it is not JSON, that is reported
first, whatever the data type. It is then checked against the JSON
shape that the data type's definition declares, and by the fill value
rules, in the same way read_configuration reads a configuration: a
key the shape does not declare is reported and left out, and the rules
still check the rest. The rules see the nested fields of the
configuration as the scope read them, so a struct checks each field's
fill value against that field's own type. If the scope did not read
the data type (it is out of scope or invalid), a JSON fill value is not
checked. loc prefixes every problem.
Source code in src/zarr_metadata/definition/_definition.py
resolve ¶
resolve(
data: object,
kind: type[D],
context: Context,
loc: Loc = (),
) -> tuple[ResolvedField[D], Problems]
Read data (one metadata field) as a kind in context. Return the result of the reading, and every problem.
This runs all three steps for one field. data is refined to JSON and
its envelope is checked. An extra member, a configuration that is not
an object, and a must_understand that is not a boolean or is false
are each a problem. The name is mapped to a definition in context.
The configuration is checked against its TypedDict and by its rules.
Each nested field found by the check is read the same way, in the same
scope. A problem in a nested field belongs to that field and is
reported at its location, as with a document's fields. The result is
an AcceptedField read by the definition that claims the name; an
UnclaimedField when nothing in scope claims the name (an extension
with no model, not checked, which is what keeps the format open); or a
RefusedField, with the problems that explain why. loc prefixes
every problem. kind must be one of the kinds of a format (for v3,
those ZarrV3Kind names; for v2, those ZarrV2Kind names), with or
without type arguments, and context a scope of that format. Anything
else raises TypeError.
Source code in src/zarr_metadata/definition/_definition.py
with_problems ¶
with_problems(
fields: Iterable[tuple[Loc, ResolvedField[Any]]],
problems: Sequence[ValidationProblem],
) -> Iterator[tuple[Loc, ResolvedField[Any], Problems]]
Yield each of fields with its location and the problems in problems located inside it, including inside its nested fields.
fields and problems must come from the same read: a reading's
fields() and problems, or fields_of a field and the problems that
resolve returned with it. A field's problems include those found when
it was read, and those the document found with it in its position (its
place in the pipeline, the chunk it receives, the array's shape). So a
field with no problems is valid in that position, and canonical_of
can write it. This groups the problems at every depth, as zod's
treeifyError groups issues: a problem with a shard's inner codec
belongs to the inner codec and to the shard. Each field comes before
its nested fields, in the order of fields_of, so the last field that
lists a problem is the innermost field containing it. A problem
outside every field (with the fill value or the shape) is not listed
under any field.