Skip to content

zarr_metadata.json_schema

zarr_metadata.json_schema

JSON Schema output: a TypedDict, one Zarr v3 field, or a whole v3 zarr.json.

Each function writes JSON Schema draft 2020-12, for a validator in another language or for an editor that checks a document while it is written. A schema describes each member on its own, so it does not contain the rules that relate members to each other, and a document it accepts may still have a problem.

A TypedDict. json_schema(typed_dict) writes the rules that zarr_metadata.typed_json.check applies. The schema accepts the values that check finds no problems with. A TypedDict becomes an object with its keys, closed or open as the TypedDict declares. A bound becomes the matching JSON Schema keyword: Interval(ge=0, le=9) becomes minimum and maximum. Each TypedDict or type alias is written once in $defs, under its name. JSON Schema accepts a number with no fractional part, such as 1.0, as an integer, while check requires 1:

from zarr_metadata.json_schema import json_schema

json_schema(GzipCodecConfiguration)
# {'$schema': 'https://json-schema.org/draft/2020-12/schema',
#  'type': 'object',
#  'properties': {'level': {'type': 'integer', 'minimum': 0, 'maximum': 9}},
#  'required': ['level'], 'additionalProperties': False}

One v3 field. field_json_schema_v3(kind, context=None) writes one field read as kind in the scope context: a field that a definition in scope reads, or a name that no definition claims, with any configuration.

A whole zarr.json. node_metadata_json_schema_v3(context=None) writes a whole v3 zarr.json, array or group, as the scope reads it (CORE_AND_EXTENSIONS when none is given). Its fields are written as the scope reads them. For each definition, the schema accepts its field (its name, its configuration as its TypedDict declares it, including bounds, a must_understand of true, and its bare name when it needs no configuration). It also accepts a name that nothing in scope claims, with any configuration. A nested field is written in the same scope, and a member that accepts only codecs of static size accepts only those. The rules are not in the schema, so a field it accepts may still have a problem. Any field that resolve reads without a problem is accepted by the schema, as JSON (arrays as lists, as a parser returns them). Each configuration TypedDict and each field alias is written once, in $defs, under its name. The fill value must match its data type's fill value schema.

JSONSchema module-attribute

JSONSchema: TypeAlias = dict[str, JSONValue]

A JSON Schema as a JSON object, with arrays as lists, which is the form validators accept.

__all__ module-attribute

__all__ = [
    "JSONSchema",
    "field_json_schema_v3",
    "json_schema",
    "node_metadata_json_schema_v3",
]

field_json_schema_v3

field_json_schema_v3(
    kind: type[ZarrV3Kind],
    *,
    context: ZarrV3Context | None = None,
) -> JSONSchema

The JSON Schema of one metadata field read as kind in context (CORE_AND_EXTENSIONS when none is given): everything resolve checks, except the rules. A scope of another format raises TypeError, as it does for every v3 reader. An extra field is written as the member alone, {key: value}.

The schema accepts a field that one of the definitions in scope reads (its name, its configuration as the TypedDict declares it, an optional must_understand of true, and its bare name when it needs no configuration). It also accepts a name that no definition claims, with any configuration, which is what keeps the format open. A field inside a configuration is written the same way, in the same scope, and a member that accepts only codecs of static size accepts only those. The output is JSON Schema draft 2020-12, as json_schema writes it. The nested fields and the configuration of each definition are in $defs, under the name of the field alias or TypedDict. Constraints that only a rule enforces (a blosc typesize checked against its shuffle) are not in the schema, so a field it accepts may still have a problem.

Source code in src/zarr_metadata/v3/_json_schema.py
def field_json_schema_v3(
    kind: type[ZarrV3Kind], *, context: ZarrV3Context | None = None
) -> JSONSchema:
    """The JSON Schema of one metadata field read as `kind` in `context` (`CORE_AND_EXTENSIONS` when none is given): everything `resolve` checks, except the rules. A scope of another format raises `TypeError`, as it does for every v3 reader. An extra field is written as the member alone, `{key: value}`.

    The schema accepts a field that one of the definitions in scope reads
    (its name, its configuration as the TypedDict declares it, an optional
    `must_understand` of `true`, and its bare name when it needs no
    configuration). It also accepts a name that no definition claims, with
    any configuration, which is what keeps the format open. A field inside
    a configuration is written the same way, in the same scope, and a
    member that accepts only codecs of static size accepts only those. The
    output is JSON Schema draft 2020-12, as `json_schema` writes it. The
    nested fields and the configuration of each definition are in
    `$defs`, under the name of the field alias or TypedDict. Constraints
    that only a rule enforces (a blosc `typesize` checked against its
    `shuffle`) are not in the schema, so a field it accepts may still have
    a problem.
    """
    asked = as_kind(kind)
    if not is_v3_kind(asked):
        msg = (
            f"{asked.__name__} is a kind of another format; the JSON Schema writer writes "
            "Zarr v3 fields only"
        )
        raise TypeError(msg)
    context = scoped(context, CORE_AND_EXTENSIONS)
    schemas = Schemas(field_schemas(context))
    if issubclass(asked, (ArrayExtraFieldDefinition, GroupExtraFieldDefinition)):
        return schemas.document(_extra_field_schema(asked, context, schemas))
    return schemas.document(schemas.of(kind_field(asked)))

json_schema

json_schema(shape: type) -> JSONSchema

The JSON Schema for the JSON that check accepts as shape, a TypedDict.

The schema uses draft 2020-12 and is a JSON object, with arrays as lists and $schema first. The schema is built as follows:

  • A TypedDict becomes an object with its keys, its required keys, and what any other key may hold (nothing, for a closed TypedDict).
  • A bound becomes the matching JSON Schema keyword: Ge(0) becomes minimum.
  • A Doc becomes the description. Only a Doc produces a description; docstrings are for Python readers and are not used. This matches zod, which writes only what .describe() sets.
  • A Literal becomes its values.
  • A union becomes anyOf its branches.

Each TypedDict or type alias is written once in $defs, under its name, and referenced wherever it occurs. The exception is shape itself, which is written inline unless it refers to itself.

One difference comes from JSON Schema itself: it accepts a number with no fractional part, such as 1.0, as an integer, while check requires 1. Raises TypeError if shape is not a TypedDict, or contains a type that has no parser, in the same cases as check.

Source code in src/zarr_metadata/_typed_json.py
def json_schema(shape: type) -> JSONSchema:
    """The JSON Schema for the JSON that `check` accepts as `shape`, a TypedDict.

    The schema uses draft 2020-12 and is a JSON object, with arrays as
    lists and `$schema` first. The schema is built as follows:

    - A TypedDict becomes an object with its keys, its required keys, and
      what any other key may hold (nothing, for a closed TypedDict).
    - A bound becomes the matching JSON Schema keyword: `Ge(0)` becomes
      `minimum`.
    - A `Doc` becomes the `description`. Only a `Doc` produces a
      description; docstrings are for Python readers and are not used.
      This matches zod, which writes only what `.describe()` sets.
    - A `Literal` becomes its values.
    - A union becomes `anyOf` its branches.

    Each TypedDict or type alias is written once in `$defs`, under its
    name, and referenced wherever it occurs. The exception is `shape`
    itself, which is written inline unless it refers to itself.

    One difference comes from JSON Schema itself: it accepts a number with
    no fractional part, such as `1.0`, as an integer, while `check`
    requires `1`. Raises `TypeError` if `shape` is not a TypedDict, or
    contains a type that has no parser, in the same cases as `check`.
    """
    if not is_typeddict(shape):
        msg = f"{shape!r} is not a TypedDict"
        raise TypeError(msg)
    _checker(shape)
    schemas = Schemas()
    return schemas.document(schemas.of(shape))

node_metadata_json_schema_v3

node_metadata_json_schema_v3(
    *, context: ZarrV3Context | None = None
) -> JSONSchema

The JSON Schema of a v3 zarr.json read in context: an array document or a group document, as read_node_metadata_v3 reads it, except for the rules.

Use it in an editor that validates a zarr.json while it is written, or in a validator written in another language. The schema is JSON Schema draft 2020-12, in the form json_schema writes. Each extension point is a field as the scope context reads it: either a name that a definition in scope claims, with the configuration that definition declares, or a name that no definition claims. When the document names a data type in scope, the fill value must have the JSON shape that the data type's definition declares (for int8, an integer in [-128, 127]). In a scope that claims it, as CORE does, a group's consolidated_metadata is the envelope the core spec fixes, with any JSON as the documents it lists, because the reader does not read them, and a null value, which zarr-python 3.0 and 3.1 wrote, is refused, because the reader refuses it too. Each document is in $defs under the name of its TypedDict. ZarrV3ArrayMetadataJSON describes only an array.

A JSON Schema describes each member on its own. It does not contain the rules that relate members to each other: one dimension name per dimension of the shape, a chunk grid that fits the shape, codecs in pipeline order with each codec checked against the chunk it receives, and the checks in a definition's rules. So a document the schema accepts may still have a problem. Every JSON document that read_node_metadata_v3 reads without a problem is accepted by the schema. A validator reads JSON as a parser returns it, with arrays as lists. A model's to_json returns tuples, and a Python validator does not accept tuples as arrays.

Source code in src/zarr_metadata/v3/_json_schema.py
def node_metadata_json_schema_v3(*, context: ZarrV3Context | None = None) -> JSONSchema:
    """The JSON Schema of a v3 `zarr.json` read in `context`: an array document or a group document, as `read_node_metadata_v3` reads it, except for the rules.

    Use it in an editor that validates a `zarr.json` while it is
    written, or in a validator written in another language. The schema
    is JSON Schema draft 2020-12, in the form `json_schema` writes. Each
    extension point is a field as the scope `context` reads it: either a
    name that a definition in scope claims, with the configuration that
    definition declares, or a name that no definition claims. When the
    document names a data type in scope, the fill value must have the
    JSON shape that the data type's definition declares (for `int8`, an
    integer in [-128, 127]). In a scope that claims it, as `CORE` does,
    a group's `consolidated_metadata` is the envelope the core spec
    fixes, with any JSON as the documents it lists, because the reader
    does not read them, and a `null` value, which zarr-python 3.0 and 3.1
    wrote, is refused, because the reader refuses it too. Each document
    is in `$defs` under the name of its TypedDict.
    `ZarrV3ArrayMetadataJSON` describes only an array.

    A JSON Schema describes each member on its own. It does not contain
    the rules that relate members to each other: one dimension name per
    dimension of the shape, a chunk grid that fits the shape, codecs in
    pipeline order with each codec checked against the chunk it
    receives, and the checks in a definition's `rules`. So a document
    the schema accepts may still have a problem. Every JSON document
    that `read_node_metadata_v3` reads without a problem is accepted
    by the schema. A validator reads JSON as a parser returns it, with
    arrays as lists. A model's `to_json` returns tuples, and a Python
    validator does not accept tuples as arrays.
    """
    context = scoped(context, CORE_AND_EXTENSIONS)
    schemas = Schemas(_documents(context))
    array = schemas.of(ZarrV3ArrayMetadataJSON)
    group = schemas.of(ZarrV3GroupMetadataJSON)
    return schemas.document({"anyOf": [array, group]})