Skip to content

Commit 9783489

Browse files
d-v-bclaude
andauthored
docs(zarr-metadata): split the landing page into guide pages and link each spec claim (#4605)
* docs(zarr-metadata): split the landing page into guide pages and link each spec claim Shrink index.md to a short pitch, one example and a reader-by-reader list of where to go. Move the rest into guide pages: models and readers, what the readers check, consolidated metadata, repairing known writer bugs, writing an extension, JSON Schema and Pydantic. Each claim backed by a Zarr spec links to the spec section that makes it; the consolidated metadata page says what the specs do and do not settle and links the zarr-specs PRs and issues and the original zarr-python implementation. Trim the README so it no longer mirrors the landing page, and say "context" where the pages said "scope". Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): number the guide-pages fragment after its fork PR Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): say "member" for a JSON name/value pair in the guide pages The rename branch keeps "member" for a name/value pair of a metadata object, so the new pages follow: "a top-level member that the spec does not define is an extra field". "Extra field" and "extension field" stay, after the names extra_fields and ZarrV3ExtensionField. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): say "member" for a JSON name/value pair on every page Sweep the parsing-validation and context pages, which said "field" for a name/value pair of a metadata object, and state the one exception on the models page: "extra field" follows the spec's "additional fields" and the names extra_fields, ExtraFieldDefinition and must_understand_fields; "field" otherwise means a field of a Python class, or a part of a structured data type. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): say "member" in the two must_understand_fields sentences The models page said "each unclaimed field" and "any such field" where the landing page and every neighboring sentence say "member". Roborev flagged the mismatch. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): say why the additional-fields link points at the latest spec v3.1.0 has no "Additional fields" heading; PR 373 added it after the release, so the link cannot be pinned. Say so beside the link. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): split the additional-fields sentence and date its claim Roborev found the three-clause sentence hard to parse and its "no tagged release yet" unanchored in time. Two sentences now, anchored to v3.1.0, with a note to pin the link once a release carries the heading. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): fix six wording and example issues roborev found on the guide pages The pydantic example now builds the manifest with model_validate, so it type-checks as the next sentence claims. The consolidated page says three other readers take a context, not two. The equality section quotes the v2 spec on a missing .zattrs in two sentences. The two numbered Sphinx anchors are replaced by line ranges in the spec source at the v3.1.0 tag. The landing page says once that the library does no chunk or store I/O. The extensions page example and the zarr_metadata.definition docstring example are pinned to each other by a test. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * test(zarr-metadata): make the example-drift test say what it could not find Roborev asked for diagnostics: the test now asserts there is exactly one extensions-session block and exactly one marker line in the docstring, each with a message, and compares line lists so a mismatch points at the differing line. Assisted-by: ClaudeCode:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <[email protected]> * docs(zarr-metadata): build the extension example with union and say "context" throughout On main a Context has no update method, so the acme.lz4 example on the extensions page now adds its definition with union, as the definition module docstring does. Two leftover "scope"s in the parsing page say "context", and the models page says "member" for an extra field. The example-drift test imports at module level. Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(zarr-metadata): number the guide-pages fragment after upstream PR 4605 Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(zarr-metadata): fence the module docstring examples as python so the API pages highlight them The examples in the definition, json_schema, pydantic and typed_json module docstrings were indented code blocks. An indented block names no language and pymdownx.highlight does not guess one, so the API reference rendered them as plain text. Each is now a ```python fence, and the example-drift test reads the definition docstring's fence. Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <[email protected]> * docs(zarr-metadata): write the guide pages in short sentences and lists Long comma-chained sentences on the guide pages, README and landing page are split into short sentences, and enumerations become bulleted or numbered lists. models, validation-rules, consolidated-metadata, repair, extensions, json-schema and pydantic are restructured; context and parsing-validation keep their narrative voice and only have their long sentences split. No link, claim or code block changes. Assisted-by: ClaudeCode:claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]>
1 parent f50cce1 commit 9783489

19 files changed

Lines changed: 1055 additions & 868 deletions

‎packages/zarr-metadata/README.md‎

Lines changed: 23 additions & 387 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The documentation now has a short landing page that points each kind of reader to a guide page, instead of one long page. New guide pages cover models and readers, the rules the readers check, consolidated metadata, repairing known writer bugs, writing an extension, JSON Schema and Pydantic. Each claim the pages make about a Zarr specification links to the section of the spec that makes it, and the consolidated metadata page says what the specs do and do not settle, with links to the zarr-specs pull requests and issues and to the original zarr-python implementation. The pages say "context" for a `Context` where some said "scope".

‎packages/zarr-metadata/docs/api/index.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The package is organized to mirror the structure of the Zarr specifications:
99
- [`zarr_metadata.json_schema`](json_schema.md) — JSON Schema output for
1010
any `TypedDict`, one v3 declaration, or a whole v3 `zarr.json`
1111
- [`zarr_metadata.model`](model.md) — document models (each one a
12-
document and the scope it was read in), the readers that return
12+
document and the context it was read in), the readers that return
1313
problems instead of raising them, and the `UNSET` sentinel
1414
- [`zarr_metadata.problems`](problems.md) — `ValidationProblem`,
1515
`ProblemKind`, `Problems` and `MetadataValidationError`, which every
@@ -21,7 +21,7 @@ The package is organized to mirror the structure of the Zarr specifications:
2121
typing spec defines them, with every problem located
2222
- [`zarr_metadata.v2`](v2.md) — `TypedDict` shapes for Zarr v2 documents
2323
(`.zarray`, `.zgroup`, `.zattrs`, `.zmetadata`), and
24-
`zarr_metadata.v2.definition`: the v2 kinds of metadata, the scope
24+
`zarr_metadata.v2.definition`: the v2 kinds of metadata, the context
2525
`CORE_V2_CONTEXT`, and the readers of one `dtype` or codec. Each dtype family's
2626
definition is in `zarr_metadata.v2.data_type`, and each codec's in
2727
`zarr_metadata.v2.codec`
@@ -32,10 +32,11 @@ The package is organized to mirror the structure of the Zarr specifications:
3232
- [`zarr_metadata.definition`](definition.md) — what both formats share
3333
for reading a declaration: `Definition`, `resolve`, the three results of a
3434
reading (`AcceptedDeclaration`, `UnclaimedDeclaration`, `RefusedDeclaration`), and the
35-
scope type `Context`. Its module docstring is the guide
35+
context type `Context`. Its module docstring is the guide
3636
- [`zarr_metadata.v3.definition`](v3/definition.md) — the five v3 kinds
3737
of metadata (`CodecDefinition`, `DataTypeDefinition`, ...), the codec
38-
pipeline types, and the v3 scopes `CORE_CONTEXT` and `CORE_AND_EXTENSIONS_CONTEXT`
38+
pipeline types, and the v3 contexts `CORE_CONTEXT`,
39+
`CORE_AND_EXTENSIONS_CONTEXT` and `ZARR_PYTHON_CONTEXT`
3940

4041
The document types, models, and spec vocabulary — including the store keys —
4142
are re-exported at the top level, so
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Consolidated metadata
2+
3+
Consolidated metadata is a copy of every metadata document below a group,
4+
kept in one place. A reader can then open a whole hierarchy with one read.
5+
6+
Consolidated metadata is a convention of zarr-python, the reference
7+
implementation. Neither Zarr specification defines it in full. This page
8+
first says what is settled, then describes the API.
9+
10+
## Status in the specifications
11+
12+
**Zarr v2.** The v2 spec has no consolidated metadata.
13+
14+
- The `.zmetadata` file was designed in zarr-python
15+
[PR 268](https://github.com/zarr-developers/zarr-python/pull/268). It
16+
merged in November 2018 and shipped in zarr-python 2.3.0.
17+
- The
18+
[zarr-python 2 tutorial](https://zarr.readthedocs.io/en/v2.18.3/tutorial.html#consolidating-metadata)
19+
describes it.
20+
- zarr-specs [issue 113](https://github.com/zarr-developers/zarr-specs/issues/113),
21+
on standardizing the format, is open.
22+
23+
**Zarr v3.** The core spec names the member but does not define its
24+
entries.
25+
26+
- zarr-specs [PR 309](https://github.com/zarr-developers/zarr-specs/pull/309)
27+
proposes the `consolidated_metadata` member, with the `kind: "inline"`
28+
envelope that zarr-python 3 writes. It follows
29+
[issue 136](https://github.com/zarr-developers/zarr-specs/issues/136). It
30+
has been open since August 2024 and is not merged.
31+
- [PR 373](https://github.com/zarr-developers/zarr-specs/pull/373) merged
32+
in July 2026. Since then, the core spec names the member "for historical
33+
reasons" and fixes its envelope
34+
([spec text](https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L802-L816)).
35+
The envelope is an object with:
36+
- `must_understand: false`,
37+
- `kind: "inline"`,
38+
- a `metadata` object.
39+
- PR 373 closed
40+
[issue 371](https://github.com/zarr-developers/zarr-specs/issues/371).
41+
That issue found a problem with the v3.1.0 extension rules. Read
42+
strictly, they put every consolidated group zarr-python 3 had written out
43+
of spec, because the envelope has no `name`.
44+
45+
This package reads the envelope the spec fixes. It reads the entries as
46+
zarr-python writes them.
47+
48+
## Zarr v3
49+
50+
Consolidated metadata holds the hierarchy below its group, with the group
51+
as the root.
52+
53+
- The document of the node at `/a/b` is stored under the key `a/b`. The
54+
[stored representation](https://zarr-specs.readthedocs.io/en/v3.1.0/v3/core/index.html#stored-representation)
55+
strips the leading `/` of a path.
56+
- The documents and the group must form a tree. Only groups have child
57+
nodes, and the parent of every node is present.
58+
`ZarrV3ConsolidatedMetadata` checks this when it reads the documents.
59+
60+
`NodeName` and `NodePath`, in `zarr_metadata.v3`, are string types. They
61+
satisfy the spec's rules for
62+
[node names](https://zarr-specs.readthedocs.io/en/v3.1.0/v3/core/index.html#node-names)
63+
and paths. They are modeled on the zarrs types with the same names. These
64+
functions check a string against those rules:
65+
66+
- `validate_node_name_v3`, `is_node_name_v3` and `parse_node_name_v3`,
67+
- the matching `node_path` functions.
68+
69+
### Reading
70+
71+
A group does not read the documents its `consolidated_metadata` lists.
72+
Consolidated metadata is a convention of the reference implementation, not
73+
core metadata. So the group checks the
74+
[envelope that the core spec fixes](https://github.com/zarr-developers/zarr-specs/blob/fc7dd9c9beb5a50b87f9b08b00bf50fc0048482f/docs/v3/core/index.rst#L802-L816),
75+
then holds the member as written. `CORE_CONTEXT` holds
76+
`CONSOLIDATED_METADATA_EXTRA_FIELD`, the definition that reads this
77+
envelope.
78+
79+
A reader can use the member as a source of child documents, in place of
80+
the store. There are two ways:
81+
82+
- Read one document with `read_node_metadata_v3` when that node is opened.
83+
- Read all of them at once, with one of:
84+
- the group's `consolidated()`,
85+
- `ZarrV3ConsolidatedMetadata`,
86+
- `read_consolidated_metadata_v3`, which returns problems instead of
87+
raising them.
88+
89+
Either way, a document with a problem fails only when it is read.
90+
91+
Which context the documents are read in:
92+
93+
- `consolidated()` uses the group's own context.
94+
- The other three take a `context`. Pass `context=group.context` to use the
95+
group's own context. With no `context`, they use
96+
`CORE_AND_EXTENSIONS_CONTEXT`.
97+
98+
### Writing
99+
100+
To write consolidated metadata:
101+
102+
1. Build a `ZarrV3ConsolidatedMetadata`.
103+
2. Pass it to the group's `update`.
104+
105+
The group reads the listed models in its own context. It refuses a model
106+
that its context reads differently. Then it writes the document.
107+
108+
`ZarrV3ConsolidatedMetadata` reads every document strictly. It also
109+
accepts node models as entries. It accepts such an entry when the entry's
110+
claims refine into the model's context. Otherwise it refuses the entry at
111+
its path. To consolidate children read in several contexts, consolidate
112+
them in the `union` of those contexts.
113+
114+
## Zarr v2
115+
116+
A v2 hierarchy's consolidated metadata is the `.zmetadata` document at the
117+
root of the store. zarr-python 2.3 and later write it. It is an object
118+
with:
119+
120+
- `zarr_consolidated_format: 1`,
121+
- a `metadata` object. This maps each metadata file's path to that file's
122+
contents, under keys such as `a/.zarray`, `a/.zattrs` and `a/b/.zgroup`.
123+
124+
The format comes from zarr-python
125+
[PR 268](https://github.com/zarr-developers/zarr-python/pull/268). No
126+
specification defines it.
127+
128+
`ZarrV2ConsolidatedMetadata` reads such a document.
129+
`read_consolidated_metadata_v2` does the same, but returns problems instead
130+
of raising them. Both take a `context`. The default is `CORE_V2_CONTEXT`.
131+
132+
How the entries are read:
133+
134+
- Each `.zarray` or `.zgroup` entry is read as a node, merged with its
135+
sibling `.zattrs`.
136+
- A `.zattrs` with no sibling is kept, but forms no node.
137+
- Any other entry must be JSON, and is kept.
138+
139+
`to_json` returns the entries keyed by file, as written.
140+
141+
The `.zmetadata` that zarr-python 3 writes for a v2 hierarchy has a known
142+
bug. `read_repaired_consolidated_metadata_v2` undoes it. See
143+
[Repairing known writer bugs](repair.md).

‎packages/zarr-metadata/docs/context.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Resolving metadata documents requires a context, a `Context`, that defines which
44

55
## Why do we need a context
66

7-
The Zarr V3 spec defines the JSON structure of `zarr.json` for arrays and groups exactly, by defining the fields that must be present and the JSON types they must have. But the __interpretation__ of those fields as actual data types, codecs, chunk grids, etc is not strictly defined by the spec. Relating a JSON value like `"bytes"` to the `bytes` codec requires a mapping from names to codec types.
7+
The Zarr V3 spec defines the JSON structure of `zarr.json` for arrays and groups exactly. It defines the members that must be present and the JSON types they must have. But the __interpretation__ of those members as actual data types, codecs, chunk grids, etc is not strictly defined by the spec. Relating a JSON value like `"bytes"` to the `bytes` codec requires a mapping from names to codec types.
88

99
`zarr-metadata` makes this explicit by validating JSON documents with a __context object__. That context object contains definitions of codecs, data types, etc, which can be matched to the declarations in the JSON document.
1010

@@ -47,7 +47,7 @@ document: ZarrV3ArrayMetadataJSON = {
4747
}
4848
```
4949

50-
In the above code snippet we define a `zarr.json` document with three codec declarations (we will just call them codecs for brevity): one named `bytes`, another named `gzip`, and a third named `unknown`. The `bytes` codec here is correctly configured according to the `bytes` codec spec. The `gzip` declaration here violates the `gzip` codec spec because the `level` configuration field is too high. And the `unknown` codec is unknown, it has no specification and cannot be resolved.
50+
In the above code snippet we define a `zarr.json` document with three codec declarations. (We will just call them codecs for brevity.) They are named `bytes`, `gzip` and `unknown`. The `bytes` codec here is correctly configured according to the `bytes` codec spec. The `gzip` declaration here violates the `gzip` codec spec because the `level` configuration member is too high. And the `unknown` codec is unknown, it has no specification and cannot be resolved.
5151

5252
These three codecs represent three different scenarios that this library handles, two with distinct failure modes. We demonstrate how this works in the next code snippet.
5353

@@ -101,7 +101,7 @@ These are the three scenarios:
101101

102102
- `bytes` is parsed to an `AcceptedDeclaration`. A definition in the context claims the name `bytes`, and the configuration passes its checks.
103103
- The malformed `gzip` is parsed to a `RefusedDeclaration`. A definition claims the name `gzip`, but the configuration fails one of its checks: a `level` of 99 is outside the range gzip allows. The read reports the problem, with its location in the document, and builds no model, so `reading.metadata` is `None`. Building the model directly raises the same problem as a `MetadataValidationError`.
104-
- The `unknown` codec is parsed to an `UnclaimedDeclaration`. Nothing in the context claims the name, so it is kept as written and not checked. This is not a problem: the spec lets a document name codecs a reader does not know.
104+
- The `unknown` codec is parsed to an `UnclaimedDeclaration`. Nothing in the context claims the name, so it is kept as written and not checked. This is not a problem: the spec [lets a document name codecs](https://zarr-specs.readthedocs.io/en/v3.1.0/v3/core/index.html#extension-codecs) defined outside it, which a reader may not know.
105105

106106
So one JSON document can be valid in one context and invalid in another. This is why a model is a document _and_ the context it was read in: the context defines the interpretation of the document. A model keeps its context, and moving it to another context reads the document again. Our `small` model was valid in its original context, but it is not valid in the new one:
107107

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Writing an extension
2+
3+
A v3 document may name a codec, data type, chunk grid, chunk key encoding
4+
or storage transformer that this package does not define. Each of these is
5+
an
6+
[extension point](https://zarr-specs.readthedocs.io/en/v3.1.0/v3/core/index.html#extension-points)
7+
of the v3 spec.
8+
9+
The readers leave such a declaration unclaimed. To check it, a program
10+
adds a **definition** to the context it reads in.
11+
12+
The [`zarr_metadata.definition`](api/definition.md) module docstring is the
13+
full reference. This page is the short version.
14+
15+
A definition is a value, not a class to subclass. It has:
16+
17+
- a `name`,
18+
- a `TypedDict` for the configuration's JSON,
19+
- a `rules` function, when the type cannot express every rule. The function
20+
yields the spec violations in a configuration.
21+
22+
A v3 codec definition also says:
23+
24+
- what kind of codec it is,
25+
- whether its output size is fixed by its input size.
26+
27+
```python test="true" session="extensions"
28+
from collections.abc import Iterator
29+
from typing import Annotated, NotRequired
30+
31+
from annotated_types import Ge
32+
from typing_extensions import TypedDict
33+
34+
from zarr_metadata import ValidationProblem
35+
from zarr_metadata.definition import Nested
36+
from zarr_metadata.v3.definition import CORE_AND_EXTENSIONS_CONTEXT, CodecDefinition, ZarrV3Context
37+
38+
39+
class AcmeLz4Configuration(TypedDict, closed=True):
40+
acceleration: Annotated[int, Ge(1)]
41+
dictionary: NotRequired[str]
42+
dictionary_size: NotRequired[Annotated[int, Ge(1)]]
43+
44+
45+
def acme_lz4_rules(
46+
configuration: AcmeLz4Configuration, nested: Nested
47+
) -> Iterator[ValidationProblem]:
48+
if "dictionary" in configuration and "dictionary_size" not in configuration:
49+
yield ValidationProblem(
50+
("dictionary_size",), "a dictionary needs its size", "missing_key"
51+
)
52+
53+
54+
ACME_LZ4 = CodecDefinition(
55+
name="acme.lz4",
56+
configuration=AcmeLz4Configuration,
57+
kind="bytes_bytes",
58+
size="dynamic",
59+
rules=acme_lz4_rules,
60+
)
61+
CONTEXT = CORE_AND_EXTENSIONS_CONTEXT.union(ZarrV3Context(codecs=[ACME_LZ4]))
62+
```
63+
64+
Pass `context=CONTEXT` to any reader or model constructor. A document that
65+
names `acme.lz4` is then checked against this definition.
66+
67+
## Rules for the pieces
68+
69+
**The `TypedDict`**
70+
71+
- It must be a `typing_extensions.TypedDict`.
72+
- It must say how undeclared keys are handled: `closed=True`,
73+
`extra_items=`, or `closed=False`. An open-by-default `TypedDict` is
74+
refused, because it would accept a misspelled key silently.
75+
76+
**Its members** must be JSON shapes:
77+
78+
- `int`, `float`, `bool`, `str`, `None` or `JSONValue`,
79+
- a `Literal`,
80+
- `tuple[T, ...]` or `tuple[T1, T2]`,
81+
- a union,
82+
- a `TypedDict` or `Mapping[str, V]`,
83+
- a `NewType` or a type alias.
84+
85+
A number may carry bounds from annotated-types: `Gt`, `Ge`, `Lt`, `Le` or
86+
`Interval`. Any other `Annotated` metadata is refused when the definition
87+
is built.
88+
89+
**The rules function**
90+
91+
- It runs only on a configuration within its bounds, so it can rely on
92+
them.
93+
- It reports the location of each problem. The value there becomes the
94+
problem's `input`.
95+
- Show values in messages with `zarr_metadata.problems.shown`. Then they
96+
read like the package's own messages.
97+
98+
**Pickling.** Define each function at module top level. A model holds the
99+
definitions that read it, so the model can be pickled only if they can.
100+
101+
## Other kinds of definition
102+
103+
Other kinds declare more:
104+
105+
- A data type declares its fill value (`fill_value`, `fill_value_rules`,
106+
`fill_value_canonical`). It also declares how its values are stored
107+
(`storage`).
108+
- A chunk grid declares which shapes it fits (`shape_rules`) and its chunk
109+
lengths (`chunk_lengths`).
110+
- A codec declares:
111+
- what it checks about the chunk it receives (`chunk_rules`),
112+
- what it hands on (`transition`),
113+
- any pipelines it contains (`pipelines`).
114+
115+
The [`zarr_metadata.v3.definition`](api/v3/definition.md) docstring
116+
describes each of these.

0 commit comments

Comments
 (0)