zarr_metadata.definition
zarr_metadata.definition ¶
Reading declarations 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 declaration: 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 source 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 declaration 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 declaration alias is checked as declaration JSON.definition.read_configuration(configuration)returns the configuration as that definition reads it. It type-checks the configuration, checks the envelope of each nested declaration (an extra member, amust_understandoffalse), and then runs the rules, for one configuration:GZIP_CODEC.read_configuration({"level": 12}).-
resolve(declaration, CodecDefinition, CORE_AND_EXTENSIONS_CONTEXT)reads a whole declaration in a scope. It checks the envelope, maps the name to a definition, checks the configuration, and reads each nested declaration the same way. It returns the result of the reading and every problem. The result is one of three types.AcceptedDeclarationis a declaration read by the definition that claims its name, with the configuration it checked and allowed, and its nested declarations, each read the same way.UnclaimedDeclarationis a name that nothing in scope claims. It is not checked, which is what keeps the format open.RefusedDeclarationis a declaration with problems that explain why it was rejected. Each type has the declaration's JSON, thenameit is written with, the kind it was read as (read_as), thedefinitionthat claims its name (None forUnclaimedDeclaration), and its nested declarations as the scope read them (nested). The two types a model can hold,AcceptedDeclarationandUnclaimedDeclaration, have aconfigurationandto_json(), which returns the declaration as a document writes it. Two of them are equal when they read the same, however each was spelled.ResolvedDeclarationis the union of the three, for use withmatch.configuration_of(declaration, GZIP_CODEC)returns the configuration of a declaration thatGZIP_CODECaccepted, typed as its TypedDict (GzipCodecConfiguration), and None for any other declaration, including one thatGZIP_CODECclaimed and refused. A declaration is read as one of the kinds of its format, with or without type arguments.resolve(declaration, 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 declaration 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_CONTEXT, CodecDefinition
resolved, problems = resolve({"name": "gzip", "configuration": {"level": 12}}, CodecDefinition, CORE_AND_EXTENSIONS_CONTEXT) resolved # RefusedDeclaration(..., 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_CONTEXT) 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 declaration 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 declaration 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
declarations as the scope read them. A declaration that is read stores its nested
declarations as AcceptedDeclaration.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 declarations. 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 declarations, 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_CONTEXT, 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_CONTEXT.update(codecs=[*CORE_AND_EXTENSIONS_CONTEXT.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 declarations 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 declaration 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 declaration without problems has a simplest
equivalent spelling, and declarations are compared by it. Each nested declaration 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 declaration 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 declaration 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
declaration its format has, named like the document member that holds such
declarations: 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; an attribute 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 declaration, 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, ResolvedDeclaration[Any]]
The declarations in a configuration, each as the scope read it, keyed by location in the configuration.
ResolvedDeclaration
module-attribute
¶
ResolvedDeclaration = TypeAliasType(
"ResolvedDeclaration",
"AcceptedDeclaration[D] | UnclaimedDeclaration | RefusedDeclaration[D]",
type_params=(D,),
)
One declaration as a scope read it: accepted by the definition that claims its name, unclaimed, or refused.
AcceptedDeclaration
dataclass
¶
Bases: Generic[D]
A declaration 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 envelope member, a must_understand of
false) is reported with the declaration, and the declaration is still read. The
same applies to a problem in a nested declaration, which belongs to that
nested declaration, as nested shows. Two declarations are equal when they read
the same, however each was spelled, as compared by declaration_key. For
example, "bytes" and {"name": "bytes"} are one declaration. So are a
blosc with and without the typesize that noshuffle ignores, because
the definition's canonical removes it. Equal declarations have equal
hashes.
Source code in src/zarr_metadata/definition/_definition.py
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 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 | |
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 declaration is written as that declaration'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 declaration as written, refined (arrays as tuples). It is not used for equality.
name
instance-attribute
¶
name: str
The name the declaration 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 declarations 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 declarations 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 declaration was read as: the kind of its definition.
to_json ¶
to_json() -> JSONValue
The declaration as a document writes it, in a form every reader accepts. The configuration is as read, and the result shares no objects with the declaration.
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 declaration 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
claimed
instance-attribute
¶
claimed: Definition[Any] | None
The definition one reading or scope gives the name; None when it leaves the name unclaimed.
found
instance-attribute
¶
found: Definition[Any] | None
The definition the other gives the name; None when it gives none, so the name would lose its meaning.
Context ¶
Bases: Protocol
A scope: the definitions in scope while metadata of one Zarr format is read.
This is what reading a declaration 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 declaration its format has, and so does any other object that
provides these two. resolve reads a declaration 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 source
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 declarations are passed as the scope read them (for example, a
struct's field types). The nested declarations 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
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 368 369 370 371 372 373 374 375 376 | |
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 declaration in its own canonical form, because it knows the
location of each one.
declaration_aliases
class-attribute
¶
declaration_aliases: tuple[TypeAliasType, ...] = ()
The declaration aliases used to annotate a configuration member that holds a declaration of this kind. For a codec: ZarrV3CodecDeclaration and ZarrV3StaticCodecDeclaration.
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 declarations, 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 declarations. 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 declarations 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
declarations here, so the rules see none of them as read. A rule about a
nested declaration (for example, a struct field whose type has values of
varying size) finds nothing to check. resolve reads the declaration 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 declaration is written with its name only.
RefusedDeclaration
dataclass
¶
Bases: Generic[D]
A declaration that could not be read: it is not a declaration 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 declaration; None when nothing in scope claims it, or when the declaration has no name.
json
instance-attribute
¶
The declaration 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 declaration is written with; None when it has no name.
nested
class-attribute
instance-attribute
¶
nested: Nested = dataclasses.field(
default_factory=_nothing_nested
)
The nested declarations, 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 declaration 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
UnclaimedDeclaration
dataclass
¶
A declaration that nothing in scope claims: an extension that the scope does not check. Accepting such declarations is what keeps the format open.
It is equal to another unclaimed declaration 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 declaration, so it is compared as JSON text,
as declaration_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 declaration. Always None, because no definition read it.
json
instance-attribute
¶
json: JSONValue
The declaration as written, refined (arrays as tuples). It is not used for equality.
nested
property
¶
nested: Nested
The nested declarations 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 declaration 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 declarations 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: ResolvedDeclaration[F], value: object
) -> JSONValue | UNSET
value (a fill value of data_type, a data type declaration 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 declaration 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: ResolvedDeclaration[Any],
problems: Sequence[ValidationProblem],
) -> JSONValue | None
resolved (a declaration a scope read) in its simplest equivalent spelling, as canonicalize writes it; None when it has a problem.
problems are the declaration's problems, as returned by resolve, or as
with_problems returns them for each declaration of a reading. Only a declaration
with no problems has a simplest spelling. A simpler spelling of a
declaration 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
declaration again, and is plain JSON that shares nothing with the declaration.
Source code in src/zarr_metadata/definition/_definition.py
configuration_of ¶
configuration_of(
resolved: ResolvedDeclaration[Any],
definition: Definition[C],
) -> C | None
The configuration of resolved, typed as definition declares it, if definition accepted it.
AcceptedDeclaration stores its configuration as a read-only mapping. When
called with the definition that read the declaration, or one equal to it
(as with a pickled declaration), this returns a copy of it, a plain dict
typed as the TypedDict, which shares nothing with the declaration. Returns
None for any other declaration: 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: ResolvedDeclaration[F],
value: object,
loc: Loc = (),
) -> Problems
The problems with value as a fill value of data_type (a data type declaration 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 declarations 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[ResolvedDeclaration[D], Problems]
Read data (one declaration) as a kind in context. Return the result of the reading, and every problem.
This runs all three steps for one declaration. data is refined to JSON and
its envelope is checked. An extra envelope 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 declaration found by the check is read the same way, in the same
scope. A problem in a nested declaration belongs to that declaration and is
reported at its location, as with a document's declarations. The result is
an AcceptedDeclaration read by the definition that claims the name; an
UnclaimedDeclaration when nothing in scope claims the name (an extension
with no model, not checked, which is what keeps the format open); or a
RefusedDeclaration, 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(
declarations: Iterable[
tuple[Loc, ResolvedDeclaration[Any]]
],
problems: Sequence[ValidationProblem],
) -> Iterator[
tuple[Loc, ResolvedDeclaration[Any], Problems]
]
Yield each of declarations with its location and the problems in problems located inside it, including inside its nested declarations.
declarations and problems must come from the same read: a reading's
declarations() and problems, or declarations_of a declaration and the problems that
resolve returned with it. A declaration'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
declaration 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 declaration comes before
its nested declarations, in the order of declarations_of, so the last declaration that
lists a problem is the innermost declaration containing it. A problem
outside every declaration (with the fill value or the shape) is not listed
under any declaration.