Skip to content

Commit 093f96b

Browse files
committed
fix: preserve rectilinear grids in from_array
1 parent 66fae17 commit 093f96b

2 files changed

Lines changed: 51 additions & 10 deletions

File tree

src/zarr/core/array.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@
101101
Selection,
102102
VIndex,
103103
_iter_grid,
104-
_iter_regions,
105104
check_fields,
106105
check_no_multi_fields,
107106
is_pure_fancy_indexing,
@@ -126,6 +125,7 @@
126125
)
127126
from zarr.core.metadata.v3 import (
128127
ChunkGridMetadata,
128+
RegularChunkGridMetadata,
129129
create_chunk_grid_metadata,
130130
parse_node_type_array,
131131
)
@@ -4814,13 +4814,24 @@ def _parse_keep_array_attr(
48144814
dict[str, JSON] | None,
48154815
]:
48164816
if isinstance(data, Array):
4817+
# ``ChunkGrid.from_metadata`` represents rectilinear dimensions with
4818+
# uniform edges as ``FixedDimension``. Consequently,
4819+
# ``data._chunk_grid.is_regular`` does not preserve the grid kind
4820+
# recorded in v3 metadata, which is what ``chunks='keep'`` must copy.
4821+
if isinstance(data.metadata, ArrayV3Metadata):
4822+
metadata_has_regular_grid = isinstance(
4823+
data.metadata.chunk_grid, RegularChunkGridMetadata
4824+
)
4825+
else:
4826+
# Zarr format 2 always has a regular chunk grid.
4827+
metadata_has_regular_grid = True
48174828
if chunks == "keep":
4818-
if data._chunk_grid.is_regular:
4829+
if metadata_has_regular_grid:
48194830
chunks = data.chunks
48204831
else:
48214832
chunks = data.write_chunk_sizes
48224833
if shards == "keep":
4823-
shards = data.shards if data._chunk_grid.is_regular else None
4834+
shards = data.shards if metadata_has_regular_grid else None
48244835
if zarr_format is None:
48254836
zarr_format = data.metadata.zarr_format
48264837
if filters == "keep":
@@ -5293,13 +5304,12 @@ def _iter_shard_regions(
52935304
A tuple of slice objects representing the region spanned by each shard in the selection or chunk
52945305
when no shards are present.
52955306
"""
5296-
if array.shards is None:
5297-
shard_shape = array.chunks
5298-
else:
5299-
shard_shape = array.shards
5300-
5301-
return _iter_regions(
5302-
array.shape, shard_shape, origin=origin, selection_shape=selection_shape, trim_excess=True
5307+
# The array's metadata grid describes the outer storage layout in both
5308+
# sharded and non-sharded arrays. Using it directly supports rectilinear
5309+
# grids, for which ``chunks`` and ``shards`` are intentionally undefined.
5310+
return array._chunk_grid.iter_chunk_regions(
5311+
origin=origin,
5312+
selection_shape=selection_shape,
53035313
)
53045314

53055315

tests/test_array.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import re
88
import sys
99
from itertools import accumulate, starmap
10+
from pathlib import Path
1011
from typing import TYPE_CHECKING, Any, Literal
1112
from unittest import mock
1213

@@ -72,6 +73,7 @@
7273
from zarr.core.group import AsyncGroup
7374
from zarr.core.indexing import BasicIndexer, _iter_grid, _iter_regions
7475
from zarr.core.metadata.v2 import ArrayV2Metadata
76+
from zarr.core.metadata.v3 import RectilinearChunkGridMetadata
7577
from zarr.core.sync import sync
7678
from zarr.errors import (
7779
ContainsArrayError,
@@ -1889,6 +1891,35 @@ def test_from_array_arraylike_gains_no_attributes() -> None:
18891891
assert result.fill_value == 0
18901892

18911893

1894+
@pytest.mark.parametrize("store_type", ["memory", "local"])
1895+
def test_from_array_keeps_uniform_rectilinear_grid(
1896+
store_type: Literal["memory", "local"], tmp_path: Path
1897+
) -> None:
1898+
"""``chunks='keep'`` uses the stored v3 grid kind, not its runtime shape."""
1899+
with zarr.config.set({"array.rectilinear_chunks": True}):
1900+
if store_type == "memory":
1901+
source_store: MemoryStore | LocalStore = MemoryStore()
1902+
destination_store: MemoryStore | LocalStore = MemoryStore()
1903+
else:
1904+
source_store = LocalStore(tmp_path / "source.zarr")
1905+
destination_store = LocalStore(tmp_path / "destination.zarr")
1906+
1907+
source = zarr.create_array(
1908+
store=source_store,
1909+
shape=(24,),
1910+
chunks=[[10, 10, 4]],
1911+
dtype="i4",
1912+
zarr_format=3,
1913+
)
1914+
source[:] = np.arange(24, dtype="i4")
1915+
1916+
result = zarr.from_array(store=destination_store, data=source)
1917+
1918+
assert isinstance(result.metadata.chunk_grid, RectilinearChunkGridMetadata)
1919+
assert result.write_chunk_sizes == ((10, 10, 4),)
1920+
np.testing.assert_array_equal(result[:], source[:])
1921+
1922+
18921923
def test_from_array_F_order() -> None:
18931924
arr = zarr.create_array(store={}, data=np.array([1]), order="F", zarr_format=2)
18941925
with pytest.warns(

0 commit comments

Comments
 (0)