Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
219 commits
Select commit Hold shift + click to select a range
cd7d264
fix(chunk-grids): enforce one zero-length-axis invariant across model…
d-v-b Sep 9, 2026
a171507
fix(metadata): read a legacy v2 zero chunk edge on an empty axis as 1
d-v-b Sep 9, 2026
921be0d
chore: rename changelog fragment to the PR number
d-v-b Sep 9, 2026
047a92e
docs: state what 2.x actually did with a zero chunk edge
d-v-b Sep 9, 2026
7a2a71a
Merge branch 'main' into fix/zero-length-single-invariant
d-v-b Sep 12, 2026
2297c62
docs: qualify zero-chunk compatibility history
d-v-b Sep 13, 2026
2873178
Merge branch 'main' into fix/zero-length-single-invariant
d-v-b Sep 16, 2026
d868213
Merge branch 'main' into fix/zero-length-single-invariant
d-v-b Sep 16, 2026
92ead03
fix: read mixed regular/rectilinear chunk grids written by zarr 3.2.x
d-v-b Sep 18, 2026
7259833
docs: add changelog fragment for #4375
d-v-b Sep 18, 2026
955fdeb
fix: accept numpy integers in regular chunk grids; normalize tuples i…
d-v-b Sep 18, 2026
39b63d4
test: sample the full rectilinear chunk grid declaration space
d-v-b Sep 18, 2026
e118ab8
fix: make the chunk normalizer the one judge of a chunk specification
d-v-b Sep 18, 2026
9d0f413
docs: add changelog fragment for #4376
d-v-b Sep 18, 2026
0410129
docs: add changelog fragment for #4377
d-v-b Sep 18, 2026
f57d01b
refactor: branch on integer vs sequence when parsing rectilinear chun…
d-v-b Sep 19, 2026
38cecdb
docs: note the rectilinear parsing change in the changelog fragment
d-v-b Sep 19, 2026
087b62d
refactor: validate each chunk grid kind once, in one place
d-v-b Sep 19, 2026
4456d80
Merge branch 'main' into fix/zero-length-single-invariant
d-v-b Sep 19, 2026
49564dc
fix(metadata): read a legacy zero chunk size on an empty axis in Zarr…
d-v-b Sep 19, 2026
5bb1f97
refactor(metadata): check stored chunk shapes against the array shape…
d-v-b Sep 19, 2026
f93193d
refactor(metadata): scope the stored chunk shape check to regular chu…
d-v-b Sep 19, 2026
d69a6a5
test: tighten rectilinear strategy contracts from review
d-v-b Sep 24, 2026
a19718b
Merge remote-tracking branch 'upstream/main' into pr-4377
d-v-b Sep 24, 2026
f5ef3df
Merge branch 'fix/zero-length-single-invariant' (#4334) into test/rec…
d-v-b Sep 25, 2026
84b19cc
test: draw zero-length axes in the rectilinear declaration strategies
d-v-b Sep 25, 2026
e32fd4b
Merge branch 'fix/zero-length-single-invariant' (#4334) into fix/mixe…
d-v-b Sep 25, 2026
e324baa
refactor(metadata): read both legacy regular chunk grid forms in one …
d-v-b Sep 25, 2026
64b3550
Merge branch 'main' into fix/zero-length-single-invariant
d-v-b Sep 25, 2026
8414ea3
fix(metadata): read a stored zero chunk size on a grown axis as one s…
d-v-b Sep 25, 2026
2bf32df
test: a stateful test of one array's create/append/resize/write life
d-v-b Sep 25, 2026
42230e8
Merge branch 'fix/zero-length-single-invariant' (#4334) into test/rec…
d-v-b Sep 25, 2026
b2e3c99
Merge branch 'fix/zero-length-single-invariant' (#4334) into fix/mixe…
d-v-b Sep 25, 2026
1723f0b
fix(metadata): stop the zero chunk size warning from naming writers
d-v-b Sep 25, 2026
b75e1c0
Merge branch 'fix/zero-length-single-invariant' (#4334) into test/rec…
d-v-b Sep 25, 2026
8d672b6
Merge branch 'fix/zero-length-single-invariant' (#4334) into fix/mixe…
d-v-b Sep 25, 2026
09639f4
fix(metadata): stop the mixed chunk grid messages from naming releases
d-v-b Sep 25, 2026
3d223f1
chore(metadata): drop an orphaned comment in ArrayV2Metadata.__init__
d-v-b Sep 25, 2026
5b44dfc
Merge remote-tracking branch 'origin/main' into loop/4376
d-v-b Sep 25, 2026
d9d109d
refactor(chunk_grids): one integer test for chunk input
d-v-b Sep 25, 2026
dfd16ea
test: keep numpy chunk tests that cover real bugs; pin legacy v2 rect…
d-v-b Sep 25, 2026
626e227
docs: note legacy v2 chunks=0/[] and bool chunk sizes in the #4376 ch…
d-v-b Sep 25, 2026
339c514
fix(chunk-grids): size a full-span shard as a multiple of the inner c…
d-v-b Sep 25, 2026
e49eb7e
refactor(metadata): read invalid stored chunk sizes in one module of …
d-v-b Sep 25, 2026
f6f5c7b
docs: rewrite the 4334 changelog fragment and trim restating docstrings
d-v-b Sep 25, 2026
f873358
test: model exactly what the store holds in the array lifecycle state…
d-v-b Sep 25, 2026
960580d
Merge loop/4334 into w4377
d-v-b Sep 25, 2026
d277b47
Merge branch 'loop/4334' (#4334) into loop/4375
d-v-b Sep 25, 2026
ae7c295
refactor(metadata): warn about an upgraded document only once it vali…
d-v-b Sep 25, 2026
d17a848
fix(metadata): read a regular grid listing chunk edges as a document …
d-v-b Sep 25, 2026
fe4359a
test(strategies): drop the overhang parameter and trim rectilinear ev…
d-v-b Sep 25, 2026
578f935
test: check rectilinear properties against the declaration, not zarr'…
d-v-b Sep 25, 2026
9504416
test: round-trip data through a rectilinear zero-length axis as it grows
d-v-b Sep 25, 2026
60853fd
test: draw the state machine's rectilinear chunks from the shared str…
d-v-b Sep 25, 2026
8aef319
refactor(metadata): drop numpy and tuple widening from chunk grid met…
d-v-b Sep 25, 2026
d56ac40
docs: describe reading mixed regular grids without the rectilinear flag
d-v-b Sep 25, 2026
d9549b8
refactor(metadata): warn about an upgraded document only once it vali…
d-v-b Sep 25, 2026
7bd21ab
Merge loop/4334 into w4377
d-v-b Sep 25, 2026
355ce92
Merge loop/4334 into w4375
d-v-b Sep 25, 2026
27ff101
refactor(chunk_grids): explicit edge lists stay rectilinear in ChunkG…
d-v-b Sep 25, 2026
5ef0f51
refactor(metadata): one per-axis rule for stored chunk sizes, one rul…
d-v-b Sep 26, 2026
192f86c
refactor(chunk-grids): start chunk guessing from the one full-span rule
d-v-b Sep 26, 2026
2e878cf
test: run the array lifecycle state machine in the slow Hypothesis job
d-v-b Sep 26, 2026
17d6686
Merge loop/4334 into w4377
d-v-b Sep 26, 2026
0723ca3
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
8e0cfc2
fix(metadata): read a regular grid mixing sizes and edge lists per axis
d-v-b Sep 26, 2026
9c9ee76
fix(array): check that metadata can be stored before touching the store
d-v-b Sep 26, 2026
986576b
test: draw distinct rectilinear dividers without rejection
d-v-b Sep 26, 2026
c571b2a
refactor(testing): type rectilinear declarations with RectilinearDimS…
d-v-b Sep 26, 2026
0cc909e
test: draw the list form of rectilinear chunks= from rectilinear_chunks
d-v-b Sep 26, 2026
b16dae4
fix(chunk_grids): reject every spelling of a boolean chunk size with …
d-v-b Sep 26, 2026
c0d4b3c
fix(array): store upgraded metadata before writing the first chunk
d-v-b Sep 26, 2026
121b6b2
refactor(metadata): name an array one way in upgrade warnings
d-v-b Sep 26, 2026
c1353bf
refactor(metadata): one rule for bare sizes and edge lists; one noun …
d-v-b Sep 26, 2026
1b14c10
Merge branch 'loop/4334' into loop/4377
d-v-b Sep 26, 2026
285292b
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
0d640f8
refactor(testing)!: remove chunks_param_from_rectilinear
d-v-b Sep 26, 2026
84c85a6
refactor(testing): drop the extent-1 special case in rectilinear_dim_…
d-v-b Sep 26, 2026
d182618
fix: encode metadata before deleting store content
d-v-b Sep 26, 2026
d3a6eae
test: reject a run-length entry that is not a pair
d-v-b Sep 26, 2026
ab7d03b
perf(testing): bound the chunks a drawn rectilinear grid declares in …
d-v-b Sep 26, 2026
f081f1b
fix(array): store the upgrade of the current stored document before w…
d-v-b Sep 26, 2026
9e29333
test: prefer zero-length axes for stored chunk size 0 in the lifecycl…
d-v-b Sep 26, 2026
396558a
Merge loop/4334 into w4377
d-v-b Sep 26, 2026
d7a9696
refactor(testing): declare the rectilinear chunk limits and per-axis …
d-v-b Sep 26, 2026
57b840d
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
140a706
refactor: the caller that knows the node names it in gate errors
d-v-b Sep 26, 2026
299ecb3
fix(group): delete a consolidated member in place, after encoding wit…
d-v-b Sep 26, 2026
632ac2b
fix(metadata): keep accepting the chunk sizes zarr 3.4.0 accepted in …
d-v-b Sep 26, 2026
245acae
fix(array): leave a valid stored document as written on a stale handl…
d-v-b Sep 26, 2026
2d0121d
docs: describe the 4334 changes to metadata constructors for a patch …
d-v-b Sep 26, 2026
b5dbe10
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
8c6af46
test: expect the patch release's wording for non-integral chunk edges…
d-v-b Sep 26, 2026
82a132d
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
0ed0015
fix(testing): keep chunks_param_from_rectilinear in zarr.testing (pat…
d-v-b Sep 26, 2026
6caf00e
fix(testing): keep rectilinear_chunks drawing an edge list per dimens…
d-v-b Sep 26, 2026
f7c10df
docs: describe the 4377 strategy changes as additive for a patch release
d-v-b Sep 26, 2026
1c970d6
fix(chunk_grids): keep zarr 3.4.0's reading of bool chunk sizes and f…
d-v-b Sep 26, 2026
3fe6a83
docs: describe the 4376 changes for a patch release
d-v-b Sep 26, 2026
b0c0ad1
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
bdfa443
Merge loop/4376 into loop/minor-strict
d-v-b Sep 26, 2026
f65abeb
test: expect "must be an int" for chunk edges that are not ints
d-v-b Sep 26, 2026
7014593
fix(metadata)!: make metadata constructors strict about chunk sizes
d-v-b Sep 26, 2026
8987624
feat(testing)!: draw bare-int steps in rectilinear_chunks
d-v-b Sep 26, 2026
b25df41
refactor(testing)!: remove chunks_param_from_rectilinear
d-v-b Sep 26, 2026
01a1db4
fix(chunk_grids)!: reject every spelling of a boolean chunk size with…
d-v-b Sep 26, 2026
a4a2a5f
docs: add the changelog fragment for the strict chunk size release
d-v-b Sep 26, 2026
4c4fe21
fix(metadata): read float edges only in stored rectilinear documents
d-v-b Sep 26, 2026
d07c05b
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
c678c9c
test: read float edges in a regular grid that lists chunk edge lengths
d-v-b Sep 26, 2026
5862c18
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
f95b5f4
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
521af07
docs: say that stored float edges are still read in the strict release
d-v-b Sep 26, 2026
fad2230
fix(array): read one-element and empty numpy chunk arrays in legacy v…
d-v-b Sep 26, 2026
821331b
refactor(chunk_grids): let operator.index be the only integer test in…
d-v-b Sep 26, 2026
e1bdbfd
test(chunk_grids): read bool chunk sizes as rows of the normalizer table
d-v-b Sep 26, 2026
6fde402
test(codecs): pickle a ShardingCodec with an inner chunk size of 0
d-v-b Sep 26, 2026
b0ea125
fix(group): build every node before create_hierarchy deletes or store…
d-v-b Sep 26, 2026
a903d66
fix(metadata): warn only where the user must act; guard and refresh u…
d-v-b Sep 26, 2026
f78e115
test: expect the lifecycle machine's upgrade warning only on non-empt…
d-v-b Sep 26, 2026
f3d009e
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
820f083
fix(group): encode a group's consolidated metadata before storing mem…
d-v-b Sep 26, 2026
93a7e78
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
ceb31fd
test: pin chunks_param_from_rectilinear returning lists, as zarr 3.4.…
d-v-b Sep 26, 2026
5ab45df
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
5f46e19
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
538b02a
Merge loop/4376 into loop/minor-strict
d-v-b Sep 26, 2026
7cb5694
fix(metadata)!: take metadata objects as built, now that constructors…
d-v-b Sep 26, 2026
8d2a45e
fix(chunk_grids): say how to chunk automatically when chunks=True
d-v-b Sep 26, 2026
9ea76e4
test: pin the strict release's legacy v2 chunks, bool RLE counts and …
d-v-b Sep 26, 2026
91dc1fb
docs: describe what the strict release reverses and the stored shapes…
d-v-b Sep 26, 2026
dec2af3
refactor(array): name the legacy v2 chunks-given test as _v2_chunks_g…
d-v-b Sep 26, 2026
9016f32
test(array): pin 0-d and all-zero numpy chunks in legacy v2 create
d-v-b Sep 26, 2026
bc306aa
fix(metadata): leave a sharded chunk size of 0 unread when the inner …
d-v-b Sep 26, 2026
3bc802f
perf(metadata): read each upgraded member once, concurrently, and ado…
d-v-b Sep 26, 2026
7f5e5fd
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
76e334e
fix(group): store the consolidated metadata as it is after a deletion
d-v-b Sep 26, 2026
f78932d
refactor: name a node in errors as "Array 'path': what happened"
d-v-b Sep 26, 2026
04c5075
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
fd60747
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
1351bea
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
e1ff75b
Merge loop/4376 into loop/minor-strict
d-v-b Sep 26, 2026
e7f7fb1
fix(chunk_grids): say how to chunk automatically for chunks=None too
d-v-b Sep 26, 2026
3eb30f2
fix(group): name the consolidated member that cannot be read
d-v-b Sep 26, 2026
df9489a
refactor: one chunk normalization in _create and one chunk shape type
d-v-b Sep 26, 2026
ed52242
docs: list where the strict release still reads a stored true, and wh…
d-v-b Sep 26, 2026
dc50ec6
fix(group): adopt refreshed consolidated members in place
d-v-b Sep 26, 2026
cddb877
fix(metadata): raise the rectilinear flag error when refreshing a con…
d-v-b Sep 26, 2026
38181f8
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
b0628e6
fix(group): say nothing was stored when a consolidated member needs t…
d-v-b Sep 26, 2026
8753ef4
perf(group): read each upgraded member once when deleting a member
d-v-b Sep 26, 2026
958b31c
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
d9d4389
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
86df5c6
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
4b9f82c
fix(group): store a group's documents only; keep upgraded members as …
d-v-b Sep 26, 2026
9acf8ee
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
e00178f
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
c951586
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
ad1b266
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
65f3f8e
docs(group): say what consolidated metadata stores for an upgraded array
d-v-b Sep 26, 2026
1bedd32
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
0bd3c07
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
58cfe28
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
60faa8a
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
faa6efe
fix(array): clear the stored document whenever an array stores its me…
d-v-b Sep 26, 2026
e9e45bd
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
8ce9a91
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
dfffd3f
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
1a05f63
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
30d7ca6
test(metadata): pin that a failed metadata save keeps the stored docu…
d-v-b Sep 26, 2026
8d5785a
Merge loop/4334 into loop/4375
d-v-b Sep 26, 2026
d13c317
Merge loop/4334 into loop/4377
d-v-b Sep 26, 2026
79b51b7
Merge loop/4375 into loop/minor-strict
d-v-b Sep 26, 2026
7a008b3
Merge loop/4377 into loop/minor-strict
d-v-b Sep 26, 2026
b3e355a
chore: name the strict chunk size changelog fragment after its PR
d-v-b Sep 26, 2026
7a0154e
Merge remote-tracking branch 'up/main' into up/4334
d-v-b Sep 29, 2026
dcffa3d
Merge branch 'up/4334' into up/4375
d-v-b Sep 29, 2026
b8c6c5b
Merge branch 'up/4334' into up/4377
d-v-b Sep 29, 2026
86fca10
Merge remote-tracking branch 'up/main' into up/4376
d-v-b Sep 29, 2026
6707b45
fix(metadata): read a stored chunk size of 0 as 1, however long the axis
d-v-b Sep 29, 2026
07f5b22
Merge branch 'up/4334' into up/4375
d-v-b Sep 29, 2026
d43470f
Merge branch 'up/4334' into up/4377
d-v-b Sep 29, 2026
5687c55
Merge branch 'up/4334' into up/4431
d-v-b Sep 29, 2026
9e07aaf
fix(metadata): re-save only upgrades that move chunks, and read non-J…
d-v-b Sep 29, 2026
1a91532
Merge branch 'up/4334' into up/4375
d-v-b Sep 29, 2026
23231e6
fix(array): encode new array metadata before deleting an existing node
d-v-b Sep 29, 2026
8f1320b
Merge branch 'up/4334' into up/4377
d-v-b Sep 29, 2026
007f60c
fix(chunk-grids): accept sequences that only define __getitem__ and _…
d-v-b Sep 29, 2026
8ffd775
Merge branch 'up/4375' into up/4431
d-v-b Sep 29, 2026
f1b54f3
Merge branch 'up/4376' into up/4431
d-v-b Sep 29, 2026
e89e45b
Merge branch 'up/4377' into up/4431
d-v-b Sep 29, 2026
2b38f64
Merge remote-tracking branch 'up/main' into up/4431
d-v-b Sep 29, 2026
37fc614
fix(chunk-grids): reject NumPy booleans as chunk sizes on NumPy befor…
d-v-b Sep 29, 2026
d7ff723
test(metadata): type the rectilinear chunks passed to zarr.create in …
d-v-b Sep 29, 2026
cbafe50
Merge branch 'up/4375' into up/4431
d-v-b Sep 29, 2026
100040f
Merge branch 'up/4376' into up/4431
d-v-b Sep 29, 2026
c279840
Merge branch 'main' into fix/mixed-regular-chunk-grid-4374
d-v-b Sep 30, 2026
369a028
Merge branch 'main' into fix/strict-chunk-sizes
d-v-b Sep 30, 2026
5dccfb8
fix(metadata): recommend recreating an array whose stored chunk size …
d-v-b Sep 30, 2026
630d5fc
Merge branch 'work/4334' (recreate hint for stored chunk size 0)
d-v-b Sep 30, 2026
4ee795a
Merge branch 'work/4334' (recreate hint for stored chunk size 0)
d-v-b Sep 30, 2026
f766fb2
test(metadata): widen the abbreviated-message bound for the two hints
d-v-b Sep 30, 2026
eb66aa4
test(metadata): widen the abbreviated-message bound for the two hints
d-v-b Sep 30, 2026
fcc24bb
fix(metadata): give the recipe for recreating an array read from a st…
d-v-b Sep 30, 2026
4956f7a
Merge branch 'work/4334' into work/4375
d-v-b Sep 30, 2026
149d11b
Merge branch 'work/4334' into work/4431
d-v-b Sep 30, 2026
e88b6f9
test(metadata): widen the abbreviated-message bound for the recipe
d-v-b Sep 30, 2026
e69c5b6
test(metadata): widen the abbreviated-message bound for the recipe
d-v-b Sep 30, 2026
82d38e8
Merge branch 'main' into fix/mixed-regular-chunk-grid-4374
d-v-b Sep 30, 2026
c4eb475
Merge branch 'main' into fix/zero-length-single-invariant
d-v-b Oct 1, 2026
d542eb9
Merge the updated #4334 (main merged) into fix/mixed-regular-chunk-gr…
d-v-b Oct 1, 2026
8bea13c
Merge the updated #4334 (main merged) into fix/strict-chunk-sizes
d-v-b Oct 1, 2026
0992e94
refactor(metadata): call the lenient readings of stored documents rep…
d-v-b Oct 1, 2026
87c1af4
Merge the upgrade-to-repair rename from #4334
d-v-b Oct 1, 2026
299d284
Merge the upgrade-to-repair rename from #4334
d-v-b Oct 1, 2026
2400a8e
Merge branch 'main' into fix/mixed-regular-chunk-grid-4374
d-v-b Oct 1, 2026
e121db6
Merge branch 'main' into fix/strict-chunk-sizes
d-v-b Oct 1, 2026
01dd403
docs(changes): trim the #4375 fragment to what the PR adds over main
d-v-b Oct 1, 2026
e7eeb5c
Merge branch 'work/4375' into work/4431
d-v-b Oct 1, 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
3 changes: 3 additions & 0 deletions changes/4375.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Arrays whose stored `regular` chunk grid mixes chunk sizes with lists of chunk edge lengths, such as `"chunk_shape": [2, [5, 10, 5]]` (written by zarr 3.2.0 and 3.2.1 for `chunks=(2, (5, 10, 5))`, and as `[2, [5.0, 10.0, 5.0]]` for float edges), can be read again, without enabling `array.rectilinear_chunks`. The grid is read as the rectilinear chunk grid it describes, with a `ZarrUserWarning`; re-saving the array's metadata (or writing chunks to it) stores that rectilinear grid, which requires the flag. A group's consolidated metadata keeps such an array's metadata as it was stored.

The `array.rectilinear_chunks` flag now gates reading and storing array metadata that declares a rectilinear chunk grid, including in a group's consolidated metadata, instead of constructing `RectilinearChunkGridMetadata`. An operation that would store such metadata with the flag off (creating an array, `Array.resize`, deleting a member of a group with consolidated metadata, `create_hierarchy`) raises before it deletes or writes anything, and the error names the array, also when it is a member of a group's consolidated metadata.
1 change: 1 addition & 0 deletions changes/4376.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Chunk specifications are judged only by the chunk normalizer: the separate duck-typed check for rectilinear input is gone, so the Zarr format 2 and sharding restrictions apply to what the normalized grid says was declared. A chunk size is an integer by Python's integer protocol: numpy integer scalars and 0-d integer arrays are accepted wherever a chunk or shard shape is. The legacy `zarr.create(..., zarr_format=2)` no longer tests the truth value of a numpy array given as `chunks`, so numpy arrays work there. A non-integer scalar chunk specification raises a `TypeError` naming the problem. An explicit list of chunk edges declares a rectilinear dimension everywhere, including a stored rectilinear chunk grid passed as `chunks=` whose edges happen to be uniform, so the Zarr format 2 and sharding restrictions treat it the same whether it is given as lists or as metadata, and `zarr.create` stores it as a rectilinear chunk grid, as `create_array` does. A `RectilinearChunkGridMetadata` made only of bare integers declares no explicit edge list, so it is read as the regular grid it describes: it is accepted for Zarr format 2 and as the chunk shape of a sharded array.
1 change: 1 addition & 0 deletions changes/4377.misc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The hypothesis strategies in `zarr.testing.strategies` now sample the full space of rectilinear chunk grid declarations: `arrays()` passes a rectilinear grid to `create_array` either as metadata or as a `chunks=` specification mixing bare-int steps and explicit edge lists in any arrangement, and the new `rectilinear_chunk_shape_declarations` and `rectilinear_chunk_grids` cover stored metadata, including run-length encoded edge lists in canonical and arbitrary groupings and edges overhanging the array extent. `rectilinear_chunks` and `chunk_grids` also draw zero-length dimensions.
7 changes: 7 additions & 0 deletions changes/4431.removal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Metadata built in code is strict about chunk edge lengths: `ArrayV2Metadata(chunks=...)`, `RegularChunkGridMetadata`, `RectilinearChunkGridMetadata` and `ShardingCodec(chunk_shape=...)` take them as Python `int`s of at least 1. A chunk size of 0 now raises a `ValueError`, as in `ArrayV2Metadata(chunks=(0,))` for an empty array. Code that passes an array's shape as its chunk shape, such as `ArrayV2Metadata(chunks=arr.shape)` or `RegularChunkGridMetadata(chunk_shape=arr.shape)`, fails for an array with a zero-length axis: use `tuple(max(1, s) for s in arr.shape)`. A `bool` or a NumPy integer, including a scalar `chunks=np.int64(5)`, raises a `TypeError`, as a float already did, and so does a chunk shape that is not a list or tuple. This reverses what 3.4.1 said of these classes: `ArrayV2Metadata(chunks=(0,))` is no longer accepted, and a `bool` chunk edge length is no longer read as the `int` it equals. (This entry assumes 3.4.1 is released before this release.) The array creation functions still accept NumPy integers in `chunks=` and `shards=`.

This applies to stored metadata too wherever the stored-document repairs do not read it: a stored chunk shape that is a bare integer instead of a list, as in a Zarr format 2 `"chunks": 4` or a sharding codec's `"chunk_shape": 2`, which earlier releases read as a one-dimensional chunk shape, now raises a `TypeError` when the array is opened. A group whose consolidated metadata includes such an array does not open from its consolidated metadata at all, and the error names that member: open the group with `use_consolidated=False` to reach its other members. A stored `true` as a rectilinear chunk grid's bare step (`"chunk_shapes": [true]`) or as the count of a run-length pair (`[[4, true]]`), which no known writer stored, raises a `TypeError` too. The chunk sizes the repairs read open as before: 0 or `false` as a regular chunk size (in Zarr format 2 `chunks` or a `regular` grid's `chunk_shape`); `true` as a regular chunk size, as an edge in a rectilinear edge list, as the size of a run-length pair, or as a sharding codec's inner chunk size; and integral float edges in a rectilinear chunk grid (`[[4.0, 2]]`).

Other than `chunks=False` or `shards=False` as the whole specification (one chunk spanning every axis), a boolean anywhere in a chunk specification given to the array creation functions (`chunks=True`, `np.True_`, `np.False_`, a boolean array, `chunks=(True, 5)`, `chunks=(False, 5)`, or a boolean edge in a list) raises one `TypeError`, instead of being read as a chunk size of 1 or 0 or raising a different error for each spelling. `chunks=True` and `create_array(chunks=None)` raised a `ValueError` before; both errors say how to ask for automatic chunking. The legacy `zarr.create(..., zarr_format=2)` no longer reads a falsy `chunks` as not given: `chunks=0`, `np.int64(0)` and `chunks=[]` raise a `ValueError`, and `chunks=False` means one chunk spanning the array, as it does for Zarr format 3, instead of automatic chunking; `chunks=None` still chunks automatically.

`zarr.testing.strategies.chunks_param_from_rectilinear` is removed; nothing in zarr used it after `arrays()` began drawing its `chunks=` argument from `rectilinear_chunks`. `rectilinear_chunks` now returns `list[int | list[int]]` instead of `list[list[int]]`: a dimension may be a bare int step, which can exceed the extent, so code that treats every dimension as a list of edges (`len(dim)`, `sum(dim)`) must handle ints. It also requires `shape` to have at least one dimension.
10 changes: 5 additions & 5 deletions src/zarr/codecs/sharding.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
merge_and_encode_chunk,
)
from zarr.core.common import (
ShapeLike,
ChunkShape,
parse_chunk_shape,
parse_named_configuration,
parse_shapelike,
product,
)
from zarr.core.config import config as zarr_config
Expand Down Expand Up @@ -459,7 +459,7 @@ class ShardingCodec(
def __init__(
self,
*,
chunk_shape: ShapeLike,
chunk_shape: ChunkShape,
codecs: Iterable[Codec | dict[str, JSON]] = (BytesCodec(endian="little"),),
index_codecs: Iterable[Codec | dict[str, JSON]] = (
BytesCodec(endian="little"),
Expand All @@ -468,7 +468,7 @@ def __init__(
index_location: ShardingCodecIndexLocation | IndexLocation = "end",
subchunk_write_order: SubchunkWriteOrder = "morton",
) -> None:
chunk_shape_parsed = parse_shapelike(chunk_shape)
chunk_shape_parsed = parse_chunk_shape(chunk_shape)
codecs_parsed = parse_codecs(codecs)
index_codecs_parsed = parse_codecs(index_codecs)
_check_index_codecs_fixed_size(index_codecs_parsed)
Expand Down Expand Up @@ -507,7 +507,7 @@ def __getstate__(self) -> dict[str, Any]:

def __setstate__(self, state: dict[str, Any]) -> None:
config = state["configuration"]
object.__setattr__(self, "chunk_shape", parse_shapelike(config["chunk_shape"]))
object.__setattr__(self, "chunk_shape", parse_chunk_shape(config["chunk_shape"]))
object.__setattr__(self, "codecs", parse_codecs(config["codecs"]))
object.__setattr__(self, "index_codecs", parse_codecs(config["index_codecs"]))
object.__setattr__(self, "index_location", _parse_index_location(config["index_location"]))
Expand Down
83 changes: 36 additions & 47 deletions src/zarr/core/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
ChunkGrid,
_is_auto,
_is_keep,
_is_rectilinear_chunks,
guess_chunks,
normalize_chunks_nd,
resolve_outer_and_inner_chunks,
Expand Down Expand Up @@ -120,10 +119,12 @@
)
from zarr.core.metadata.io import (
ARRAY_DOCUMENTS,
encode_documents,
parse_stored_array,
read_documents,
save_metadata,
save_new_metadata,
store_documents,
upsert_metadata,
)
from zarr.core.metadata.v2 import (
Expand Down Expand Up @@ -210,14 +211,8 @@ def _chunk_sizes_from_shape(

def parse_array_metadata(data: Any, path: str | None = None) -> ArrayMetadata:
"""Array metadata from a metadata object or a metadata document, naming the array at
`path` in warnings about how an invalid document was read.

`ArrayV2Metadata` accepts a chunk size of 0, as it always has, though only an
invalid document holds one: such metadata is read as the documents it would store
are (see `zarr.core.metadata.repair`), so an array can be built from it. No data
was read or written under that chunk size, so the reading is silent."""
if isinstance(data, ArrayV2Metadata) and 0 in data.chunks:
return parse_stored_array(data.to_buffer_dict(default_buffer_prototype()), 2)
`path` in warnings about how an invalid document was read. A metadata object is
valid as built: the metadata constructors are strict."""
if isinstance(data, ArrayMetadata):
return data
if isinstance(data, dict):
Expand Down Expand Up @@ -463,6 +458,11 @@ async def _create(

# Unify the v2 (chunks) and v3 (chunk_shape) parameter names
_raw_chunks = chunks if chunks is not None else chunk_shape
if _raw_chunks is None:
item_size = dtype_parsed.item_size if isinstance(dtype_parsed, HasItemSize) else 1
outer_chunks = guess_chunks(shape, item_size)
else:
outer_chunks = normalize_chunks_nd(_raw_chunks, shape)

config_parsed = parse_array_config(config)

Expand All @@ -484,13 +484,6 @@ async def _create(
if order is not None:
_warn_order_kwarg()

item_size = 1
if isinstance(dtype_parsed, HasItemSize):
item_size = dtype_parsed.item_size
if _raw_chunks is None:
outer_chunks = guess_chunks(shape, item_size)
else:
outer_chunks = normalize_chunks_nd(_raw_chunks, shape)
chunk_grid = create_chunk_grid_metadata(outer_chunks)
result = await cls._create_v3(
store_path,
Expand All @@ -516,17 +509,8 @@ async def _create(
)
if dimension_names is not None:
raise ValueError("dimension_names cannot be used for arrays with zarr_format 2.")
if _is_rectilinear_chunks(_raw_chunks):
if not outer_chunks.is_regular:
raise ValueError("Zarr format 2 does not support rectilinear chunk grids.")

item_size = 1
if isinstance(dtype_parsed, HasItemSize):
item_size = dtype_parsed.item_size
_raw = chunks or chunk_shape
if _raw is None:
outer_chunks = guess_chunks(shape, item_size)
else:
outer_chunks = normalize_chunks_nd(_raw, shape)
_chunks = outer_chunks.chunk_shape

if order is None:
Expand Down Expand Up @@ -646,7 +630,6 @@ async def _create_v3(
dimension_names=dimension_names,
attributes=attributes,
)

array = cls(metadata=metadata, store_path=store_path, config=config)
await save_new_metadata(store_path, metadata, overwrite=overwrite, ensure_parents=True)
return array
Expand Down Expand Up @@ -727,7 +710,6 @@ async def _create_v2(
compressor=compressor_parsed,
attributes=attributes,
)

array = cls(metadata=metadata, store_path=store_path, config=config)
await save_new_metadata(store_path, metadata, overwrite=overwrite, ensure_parents=True)
return array
Expand Down Expand Up @@ -1607,7 +1589,9 @@ async def get_coordinate_selection(

async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = False) -> None:
"""Store `metadata` as this array's own documents, then clear the
`_stored_document` mark (see `_stored_document_replaced`)."""
`_stored_document` mark (see `_stored_document_replaced`). `_resize` stores the
documents it encoded before deleting chunks directly, then clears the mark the
same way."""
await save_metadata(self.store_path, metadata, ensure_parents=ensure_parents)
self._stored_document_replaced()

Expand Down Expand Up @@ -1642,14 +1626,14 @@ async def _store_repaired_document(self) -> None:
zarr_format = self.metadata.zarr_format
documents = await read_documents(self.store_path, ARRAY_DOCUMENTS[zarr_format])
try:
current = parse_stored_array(documents, zarr_format)
current = parse_stored_array(documents, zarr_format, str(self.store_path))
except ArrayNotFoundError:
pass
else:
if _chunk_layout(current) != _chunk_layout(self.metadata):
raise ValueError(
f"The metadata stored for the array at {str(self.store_path)!r} has "
"changed since this array was opened: reopen the array to write to it."
f"Array {str(self.store_path)!r}: the metadata stored has changed since "
"this array was opened; reopen the array to write to it. Nothing was stored."
)
if current._stored_document is not None:
await upsert_metadata(self.store_path, current, documents)
Expand Down Expand Up @@ -4568,8 +4552,17 @@ async def init_array(
chunk_key_encoding, zarr_format=zarr_format
)

# Validate rectilinear chunks constraints
if _is_rectilinear_chunks(chunks):
# Normalize the user's chunks into a canonical ChunkGrid

if _is_auto(chunks):
max_bytes = None if shards is None else SHARDED_INNER_CHUNK_MAX_BYTES
chunks_normalized = guess_chunks(shape_parsed, item_size, max_bytes=max_bytes)
else:
chunks_normalized = normalize_chunks_nd(chunks, shape_parsed)

# Validate rectilinear chunks constraints. The normalized grid is the one
# judge of what the user declared.
if not chunks_normalized.is_regular:
if zarr_format == 2:
raise ValueError("Zarr format 2 does not support rectilinear chunk grids.")
if shards is not None:
Expand All @@ -4579,14 +4572,6 @@ async def init_array(
"chunks=(inner_size, ...), shards=[[shard_sizes], ...]"
)

# Normalize the user's chunks into a canonical ChunkGrid

if _is_auto(chunks):
max_bytes = None if shards is None else SHARDED_INNER_CHUNK_MAX_BYTES
chunks_normalized = guess_chunks(shape_parsed, item_size, max_bytes=max_bytes)
else:
chunks_normalized = normalize_chunks_nd(chunks, shape_parsed)

# Resolve chunks + shards into outer_chunks (grid metadata) and
# inner (sub-chunk structure for ShardingCodec, None if no sharding)
outer_chunks, inner = resolve_outer_and_inner_chunks(
Expand Down Expand Up @@ -4908,10 +4893,9 @@ def _stored_rectilinear_grid_or_none(
"""The *stored* rectilinear chunk grid, or None if the stored grid is regular
(in which case `.chunks` and `.shards` are defined).

Dispatches on the stored metadata, not the runtime ``ChunkGrid``: the
runtime grid collapses a rectilinear dimension whose edges happen to be
uniform to a ``FixedDimension`` as an optimization, so it can report regular
for an array whose stored metadata — and therefore `.chunks` — is
Dispatches on the stored metadata, not the runtime `ChunkGrid`: a
rectilinear grid whose dimensions are all bare-int step sizes is regular at
runtime, while its stored metadata — and therefore `.chunks` — is
rectilinear. Zarr format 2 grids are always regular.
"""
if isinstance(metadata, ArrayV3Metadata) and isinstance(
Expand Down Expand Up @@ -5904,6 +5888,10 @@ async def _resize(
# ensure deletion is only run if array is shrinking as the delete_outside_chunks path is unbounded in memory
only_growing = all(new >= old for new, old in zip(new_shape, array.metadata.shape, strict=True))

# Encode the new metadata before deleting any chunk: metadata that cannot be stored
# then fails with the store untouched.
documents = encode_documents(array.store_path, new_metadata)

if delete_outside_chunks and not only_growing:
# Remove all chunks outside of the new shape
old_chunk_coords = set(array._chunk_grid.all_chunk_coords())
Expand All @@ -5922,7 +5910,8 @@ async def _delete_key(key: str) -> None:
)

# Write new metadata
await array._save_metadata(new_metadata)
await store_documents(array.store_path, documents)
array._stored_document_replaced()

# Update metadata and chunk_grid (in place)
object.__setattr__(array, "metadata", new_metadata)
Expand Down
Loading
Loading