Skip to content

zarr_metadata.rules

zarr_metadata.rules

Validate structure and composition of Zarr metadata documents.

zarr_metadata.model checks JSON structure. This module also checks cross-field constraints such as fill-value compatibility, codec ordering, and dimension counts. Its validate_* and parse_* functions mirror the model API, and canonicalize_array_metadata_v3 answers with either the document in its simplest equivalent spelling or every reason it is not valid.

Rules target canonical metadata and may be stricter than readers that coerce inputs. Unknown entity names are left unjudged. Known entities must match their modeled shape; extra configuration keys produce an unknown_key problem without suppressing other checks. Model round-trips preserve those unmodeled members.

__all__ module-attribute

__all__ = [
    "Canonical",
    "Invalid",
    "canonicalize_array_metadata_v3",
    "parse_array_metadata_v2",
    "parse_array_metadata_v3",
    "parse_group_metadata_v2",
    "parse_group_metadata_v3",
    "validate_array_metadata_v2",
    "validate_array_metadata_v3",
    "validate_group_metadata_v2",
    "validate_group_metadata_v3",
]

Canonical dataclass

Bases: Generic[DocumentT]

A semantically valid document, in its simplest equivalent spelling.

Source code in src/zarr_metadata/rules/_canonical.py
@dataclass(frozen=True, slots=True)
class Canonical(Generic[DocumentT]):
    """A semantically valid document, in its simplest equivalent spelling."""

    document: DocumentT
    valid: Literal[True] = True

document instance-attribute

document: DocumentT

valid class-attribute instance-attribute

valid: Literal[True] = True

__init__

__init__(
    document: DocumentT, valid: Literal[True] = True
) -> None

Invalid dataclass

Every reason a document is not semantically valid; never empty.

Source code in src/zarr_metadata/rules/_canonical.py
@dataclass(frozen=True, slots=True)
class Invalid:
    """Every reason a document is not semantically valid; never empty."""

    problems: tuple[ValidationProblem, ...]
    valid: Literal[False] = False

    def __post_init__(self) -> None:
        if len(self.problems) == 0:
            msg = "Invalid requires at least one validation problem"
            raise ValueError(msg)

problems instance-attribute

problems: tuple[ValidationProblem, ...]

valid class-attribute instance-attribute

valid: Literal[False] = False

__init__

__init__(
    problems: tuple[ValidationProblem, ...],
    valid: Literal[False] = False,
) -> None

__post_init__

__post_init__() -> None
Source code in src/zarr_metadata/rules/_canonical.py
def __post_init__(self) -> None:
    if len(self.problems) == 0:
        msg = "Invalid requires at least one validation problem"
        raise ValueError(msg)

canonicalize_array_metadata_v3

canonicalize_array_metadata_v3(
    document: ZarrV3ArrayMetadataJSON,
    *,
    context: Context = CORE_AND_EXTENSIONS,
) -> Canonical[ZarrV3ArrayMetadataJSON] | Invalid

document in canonical form, or every reason it is not valid.

Expects a document the model layer has already accepted. Passing one it has not is not an error -- the semantic problems are reported the same way -- but the structural problems come back too, and the result is Invalid rather than a canonical document.

Source code in src/zarr_metadata/rules/_canonical.py
def canonicalize_array_metadata_v3(
    document: ZarrV3ArrayMetadataJSON, *, context: Context = CORE_AND_EXTENSIONS
) -> Canonical[ZarrV3ArrayMetadataJSON] | Invalid:
    """`document` in canonical form, or every reason it is not valid.

    Expects a document the model layer has already accepted. Passing one
    it has not is not an error -- the semantic problems are reported the
    same way -- but the structural problems come back too, and the result
    is `Invalid` rather than a canonical document.
    """
    normalized = cast("ZarrV3ArrayMetadataJSON", arrays_to_tuples(document))
    problems = validate_array_metadata_v3(normalized, context=context)
    if len(problems) != 0:
        return Invalid(problems)
    # Normalized first, so a document spelled with JSON arrays reaches the
    # same fixpoint as the tuple spelling. It did not: the per-field
    # simplifications test for `tuple`, and the validator was normalizing
    # on a copy the canonicalizer never saw.
    canonical = _canonical_document(normalized, context)
    # The model layer's round trip normalizes the fields no entity owns.
    return Canonical(ZarrV3ArrayMetadata.from_json(canonical).to_json())

parse_array_metadata_v2

parse_array_metadata_v2(
    value: object,
) -> ZarrV2ArrayMetadataJSON

Return value as a valid ZarrV2ArrayMetadataJSON, or raise.

Normalizes JSON arrays to tuples, then raises a single MetadataValidationError carrying every structural and composition problem found.

Source code in src/zarr_metadata/rules/_documents.py
def parse_array_metadata_v2(value: object) -> ZarrV2ArrayMetadataJSON:
    """Return `value` as a valid `ZarrV2ArrayMetadataJSON`, or raise.

    Normalizes JSON arrays to tuples, then raises a single
    `MetadataValidationError` carrying every structural and composition
    problem found.
    """
    normalized = arrays_to_tuples(value)
    problems = _judged(normalized, _validate_structure_v2, array_problems_v2)
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    return cast("ZarrV2ArrayMetadataJSON", normalized)

parse_array_metadata_v3

parse_array_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> ZarrV3ArrayMetadataJSON

Return value as a valid ZarrV3ArrayMetadataJSON, or raise.

Normalizes JSON arrays to tuples, then raises a single MetadataValidationError carrying every structural and composition problem found.

Source code in src/zarr_metadata/rules/_documents.py
def parse_array_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> ZarrV3ArrayMetadataJSON:
    """Return `value` as a valid `ZarrV3ArrayMetadataJSON`, or raise.

    Normalizes JSON arrays to tuples, then raises a single
    `MetadataValidationError` carrying every structural and composition
    problem found.
    """
    normalized = arrays_to_tuples(value)
    problems = _judged(normalized, _validate_structure_v3, _array_semantics_v3(context))
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    return cast("ZarrV3ArrayMetadataJSON", normalized)

parse_group_metadata_v2

parse_group_metadata_v2(
    value: object,
) -> ZarrV2GroupMetadataJSON

Return value as a valid ZarrV2GroupMetadataJSON, or raise.

Source code in src/zarr_metadata/rules/_documents.py
def parse_group_metadata_v2(value: object) -> ZarrV2GroupMetadataJSON:
    """Return `value` as a valid `ZarrV2GroupMetadataJSON`, or raise."""
    normalized = arrays_to_tuples(value)
    problems = _judged(normalized, _validate_group_structure_v2, _no_semantics)
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    return cast("ZarrV2GroupMetadataJSON", normalized)

parse_group_metadata_v3

parse_group_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> ZarrV3GroupMetadataJSON

Return value as a valid ZarrV3GroupMetadataJSON, or raise.

Source code in src/zarr_metadata/rules/_documents.py
def parse_group_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> ZarrV3GroupMetadataJSON:
    """Return `value` as a valid `ZarrV3GroupMetadataJSON`, or raise."""
    normalized = arrays_to_tuples(value)
    problems = _judged(normalized, _validate_group_structure_v3, _group_semantics_v3(context))
    if len(problems) != 0:
        raise MetadataValidationError(problems)
    return cast("ZarrV3GroupMetadataJSON", normalized)

validate_array_metadata_v2

validate_array_metadata_v2(
    value: object,
) -> tuple[ValidationProblem, ...]

Every reason value is not a valid v2 array document (merged form).

JSON arrays are normalized to tuples before judgment, as in validate_array_metadata_v3.

Source code in src/zarr_metadata/rules/_documents.py
def validate_array_metadata_v2(value: object) -> tuple[ValidationProblem, ...]:
    """Every reason `value` is not a valid v2 array document (merged form).

    JSON arrays are normalized to tuples before judgment, as in
    `validate_array_metadata_v3`.
    """
    return _judged(arrays_to_tuples(value), _validate_structure_v2, array_problems_v2)

validate_array_metadata_v3

validate_array_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> tuple[ValidationProblem, ...]

Why value is not a valid v3 array document.

Every structural problem, and every semantic problem that can be determined. One member that cannot be read costs the composition judgments about the entity holding it -- whether a shard's inner shape divides the array it is handed cannot be answered by a shard that could not be built -- so a document with two defects in one configuration may need a second pass. The verdict is never affected.

Structural problems (from the model layer) and semantic problems (from the entities themselves) are reported together. JSON arrays are normalized to tuples before judgment, so list-spelled documents (e.g. fresh json.loads output) are judged at the canonical data level rather than rejected for their spelling.

Source code in src/zarr_metadata/rules/_documents.py
def validate_array_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> tuple[ValidationProblem, ...]:
    """Why `value` is not a valid v3 array document.

    Every structural problem, and every semantic problem that can be
    determined. One member that cannot be read costs the *composition*
    judgments about the entity holding it -- whether a shard's inner
    shape divides the array it is handed cannot be answered by a shard
    that could not be built -- so a document with two defects in one
    configuration may need a second pass. The verdict is never affected.

    Structural problems (from the model layer) and semantic problems
    (from the entities themselves) are reported together. JSON arrays are
    normalized to tuples before judgment, so list-spelled documents
    (e.g. fresh `json.loads` output) are judged at the canonical data
    level rather than rejected for their spelling.
    """
    return _judged(arrays_to_tuples(value), _validate_structure_v3, _array_semantics_v3(context))

validate_group_metadata_v2

validate_group_metadata_v2(
    value: object,
) -> tuple[ValidationProblem, ...]

Every reason value is not a valid v2 group document (merged form).

v2 group documents carry no composition constraints today, so this is the structural judgment, offered here for a uniform read-side API.

Source code in src/zarr_metadata/rules/_documents.py
def validate_group_metadata_v2(value: object) -> tuple[ValidationProblem, ...]:
    """Every reason `value` is not a valid v2 group document (merged form).

    v2 group documents carry no composition constraints today, so this is
    the structural judgment, offered here for a uniform read-side API.
    """
    return _judged(arrays_to_tuples(value), _validate_group_structure_v2, _no_semantics)

validate_group_metadata_v3

validate_group_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> tuple[ValidationProblem, ...]

Every reason value is not a valid v3 group document.

Composition rules recurse into inline consolidated metadata, so a consolidated child document invalid under its own rules is reported here, at its path.

Source code in src/zarr_metadata/rules/_documents.py
def validate_group_metadata_v3(
    value: object, *, context: Context = CORE_AND_EXTENSIONS
) -> tuple[ValidationProblem, ...]:
    """Every reason `value` is not a valid v3 group document.

    Composition rules recurse into inline consolidated metadata, so a
    consolidated child document invalid under its own rules is reported
    here, at its path.
    """
    return _judged(
        arrays_to_tuples(value), _validate_group_structure_v3, _group_semantics_v3(context)
    )