Skip to content

Commit 91778d4

Browse files
authored
Merge branch 'main' into docs/issue-4159-array-bytes-codec
2 parents cb161fd + 015732f commit 91778d4

63 files changed

Lines changed: 6582 additions & 551 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/needs_release_notes.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
pull-requests: write # Required to add labels to PRs
2222
runs-on: ubuntu-latest
2323
steps:
24-
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
24+
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
2525
with:
2626
repo-token: ${{ secrets.GITHUB_TOKEN }}
2727
sync-labels: true

‎.github/workflows/releases.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ jobs:
8181
name: releases
8282
path: dist
8383
- name: Generate artifact attestation
84-
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
84+
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
8585
with:
8686
subject-path: dist/*
8787
- name: Publish package to PyPI

‎.github/workflows/zarr-metadata-release.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
path: dist
8383

8484
- name: Generate artifact attestation
85-
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
85+
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
8686
with:
8787
subject-path: dist/*
8888

@@ -107,7 +107,7 @@ jobs:
107107
path: dist
108108

109109
- name: Generate artifact attestation
110-
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
110+
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
111111
with:
112112
subject-path: dist/*
113113

‎.github/workflows/zarr-metadata.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ jobs:
8282
- name: Sync test dependency group
8383
run: uv sync --group test --python 3.11
8484
- name: Run pyright
85-
run: uv run --group test --with pyright pyright src
85+
# Pinned to the last version that types PEP 661 sentinels in class
86+
# attributes correctly; 1.1.405+ regressed (microsoft/pyright#11115).
87+
# Unpin when the fix lands.
88+
run: uv run --group test --with 'pyright==1.1.404' pyright src
8689

8790
zarr-metadata-complete:
8891
name: zarr-metadata complete

‎.github/workflows/zizmor.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ jobs:
3232
persist-credentials: false
3333

3434
- name: Run zizmor
35-
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
35+
uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0

‎changes/4128.feature.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added `Group.get_array`, `Group.get_group`, `AsyncGroup.get_array`, and `AsyncGroup.get_group`: type-safe accessors that return the child array or group at a given path, raising `ArrayNotFoundError` / `GroupNotFoundError` if no node exists there, and `ContainsGroupError` / `ContainsArrayError` if the node is not of the requested kind. Unlike `Group.__getitem__`, which returns `Array | Group`, these methods have precise return types. Nested paths like `"subgroup/subarray"` are supported.

‎changes/4187.feature.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
`ZipStore` now accepts an open binary file-like object in place of a path, enabling
2+
zip archives on remote storage (e.g. a file opened with `fsspec` or an
3+
`obstore.ReadableFile`). Operations that require a filesystem location
4+
(`clear`, `move`) raise `NotImplementedError` for file-object-backed stores.

‎docs/user-guide/groups.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ print(root['foo/bar'])
5151
print(root['foo/bar/baz'])
5252
```
5353

54+
Accessing a member with `[]` returns either an [`zarr.Array`][] or a [`zarr.Group`][], depending on
55+
what is stored at the given path. When you expect a node of a particular kind, use
56+
[`zarr.Group.get_array`][] or [`zarr.Group.get_group`][] instead. These methods accept the same
57+
paths as `[]`, but they have precise return types and raise an error if no node exists at the
58+
given path, or if the node is not of the expected kind:
59+
60+
```python exec="true" session="groups" source="above" result="ansi"
61+
print(root.get_group('foo'))
62+
```
63+
64+
```python exec="true" session="groups" source="above" result="ansi"
65+
print(root.get_array('foo/bar/baz'))
66+
```
67+
68+
```python exec="true" session="groups" source="above" result="ansi"
69+
from zarr.errors import ContainsGroupError
70+
71+
try:
72+
root.get_array('foo')
73+
except ContainsGroupError as e:
74+
print(e)
75+
```
76+
5477
The [`zarr.Group.tree`][] method can be used to print a tree
5578
representation of the hierarchy, e.g.:
5679

‎docs/user-guide/storage.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ array = zarr.create_array(store=store, shape=(2,), dtype='float64')
124124
print(array)
125125
```
126126

127+
In place of a path, `ZipStore` also accepts an open binary file object (for
128+
example a file opened with `fsspec`, or an `obstore` reader), enabling zip
129+
archives on remote storage. The file must stay open for as long as the store
130+
is in use:
131+
132+
```python exec="true" session="storage" source="above" result="ansi"
133+
store.close()
134+
f = open('data.zip', mode='rb') # must stay open while the store is used
135+
array = zarr.open_array(store=zarr.storage.ZipStore(f), mode='r')
136+
print(array[:])
137+
f.close()
138+
```
139+
127140
### Remote Store
128141

129142
The [`zarr.storage.FsspecStore`][] stores the contents of a Zarr hierarchy following the same

‎packages/zarr-metadata/README.md‎

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,86 @@
11
# zarr-metadata
22

3-
Python type definitions for Zarr v2 and v3 metadata.
3+
Python types, models, and validators for Zarr v2 and v3 metadata.
44

55
## What this is
66

7-
A typed-data package: `TypedDict` definitions and `Literal` aliases for the
8-
JSON shapes specified by the [Zarr v2](https://zarr-specs.readthedocs.io/en/latest/v2/v2.0.html)
9-
and [Zarr v3](https://zarr-specs.readthedocs.io/en/latest/v3/core/index.html)
10-
specifications, plus types for [`zarr-extensions`](https://github.com/zarr-developers/zarr-extensions/)
11-
and a few widely-used-but-unspecified entities (e.g. consolidated metadata).
7+
Two layers and an optional integration:
8+
9+
- **Typed JSON shapes**: `TypedDict` definitions and `Literal` aliases for the
10+
JSON documents specified by the [Zarr v2](https://zarr-specs.readthedocs.io/en/latest/v2/v2.0.html)
11+
and [Zarr v3](https://zarr-specs.readthedocs.io/en/latest/v3/core/index.html)
12+
specifications, plus types for [`zarr-extensions`](https://github.com/zarr-developers/zarr-extensions/)
13+
and a few widely-used-but-unspecified entities (e.g. consolidated metadata).
14+
- **Document models** (`zarr_metadata.model`): canonical frozen-dataclass
15+
models of whole metadata documents, with structural validators, loc-aware
16+
parsers, and store-key (de)serialization. A document produced by `to_json`
17+
shares no mutable state with the model that produced it.
18+
- **Optional Pydantic integration** (`zarr_metadata.pydantic`, requires
19+
Pydantic 2.13 or newer): each model as a Pydantic field type that validates
20+
raw documents through the same strict parser.
1221

1322
## What this is for
1423

15-
These types describe the JSON shape of Zarr metadata. They are
16-
intended for libraries that **read, write, validate, or transform**
17-
Zarr metadata. Pair them with a runtime validator like
18-
[pydantic](https://docs.pydantic.dev/) to check JSON loaded from disk:
24+
The public `TypedDict` definitions describe the static JSON shape of Zarr
25+
metadata. For strict, loc-aware validation of JSON loaded from disk, use the
26+
model parser:
1927

2028
```python
2129
import json
22-
from pydantic import TypeAdapter
23-
from zarr_metadata.v3.array import ArrayMetadataV3
30+
from zarr_metadata.model import ZarrV3ArrayMetadata
2431

2532
with open("zarr.json", "rb") as f:
2633
raw = json.load(f)
2734

28-
metadata = TypeAdapter(ArrayMetadataV3).validate_python(raw)
35+
metadata = ZarrV3ArrayMetadata.from_json(raw)
36+
```
37+
38+
The optional Pydantic integration delegates raw input to the same strict
39+
parser and returns the same normalized model class:
40+
41+
```python
42+
from pydantic import TypeAdapter
43+
import zarr_metadata.pydantic as zmp
44+
45+
metadata = TypeAdapter(zmp.ZarrV3ArrayMetadata).validate_python(raw)
46+
encoded = metadata.to_key_value()["zarr.json"]
2947
```
3048

31-
## What this is *not*
49+
A bare `TypeAdapter` over a public document `TypedDict` is a coercive shape
50+
adapter, not a Zarr conformance validator; it may coerce values or discard
51+
members that the strict model parser rejects.
52+
53+
## Validation boundary
3254

33-
- Not a parser or builder. There are no `make_array_metadata(...)` factories —
34-
that surface belongs to consumer libraries.
35-
- Not a runtime validator on its own. Pair with `pydantic`, `msgspec`, or
36-
similar to enforce shapes at decode time.
55+
The model validators enforce the declared document structure and a small set
56+
of context-free consistency rules, including fixed format literals, finite
57+
JSON numbers, non-negative dimensions, non-empty v3 codec pipelines, and one
58+
`dimension_names` entry per array dimension. They do not interpret extension
59+
names or configurations, resolve codec pipelines, or decide whether a data
60+
type, chunk grid, codec, or storage transformer is supported. Those decisions
61+
belong to consumer implementations.
3762

38-
Even with a runtime validator, these types only describe **structural**
39-
shape — they will not flag *semantically* invalid metadata, like a 3D v3
40-
array whose `dimension_names` has 4 entries instead of 3. That's a job
41-
for downstream validator routines.
63+
The Pydantic integration's generated JSON Schemas express independently
64+
checkable document structure and field constraints, but they are not a
65+
replacement for runtime model validation. Standard JSON Schema treats a
66+
mathematically integral number such as `1.0` as an integer, while the runtime
67+
boundary requires Python `int` values, and it cannot express arbitrary
68+
same-length relations such as `dimension_names` versus `shape` or v2 `chunks`
69+
versus `shape`. Consumers should run the model parser after schema validation.
4270

4371
## Scope
4472

4573
At minimum, this library supports what Zarr-Python needs: the complete
4674
Zarr v2 and v3 specs, consolidated metadata, and a subset of the metadata
4775
defined in `zarr-extensions`. We are generally open to contributions that
48-
add types for Zarr metadata with a published spec.
76+
add types, models, or structural validation for Zarr metadata with a
77+
published spec.
78+
79+
Runtime array behavior is out of scope: nothing here encodes or decodes
80+
chunks, resolves codec or data type names to implementations, or performs
81+
store I/O. The models begin and end at the metadata documents themselves —
82+
`from_key_value` / `to_key_value` map documents to store keys and bytes,
83+
and everything past that belongs to consumer libraries.
4984

5085
## Releasing
5186

0 commit comments

Comments
 (0)