Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
88ab631
feat(experimental): add ArrayIO and AsyncArrayIO, an explicit-IO arra…
d-v-b Oct 29, 2026
c15b44a
docs: changelog fragment for #419
d-v-b Oct 29, 2026
a51bdbe
feat(experimental): give ArrayIO erasing inverses and a format-detect…
d-v-b Oct 29, 2026
c618bbe
feat(experimental): let ArrayIO verify its bound metadata against the…
d-v-b Oct 29, 2026
102a1ad
refactor(array): make AsyncArray wrap AsyncArrayIO
d-v-b Oct 29, 2026
ea0c01b
docs: note the AsyncArray delegation in the changelog fragment
d-v-b Oct 29, 2026
63c6ed7
feat(array): make ArrayIO public and reachable from every array handle
d-v-b Oct 29, 2026
adf48d2
feat(array): hold the ArrayIO document as a zarr-metadata model
d-v-b Oct 29, 2026
1b5b4ab
refactor(array): make ArrayIO.build synchronous and declare its surfa…
d-v-b Oct 31, 2026
8876ff8
refactor(array): build each format's metadata document with its own r…
d-v-b Oct 31, 2026
8956b96
refactor(array): name the model metadata and the resolved form metada…
d-v-b Oct 31, 2026
ee8b2d1
docs(array): finish the metadata wording on ArrayIO
d-v-b Oct 31, 2026
5b0a08a
fix(array): make ArrayIO meet the global configuration the way Array …
d-v-b Oct 31, 2026
19d2e63
refactor(array): name the v3 builder's data type field data_type
d-v-b Oct 31, 2026
87c1d63
docs(array): shorten the ArrayIO docstrings to what each function does
d-v-b Oct 31, 2026
07d5aea
fix(array): stop derived bindings aliasing their source's legacy meta…
d-v-b Oct 31, 2026
34421c9
fix(array): copy a legacy source's attributes when deriving a binding
d-v-b Oct 31, 2026
1546f2d
refactor(metadata): record stored documents in a per-store cache, not…
d-v-b Nov 1, 2026
a563673
build: raise the typing-extensions floor to 4.16, as zarr-metadata re…
d-v-b Nov 1, 2026
f396234
fix(metadata): carry a binding's stored-document entry through pickling
d-v-b Nov 1, 2026
6a39737
refactor(array): set a binding's fields in one place
d-v-b Nov 1, 2026
203d21b
fix(array): read a with_metadata legacy object as the constructor rea…
d-v-b Nov 1, 2026
e886f9e
docs(array): say how fresh the stored-document repair check is
d-v-b Nov 1, 2026
c42f815
fix(array): resolve v2 model attributes as plain JSON and check v2 ob…
d-v-b Nov 1, 2026
416c5a9
fix(array): keep AsyncArray's dataclass surface and read nothing new
d-v-b Nov 1, 2026
a9de177
fix(array): bind metadata dicts as models and settle ArrayIO's IO sig…
d-v-b Nov 1, 2026
0d28b55
fix(metadata): carry group cache entries, bound the cache, import mod…
d-v-b Nov 1, 2026
3c7b3dc
fix(array): reject storage transformers in bound dicts; pin cache sem…
d-v-b Nov 1, 2026
8af2f87
fix(array): take zarr-metadata JSON in bindings and rebuild pickled p…
d-v-b Nov 1, 2026
0f4bf45
fix(array): leave groups to erase, copy given metadata, name verify's…
d-v-b Nov 1, 2026
4be3936
fix(array): keep 3.4.x pickles repairing, and group writes as fast as…
d-v-b Nov 1, 2026
b6da26f
fix(array): reject storage transformers on every bind, erase corrupt …
d-v-b Nov 1, 2026
378fcf6
fix(array): name verify's fields over nested v2 attributes, reject v2…
d-v-b Nov 1, 2026
756cc2a
fix(array): parse no written document the cache does not need, keep s…
d-v-b Nov 1, 2026
a41599c
fix(array): give each AsyncArray its own state, check every bound v2 …
d-v-b Nov 1, 2026
ebd8f2c
fix(array): keep a binding's document as stored when an array reads it
d-v-b Nov 1, 2026
5c14148
docs(changes): say to_array binds a copy of the binding
d-v-b Nov 1, 2026
d3d0656
fix(array): rebind every binding through one hook, keep the model thr…
d-v-b Nov 1, 2026
e88c1a7
fix(array): honor subclass slots and reductions, read fields as MISSI…
d-v-b Nov 1, 2026
0c78637
fix(array): keep what a subclass __init__ built, pickle slots and mod…
d-v-b Nov 1, 2026
d072ad3
fix(array): keep a binding's pickled state under its own key, report …
d-v-b Nov 1, 2026
5529888
fix(array): bind a pickle's model only with its own metadata, honor s…
d-v-b Nov 1, 2026
bd9e271
fix(array): compare attributes with mixed key types, report a rejecte…
d-v-b Nov 1, 2026
b983870
fix(array): run the pipeline a rebound binding chose after with_config
d-v-b Nov 1, 2026
022ed23
test(array): refuse a parse with a Mock instead of a nested function
d-v-b Nov 2, 2026
5961941
fix(array): share a binding's first metadata resolution across threads
d-v-b Nov 2, 2026
2dea537
fix(array): read a subclass's pickled state in every form, keep attri…
d-v-b Nov 2, 2026
a79a325
fix(array): treat a pickled state as an array's only when it holds ev…
d-v-b Nov 2, 2026
3e21ba4
Merge branch 'main' into claude/arrayio-lazy-metadata-48d8e5
d-v-b Nov 3, 2026
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
6 changes: 6 additions & 0 deletions changes/419.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Added `zarr.ArrayIO` and `zarr.AsyncArrayIO`, an explicit-IO layer over Zarr arrays. Constructing one performs no IO against storage: storing metadata (`store_metadata`), storing or reading chunks (`setitem` / `getitem`), and their erasing inverses (`erase_metadata`, `erase_chunk`, `erase_chunks`) are explicit method calls that run in whatever order the caller chooses, so chunks can be written before metadata. The declaring constructors, `build_v2` and `build_v3`, are synchronous β€” binding is pure β€” take a `Store` or `StorePath`, and take the fields of each format's metadata document, with nothing hiding the difference between the formats; the documents themselves are built by the module-level `build_array_metadata_v2` and `build_array_metadata_v3`. `open` auto-detects the Zarr format by default, as `zarrs` does, and `verify` reads the stored metadata and raises, naming each differing field, when it does not match the metadata the instance is bound to.
`Array` and `AsyncArray` are built on this layer: every `AsyncArray` wraps an `AsyncArrayIO` and delegates its metadata and selection IO to it, the wrapped layer is reachable as `Array.io` / `AsyncArray.io`, and `to_array()` returns an array over a copy of the binding, so nothing done through the array (an attribute update, a resize) changes the binding it came from.
The binding holds its metadata as a `zarr-metadata` model β€” `zarr-python` now depends on `zarr-metadata`, which raises its minimum `typing-extensions` to 4.16 β€” reachable as `ArrayIO.metadata`, with `ArrayIO.metadata_legacy` the same metadata resolved into the legacy classes: metadata operations (`store_metadata`, `erase_metadata`, `erase_chunk`, `erase_chunks`, `verify`) never resolve codec or data type names, so an array whose codecs this environment lacks can still be opened, verified and erased, and names resolve into classes only when chunks are read or written.
`with_metadata`, `with_store` and `with_config` return new bindings with one part replaced.
`ArrayIO` responds to the global configuration the way `Array` always has: the codec pipeline implementation is captured when the binding is made, the runtime configuration defaults from `zarr.config`, and `build_v2`'s `order` defaults from `array.order`.
Whether a stored array document needs a repair that moves chunks (such as a stored chunk size of 0) is now recorded per store object, in a cache of the metadata documents zarr has read and stored, instead of on the metadata read from it. Every array bound to one store object shares what any of them learned, derived bindings carry nothing, and the first chunk write through any binding that knows the document, including a pickled one, stores the repair; nothing is read that was not read before.
2 changes: 2 additions & 0 deletions docs/api/zarr/array.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
::: zarr.Array
::: zarr.AsyncArray
::: zarr.ArrayIO
::: zarr.AsyncArrayIO
2 changes: 1 addition & 1 deletion docs/user-guide/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Required dependencies include:
- [numpy](https://numpy.org) (2.0 or later)
- [numcodecs](https://numcodecs.readthedocs.io/en/stable/) (0.14 or later)
- [google-crc32c](https://github.com/googleapis/python-crc32c) (1.5 or later)
- [typing_extensions](https://typing-extensions.readthedocs.io/en/latest/) (4.14 or later)
- [typing_extensions](https://typing-extensions.readthedocs.io/en/latest/) (4.16 or later)
- [donfig](https://donfig.readthedocs.io/en/latest/) (0.8 or later)

## pip
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ dependencies = [
# 0.16.6 and 0.17.1 refuse to unpickle untrusted data (GHSA-jqc8-qjfv-5m6r); 0.17.0 does not.
'numcodecs>=0.16.6,!=0.17.0',
'google-crc32c>=1.5',
'typing_extensions>=4.14',
'typing_extensions>=4.16',
'donfig>=0.8',
'msgspec>=0.19',
'zarr-metadata>=0.6.0',
]

dynamic = [
Expand Down Expand Up @@ -257,7 +258,7 @@ extra-dependencies = [
'fsspec==2023.10.0',
's3fs==2023.10.0',
'universal_pathlib==0.2.0',
'typing_extensions==4.14.*',
'typing_extensions==4.16.*',
'donfig==0.8.*',
'obstore==0.5.*',
'msgspec==0.19.*',
Expand Down
4 changes: 3 additions & 1 deletion src/zarr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
zeros,
zeros_like,
)
from zarr.core.array import Array, AsyncArray
from zarr.core.array import Array, ArrayIO, AsyncArray, AsyncArrayIO
from zarr.core.config import config
from zarr.core.group import AsyncGroup, Group

Expand Down Expand Up @@ -147,7 +147,9 @@ def set_format(log_format: str) -> None:

__all__ = [
"Array",
"ArrayIO",
"AsyncArray",
"AsyncArrayIO",
"AsyncGroup",
"Group",
"__version__",
Expand Down
7 changes: 6 additions & 1 deletion src/zarr/api/asynchronous.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
create_array,
from_array,
get_array_metadata,
observe_read,
)
from zarr.core.array_spec import ArrayConfigLike, parse_array_config
from zarr.core.buffer import NDArrayLike
Expand Down Expand Up @@ -425,7 +426,11 @@ async def open(
zarr_format = _metadata_dict["zarr_format"]
is_v3_array = zarr_format == 3 and _metadata_dict.get("node_type") == "array"
if is_v3_array or zarr_format == 2:
return AsyncArray(store_path=store_path, metadata=_metadata_dict, config=config)
array = AsyncArray(
store_path=store_path, metadata=_metadata_dict, config=config
)
observe_read(store_path, metadata_dict)
return array
except (FileNotFoundError, NodeTypeValidationError):
pass
_check_no_group_config(config, store_path)
Expand Down
Loading
Loading