Skip to content

Commit f1c5182

Browse files
committed
Add open support
1 parent 994e329 commit f1c5182

File tree

2 files changed

+60
-15
lines changed

2 files changed

+60
-15
lines changed

src/zarr/core/array.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -675,13 +675,30 @@ async def _create(
675675

676676
if chunks is not None and chunk_shape is not None:
677677
raise ValueError("Only one of chunk_shape or chunks can be provided.")
678-
item_size = 1
679-
if isinstance(dtype_parsed, HasItemSize):
680-
item_size = dtype_parsed.item_size
681-
if chunks:
682-
_chunks = normalize_chunks(chunks, shape, item_size)
678+
679+
# detect rectilinear (dask-style) chunks before normalize_chunks flattens them
680+
from zarr.core.chunk_grids import _is_rectilinear_chunks
681+
682+
_raw_chunks = chunks if chunks is not None else chunk_shape
683+
_rectilinear_chunk_grid: ChunkGridMetadata | None = None
684+
_chunks: tuple[int, ...] | None = None
685+
if _is_rectilinear_chunks(_raw_chunks):
686+
from zarr.core.metadata.v3 import (
687+
RectilinearChunkGrid as RectilinearChunkGridMeta,
688+
)
689+
690+
_rectilinear_chunk_grid = RectilinearChunkGridMeta(
691+
chunk_shapes=tuple(tuple(c) for c in _raw_chunks)
692+
)
683693
else:
684-
_chunks = normalize_chunks(chunk_shape, shape, item_size)
694+
item_size = 1
695+
if isinstance(dtype_parsed, HasItemSize):
696+
item_size = dtype_parsed.item_size
697+
if chunks:
698+
_chunks = normalize_chunks(chunks, shape, item_size)
699+
else:
700+
_chunks = normalize_chunks(chunk_shape, shape, item_size)
701+
685702
config_parsed = parse_array_config(config)
686703

687704
result: AnyAsyncArray
@@ -714,6 +731,7 @@ async def _create(
714731
attributes=attributes,
715732
overwrite=overwrite,
716733
config=config_parsed,
734+
chunk_grid=_rectilinear_chunk_grid,
717735
)
718736
elif zarr_format == 2:
719737
if codecs is not None:
@@ -726,6 +744,9 @@ async def _create(
726744
)
727745
if dimension_names is not None:
728746
raise ValueError("dimension_names cannot be used for arrays with zarr_format 2.")
747+
if _rectilinear_chunk_grid is not None:
748+
raise ValueError("Zarr format 2 does not support rectilinear chunk grids.")
749+
assert _chunks is not None
729750

730751
if order is None:
731752
order_parsed = config_parsed.order
@@ -760,7 +781,7 @@ async def _create(
760781
def _create_metadata_v3(
761782
shape: ShapeLike,
762783
dtype: ZDType[TBaseDType, TBaseScalar],
763-
chunk_shape: tuple[int, ...],
784+
chunk_shape: tuple[int, ...] | None = None,
764785
fill_value: Any | None = DEFAULT_FILL_VALUE,
765786
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
766787
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
@@ -771,8 +792,12 @@ def _create_metadata_v3(
771792
"""
772793
Create an instance of ArrayV3Metadata.
773794
774-
If `chunk_grid` is provided, it takes precedence over `chunk_shape`.
795+
Exactly one of ``chunk_shape`` or ``chunk_grid`` must be provided.
775796
"""
797+
if chunk_shape is not None and chunk_grid is not None:
798+
raise ValueError("Only one of chunk_shape or chunk_grid can be provided.")
799+
if chunk_shape is None and chunk_grid is None:
800+
raise ValueError("One of chunk_shape or chunk_grid must be provided.")
776801
filters: tuple[ArrayArrayCodec, ...]
777802
compressors: tuple[BytesBytesCodec, ...]
778803

@@ -799,11 +824,12 @@ def _create_metadata_v3(
799824
else:
800825
fill_value_parsed = fill_value
801826

802-
chunk_grid_meta = (
803-
chunk_grid
804-
if chunk_grid is not None
805-
else RegularChunkGrid(chunk_shape=parse_shapelike(chunk_shape))
806-
)
827+
chunk_grid_meta: ChunkGridMetadata
828+
if chunk_grid is not None:
829+
chunk_grid_meta = chunk_grid
830+
else:
831+
assert chunk_shape is not None # validated above
832+
chunk_grid_meta = RegularChunkGrid(chunk_shape=parse_shapelike(chunk_shape))
807833
return ArrayV3Metadata(
808834
shape=shape,
809835
data_type=dtype,
@@ -822,7 +848,7 @@ async def _create_v3(
822848
*,
823849
shape: ShapeLike,
824850
dtype: ZDType[TBaseDType, TBaseScalar],
825-
chunk_shape: tuple[int, ...],
851+
chunk_shape: tuple[int, ...] | None = None,
826852
config: ArrayConfig,
827853
fill_value: Any | None = DEFAULT_FILL_VALUE,
828854
chunk_key_encoding: (
@@ -835,7 +861,12 @@ async def _create_v3(
835861
dimension_names: DimensionNamesLike = None,
836862
attributes: dict[str, JSON] | None = None,
837863
overwrite: bool = False,
864+
chunk_grid: ChunkGridMetadata | None = None,
838865
) -> AsyncArrayV3:
866+
if chunk_shape is not None and chunk_grid is not None:
867+
raise ValueError("Only one of chunk_shape or chunk_grid can be provided.")
868+
if chunk_shape is None and chunk_grid is None:
869+
raise ValueError("One of chunk_shape or chunk_grid must be provided.")
839870
if overwrite:
840871
if store_path.store.supports_deletes:
841872
await store_path.delete_dir()
@@ -860,6 +891,7 @@ async def _create_v3(
860891
codecs=codecs,
861892
dimension_names=dimension_names,
862893
attributes=attributes,
894+
chunk_grid=chunk_grid,
863895
)
864896

865897
array = cls(metadata=metadata, store_path=store_path, config=config)
@@ -4902,7 +4934,7 @@ async def init_array(
49024934
shape=shape_parsed,
49034935
dtype=zdtype,
49044936
fill_value=fill_value,
4905-
chunk_shape=chunks_out,
4937+
chunk_shape=chunks_out if rectilinear_meta is None else None,
49064938
chunk_key_encoding=chunk_key_encoding_parsed,
49074939
codecs=codecs_out,
49084940
dimension_names=dimension_names,

tests/test_api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,19 @@ async def test_open_array(memory_store: MemoryStore, zarr_format: ZarrFormat) ->
280280
zarr.api.synchronous.open(store="doesnotexist", mode="r", zarr_format=zarr_format)
281281

282282

283+
def test_open_array_rectilinear_chunks(tmp_path: Path) -> None:
284+
"""zarr.open with rectilinear (dask-style) chunks preserves the chunk grid."""
285+
from zarr.core.metadata.v3 import RectilinearChunkGrid
286+
287+
chunks = ((3, 3, 4), (5, 5))
288+
with zarr.config.set({"array.rectilinear_chunks": True}):
289+
z = zarr.open(store=tmp_path, shape=(10, 10), dtype="float64", chunks=chunks, mode="w")
290+
assert isinstance(z, Array)
291+
assert z.shape == (10, 10)
292+
assert isinstance(z.metadata.chunk_grid, RectilinearChunkGrid)
293+
assert z.read_chunk_sizes == ((3, 3, 4), (5, 5))
294+
295+
283296
@pytest.mark.asyncio
284297
async def test_async_array_open_array_not_found() -> None:
285298
"""Test that AsyncArray.open raises ArrayNotFoundError when array doesn't exist"""

0 commit comments

Comments
 (0)