Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/zarr-metadata/changes/415.doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Every public object now has an API reference entry with a docstring, and the
`struct` data type has its reference page.
6 changes: 5 additions & 1 deletion packages/zarr-metadata/docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ The model layer's readers are imported from
[`zarr_metadata.model`](model.md) directly, and the problem types every
reader reports from [`zarr_metadata.problems`](problems.md).

## Common types
## Top-level names

Two cross-cutting types are also exported from the top-level
`zarr_metadata` namespace: `JSONValue`, documented in
[`zarr_metadata.typed_json`](typed_json.md), and `ZarrV3NamedConfigJSON`,
documented in [`zarr_metadata.v3`](v3/index.md).

The package version is exported only from the top level:

::: zarr_metadata.__version__
2 changes: 2 additions & 0 deletions packages/zarr-metadata/docs/api/v3/data_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ title: data_type

::: zarr_metadata.v3.data_type.string

::: zarr_metadata.v3.data_type.struct

::: zarr_metadata.v3.data_type.numpy_datetime64

::: zarr_metadata.v3.data_type.numpy_timedelta64
Expand Down
1 change: 1 addition & 0 deletions packages/zarr-metadata/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ test = [
"jsonschema",
"hypothesis",
"pytest-examples>=0.0.18",
"griffelib",
]
docs = [
# Pins match the zarr-python docs environment in the repo-root
Expand Down
1 change: 1 addition & 0 deletions packages/zarr-metadata/src/zarr_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@
)

__version__ = version("zarr-metadata")
"""The installed version of the `zarr-metadata` distribution."""


__all__ = [
Expand Down
8 changes: 8 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,13 @@ class ZarrV3NamedConfigJSON(TypedDict, closed=True):
"""

name: ReadOnly[str]
"""The extension's name."""
configuration: NotRequired[ReadOnly[Mapping[str, JSONValue]]]
"""The extension's parameters. May be absent."""
must_understand: NotRequired[bool]
"""Whether a reader that does not recognize this extension must refuse the document.

Absent means `true`. The spec does not permit `false` for a data type, chunk
grid or chunk key encoding
(https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L1571-L1581).
"""
10 changes: 10 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,23 @@ class ValidationProblem:
"""

loc: tuple[str | int, ...]
"""The path from the root of what was judged to the offending value.

Keys and indices, e.g. `("codecs", 0, "name")` in a document; an empty
`loc` refers to that root.
"""
message: str
"""The human-readable description of what is wrong."""
kind: ProblemKind
"""The kind of problem, for programmatic dispatch."""
input: JSONValue | UNSET = dataclasses.field(
default=UNSET, kw_only=True, compare=False, repr=False
)
"""The JSON found at `loc`, or `UNSET` when nothing is there or it is not JSON."""
ctx: Mapping[str, JSONValue] = dataclasses.field(
default_factory=_no_ctx, kw_only=True, compare=False, repr=False
)
"""What was expected, e.g. bounds or a closed set of values; empty when only a type was."""

def __post_init__(self) -> None:
# Enforce the annotations at runtime. A rule written without a type
Expand Down Expand Up @@ -215,6 +224,7 @@ class MetadataValidationError(ValueError):
"""

problems: tuple[ValidationProblem, ...]
"""Every problem found, in the order found."""

def __init__(self, problems: Sequence[ValidationProblem]) -> None:
self.problems = tuple(problems)
Expand Down
13 changes: 13 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/_typed_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,21 @@ class TypedDictKeys:
"""

members: Mapping[str, tuple[object, bool]]
"""Every key it declares, its bases' included, with its value's type and whether it is required.

The type has its qualifiers and `Annotated` metadata peeled.
"""
extra_items: object
"""What any other key may hold.

`Never` when the TypedDict is closed, `object` when it is open, and the
`extra_items` type otherwise.
"""
declared: bool
"""Whether the TypedDict or a base said what other keys may hold, rather than defaulting.

It is said with `closed=` or `extra_items=`. A TypedDict that says nothing is open.
"""

@property
def required(self) -> frozenset[str]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,13 @@ class Context(Protocol):
"""

@property
def zarr_format(self) -> Literal[2, 3]: ...
def zarr_format(self) -> Literal[2, 3]:
"""The Zarr format of the documents this scope reads."""
...

def claimant(self, kind: type[D], name: str) -> D | None: ...
def claimant(self, kind: type[D], name: str) -> D | None:
"""The definition of `kind` in scope that reads `name` (a name as a document uses it), or None if there is none."""
...


@dataclass(frozen=True, slots=True)
Expand Down
5 changes: 5 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/definition/_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ class Conflict:
"""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."""

key: ClaimKey
"""The kind, and the name the two definitions are filed under."""
claimed: Definition[Any] | None
"""The definition one reading or scope gives the name; None when it leaves the name unclaimed."""
found: Definition[Any] | None
"""The definition the other gives the name; None when it gives none, so the name would lose its meaning."""
loc: Loc | None = None
"""Where the name occurs in the document; None when no document is available."""
written: str | None = None
"""The name the document uses at the conflict location, for example `r16` for a key of `r*`; None when no document is available."""

Expand Down Expand Up @@ -90,6 +94,7 @@ class ScopeConflictError(ValueError):

def __init__(self, conflicts: Sequence[Conflict]) -> None:
self.conflicts: tuple[Conflict, ...] = tuple(conflicts)
"""Every conflict, in the order found; the message joins them."""
super().__init__("; ".join(str(conflict) for conflict in self.conflicts))

def __reduce__(
Expand Down
24 changes: 24 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/model/_array_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,15 +420,34 @@ class ZarrV2ArrayMetadataUpdate(TypedDict, total=False, closed=True):
"""

shape: tuple[int, ...]
"""The `shape` member: the length of each dimension."""
dtype: ZarrV2DataTypeMetadata
"""The `dtype` member: a NumPy typestr, or a list of field records for a structured data type."""
chunks: tuple[int, ...]
"""The `chunks` member: the length of each chunk along each dimension, one for each dimension of `shape`."""
fill_value: JSONValue
"""The `fill_value` member: the value of an element in a chunk that was never written, or `None` for none."""
order: ZarrV2ArrayOrder
"""The `order` member: the layout of elements within each chunk, `"C"` or `"F"`."""
compressor: ZarrV2CodecMetadata | None
"""The `compressor` member: the configuration of the codec that compresses each chunk, or `None` for none."""
filters: tuple[ZarrV2CodecMetadata, ...] | None
"""The `filters` member: the configuration of each codec applied to each chunk before the compressor, in order, or `None` for none."""
dimension_separator: ZarrV2ArrayDimensionSeparator | UNSET
"""The `dimension_separator` member: the separator between a chunk's grid coordinates in its key, `"."` or `"/"`.

`UNSET` removes it, which means `"."`.
"""
attributes: Mapping[str, JSONValue] | UNSET
"""The `attributes` member: user attributes, which a store holds in `.zattrs`.

`UNSET` removes it, so the array has no `.zattrs`.
"""
extra_fields: Mapping[str, JSONValue | UNSET]
"""The members the spec does not define, by key: each one given is set, or removed when its value is `UNSET`, and any other is kept.

A key the spec defines, or that the update takes by name, raises `TypeError`.
"""


class ZarrV2ArrayMetadata(Keyed):
Expand Down Expand Up @@ -460,7 +479,12 @@ class ZarrV2ArrayMetadata(Keyed):
__slots__ = ("_context", "_document", "_members", "_reading", "_shown")

zarr_format: Final[Literal[2]] = 2 # noqa: PYI064 - a bare Final does not narrow a union of models
"""The Zarr format version, `2`. A class constant: comparing it narrows a union of models."""
node_type: Final[Literal["array"]] = "array" # noqa: PYI064 - as above
"""The kind of node, `"array"`. A class constant: comparing it narrows a union of models.

A v2 document does not write this member: its `.zarray` key says the node is an array.
"""

def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
context = scoped(context, CORE_V2)
Expand Down
24 changes: 24 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/model/_array_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,15 +459,37 @@ class ZarrV3ArrayMetadataUpdate(TypedDict, total=False, closed=True):
"""

shape: tuple[int, ...]
"""The `shape` member: the length of each dimension."""
data_type: ZarrV3MetadataFieldJSON
"""The `data_type` member: the data type of the array's elements."""
chunk_grid: ZarrV3MetadataFieldJSON
"""The `chunk_grid` member: how the array is divided into chunks."""
chunk_key_encoding: ZarrV3MetadataFieldJSON
"""The `chunk_key_encoding` member: how a chunk's grid coordinates become its store key."""
fill_value: JSONValue
"""The `fill_value` member: the value of an element in a chunk that was never written."""
codecs: tuple[ZarrV3MetadataFieldJSON, ...]
"""The `codecs` member: the codecs that encode each chunk, in order."""
attributes: Mapping[str, JSONValue] | UNSET
"""The `attributes` member: user attributes.

`UNSET` removes it, so the array has no attributes.
"""
storage_transformers: tuple[ZarrV3MetadataFieldJSON, ...] | UNSET
"""The `storage_transformers` member: the storage transformers, in order.

`UNSET` removes it, which means none.
"""
dimension_names: tuple[str | None, ...] | UNSET
"""The `dimension_names` member: a name for each dimension, `None` for an unnamed one.

`UNSET` removes it, which means every dimension is unnamed.
"""
extra_fields: Mapping[str, ZarrV3ExtensionField | UNSET]
"""The members the spec does not define, by key: each one given is set, or removed when its value is `UNSET`, and any other is kept.

A key the spec defines, or that the update takes by name, raises `TypeError`.
"""


class ZarrV3ArrayMetadata(Keyed):
Expand Down Expand Up @@ -496,7 +518,9 @@ class ZarrV3ArrayMetadata(Keyed):
__slots__ = ("_context", "_document", "_members", "_reading", "_shown")

zarr_format: Final[Literal[3]] = 3 # noqa: PYI064 - a bare Final does not narrow a union of models
"""The Zarr format version, `3`. A class constant: comparing it narrows a union of models."""
node_type: Final[Literal["array"]] = "array" # noqa: PYI064 - as above
"""The kind of node, `"array"`. A class constant: comparing it narrows a union of models."""

def __init__(self, document: object, context: ZarrV3Context | None = None) -> None:
context = scoped(context, CORE_AND_EXTENSIONS)
Expand Down
10 changes: 10 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/model/_group_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ class ZarrV2GroupMetadataUpdate(TypedDict, total=False):
"""The members `ZarrV2GroupMetadata.update` replaces: `attributes` in the form a `.zattrs` writes them, or `UNSET` for no `.zattrs`."""

attributes: Mapping[str, JSONValue] | UNSET
"""The `attributes` member: user attributes, which a store holds in `.zattrs`.

`UNSET` removes it, so the group has no `.zattrs`.
"""


class ZarrV2GroupMetadata(Keyed):
Expand Down Expand Up @@ -265,7 +269,12 @@ class ZarrV2GroupMetadata(Keyed):
_shown: Mapping[str, JSONValue]

zarr_format: Final[Literal[2]] = 2 # noqa: PYI064 - a bare Final does not narrow a union of models
"""The Zarr format version, `2`. A class constant: comparing it narrows a union of models."""
node_type: Final[Literal["group"]] = "group" # noqa: PYI064 - as above
"""The kind of node, `"group"`. A class constant: comparing it narrows a union of models.

A v2 document does not write this member: its `.zgroup` key says the node is a group.
"""

def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
context = scoped(context, CORE_V2)
Expand Down Expand Up @@ -488,6 +497,7 @@ class ZarrV2ConsolidatedMetadata(Keyed):
_metadata: dict[str, ZarrV2NodeMetadata]

zarr_consolidated_format: Final = 1
"""The version of the consolidated metadata format, `1`."""

def __init__(self, document: object, context: ZarrV2Context | None = None) -> None:
context = scoped(context, CORE_V2)
Expand Down
19 changes: 19 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/model/_group_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ class ZarrV3ConsolidatedMetadataInput(TypedDict, closed=True):
"""

kind: Literal["inline"]
"""How the entries are carried, `"inline"`: in the group's own document."""
must_understand: Literal[False]
"""Always `False`: a reader that does not recognize consolidated metadata may ignore it."""
metadata: Mapping[str, ZarrV3NodeMetadataInput]
"""The document of each node below the group, or a model of one, by its path from the group."""


class ZarrV3GroupMetadataUpdate(TypedDict, total=False, closed=True):
Expand All @@ -152,13 +155,25 @@ class ZarrV3GroupMetadataUpdate(TypedDict, total=False, closed=True):
"""

attributes: Mapping[str, JSONValue] | UNSET
"""The `attributes` member: user attributes.

`UNSET` removes it, so the group has no attributes.
"""
consolidated_metadata: (
ZarrV3ConsolidatedMetadataJSON
| Mapping[str, JSONValue]
| ZarrV3ConsolidatedMetadata
| UNSET
)
"""The `consolidated_metadata` member: the documents of the nodes below the group, carried in its own document.

`UNSET` removes it.
"""
extra_fields: Mapping[str, ZarrV3ExtensionField | UNSET]
"""The members the spec does not define, by key: each one given is set, or removed when its value is `UNSET`, and any other is kept.

A key the spec defines, or that the update takes by name, raises `TypeError`.
"""


class ZarrV3GroupMetadata(Keyed):
Expand All @@ -182,7 +197,9 @@ class ZarrV3GroupMetadata(Keyed):
__slots__ = ("_context", "_document", "_members", "_reading", "_shown")

zarr_format: Final[Literal[3]] = 3 # noqa: PYI064 - a bare Final does not narrow a union of models
"""The Zarr format version, `3`. A class constant: comparing it narrows a union of models."""
node_type: Final[Literal["group"]] = "group" # noqa: PYI064 - as above
"""The kind of node, `"group"`. A class constant: comparing it narrows a union of models."""

def __init__(self, document: object, context: ZarrV3Context | None = None) -> None:
context = scoped(context, CORE_AND_EXTENSIONS)
Expand Down Expand Up @@ -491,7 +508,9 @@ class ZarrV3ConsolidatedMetadata(Keyed):
__slots__ = ("_context", "_document", "_metadata")

kind: Final = "inline"
"""How the entries are carried, `"inline"`: in the group's own document."""
must_understand: Final = False
"""Always `False`: a reader that does not recognize consolidated metadata may ignore it."""

def __init__(self, member: object, context: ZarrV3Context | None = None) -> None:
context = scoped(context, CORE_AND_EXTENSIONS)
Expand Down
1 change: 1 addition & 0 deletions packages/zarr-metadata/src/zarr_metadata/pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ class InScope:
"""

context: ZarrV3Context | ZarrV2Context
"""The scope the field is read in, in place of any scope the validation context holds."""

def __post_init__(self) -> None:
if not _is_context(self.context):
Expand Down
3 changes: 3 additions & 0 deletions packages/zarr-metadata/src/zarr_metadata/v2/_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ class ZarrV2DataTypeDefinition(WithFillValue[C], kind=True, format=2):
"""

label: ClassVar[str] = "v2 data type"
"""The name of the kind in error messages, "v2 data type"."""
field_aliases: ClassVar[tuple[TypeAliasType, ...]] = (ZarrV2DataTypeField,)
"""The field alias that annotates a configuration member holding a v2 dtype, `ZarrV2DataTypeField`. The type of each struct record is one."""

def _refusal(self) -> str | None:
if parse_typestr(self.name) is not None:
Expand Down Expand Up @@ -231,6 +233,7 @@ class ZarrV2CodecDefinition(Definition[C], kind=True, format=2):
"""

label: ClassVar[str] = "v2 codec"
"""The name of the kind in error messages, "v2 codec"."""

@classmethod
def _name_problem(cls, name: str, at: Loc) -> ValidationProblem | None:
Expand Down
1 change: 1 addition & 0 deletions packages/zarr-metadata/src/zarr_metadata/v2/_field_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ZarrV2CodecMetadata(TypedDict, extra_items=JSONValue):
"""

id: str
"""The numcodecs id that names the codec, such as `"blosc"`."""


__all__ = ["ZarrV2CodecMetadata", "ZarrV2DataTypeMetadata"]
Loading
Loading