Skip to content

Commit 99fd0a8

Browse files
committed
fixups + support partial edge chunks / resize
1 parent 6eab392 commit 99fd0a8

11 files changed

Lines changed: 953 additions & 164 deletions

File tree

docs/user-guide/arrays.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -676,14 +676,6 @@ Variable chunking has some important limitations:
676676
property will raise a `NotImplementedError`. Use `.metadata.chunk_grid.chunk_shapes`
677677
instead.
678678

679-
```python exec="true" session="arrays" source="above" result="ansi"
680-
# This will raise an error
681-
try:
682-
_ = z.chunks
683-
except NotImplementedError as e:
684-
print(f"Error: {e}")
685-
```
686-
687679
## Missing features in 3.0
688680

689681
The following features have not been ported to 3.0 yet.

src/zarr/api/asynchronous.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def _get_shape_chunks(a: ArrayLike | Any) -> tuple[tuple[int, ...] | None, tuple
122122
# bcolz carray
123123
chunks = (a.chunklen,) + a.shape[1:]
124124

125-
return shape, chunks
125+
return shape, chunks # type: ignore[return-value]
126126

127127

128128
class _LikeArgs(TypedDict):

src/zarr/api/synchronous.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from zarr.errors import ZarrDeprecationWarning
1414

1515
if TYPE_CHECKING:
16-
from collections.abc import Iterable, Sequence
16+
from collections.abc import Iterable
1717

1818
import numpy as np
1919
import numpy.typing as npt
@@ -29,7 +29,7 @@
2929
)
3030
from zarr.core.array_spec import ArrayConfigLike
3131
from zarr.core.buffer import NDArrayLike, NDArrayLikeOrScalar
32-
from zarr.core.chunk_grids import ChunkGrid
32+
from zarr.core.chunk_grids import ChunksLike
3333
from zarr.core.chunk_key_encodings import ChunkKeyEncoding, ChunkKeyEncodingLike
3434
from zarr.core.common import (
3535
JSON,
@@ -822,7 +822,7 @@ def create_array(
822822
shape: ShapeLike | None = None,
823823
dtype: ZDTypeLike | None = None,
824824
data: np.ndarray[Any, np.dtype[Any]] | None = None,
825-
chunks: tuple[int, ...] | Sequence[Sequence[int]] | ChunkGrid | Literal["auto"] = "auto",
825+
chunks: ChunksLike = "auto",
826826
shards: ShardsLike | None = None,
827827
filters: FiltersLike = "auto",
828828
compressors: CompressorsLike = "auto",
@@ -858,7 +858,7 @@ def create_array(
858858
data : np.ndarray, optional
859859
Array-like data to use for initializing the array. If this parameter is provided, the
860860
``shape`` and ``dtype`` parameters must be ``None``.
861-
chunks : tuple[int, ...] | Sequence[Sequence[int]] | ChunkGrid | Literal["auto"], default="auto"
861+
chunks : ChunksLike, default="auto"
862862
Chunk shape of the array. Several formats are supported:
863863
864864
- tuple of ints: Creates a RegularChunkGrid with uniform chunks, e.g., ``(10, 10)``

src/zarr/core/_info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class ArrayInfo:
8383
_fill_value: object
8484
_shape: tuple[int, ...]
8585
_shard_shape: tuple[int, ...] | None = None
86-
_chunk_shape: tuple[int, ...] | None = None
86+
_chunk_shape: tuple[int, ...] | tuple[tuple[int, ...], ...] | None = None
8787
_order: Literal["C", "F"]
8888
_read_only: bool
8989
_store_type: str

src/zarr/core/array.py

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from zarr.core.buffer.cpu import buffer_prototype as cpu_buffer_prototype
4343
from zarr.core.chunk_grids import (
4444
ChunkGrid,
45+
ChunksLike,
4546
RegularChunkGrid,
4647
_auto_partition,
4748
_normalize_chunks,
@@ -1048,17 +1049,20 @@ def shape(self) -> tuple[int, ...]:
10481049
return self.metadata.shape
10491050

10501051
@property
1051-
def chunks(self) -> tuple[int, ...]:
1052-
"""Returns the chunk shape of the Array.
1053-
If sharding is used the inner chunk shape is returned.
1052+
def chunks(self) -> tuple[int, ...] | tuple[tuple[int, ...], ...]:
1053+
"""Returns the chunk specification of the Array.
10541054
1055-
Only defined for arrays using using `RegularChunkGrid`.
1056-
If array doesn't use `RegularChunkGrid`, `NotImplementedError` is raised.
1055+
For arrays using RegularChunkGrid: returns a tuple of ints representing
1056+
the uniform chunk shape. If sharding is used, the inner chunk shape is returned.
1057+
1058+
For arrays using RectilinearChunkGrid: returns a tuple of tuples, where
1059+
each inner tuple contains the chunk sizes along that dimension (not RLE encoded).
10571060
10581061
Returns
10591062
-------
1060-
tuple[int, ...]:
1061-
The chunk shape of the Array.
1063+
tuple[int, ...] | tuple[tuple[int, ...], ...]
1064+
For regular chunks: (chunk_size_dim0, chunk_size_dim1, ...)
1065+
For rectilinear chunks: ((sizes_dim0), (sizes_dim1), ...)
10621066
"""
10631067
return self.metadata.chunks
10641068

@@ -1349,8 +1353,10 @@ async def example():
13491353
if self.shards is None:
13501354
chunks_per_shard = 1
13511355
else:
1356+
# Sharding only applies to RegularChunkGrid, so chunks is tuple[int, ...]
1357+
chunks = cast(tuple[int, ...], self.chunks)
13521358
chunks_per_shard = product(
1353-
tuple(a // b for a, b in zip(self.shards, self.chunks, strict=True))
1359+
tuple(a // b for a, b in zip(self.shards, chunks, strict=True))
13541360
)
13551361
return (await self._nshards_initialized()) * chunks_per_shard
13561362

@@ -1856,7 +1862,7 @@ async def resize(self, new_shape: ShapeLike, delete_outside_chunks: bool = True)
18561862
if delete_outside_chunks:
18571863
# Remove all chunks outside of the new shape
18581864
old_chunk_coords = set(self.metadata.chunk_grid.all_chunk_coords(self.metadata.shape))
1859-
new_chunk_coords = set(self.metadata.chunk_grid.all_chunk_coords(new_shape))
1865+
new_chunk_coords = set(new_metadata.chunk_grid.all_chunk_coords(new_shape))
18601866

18611867
async def _delete_key(key: str) -> None:
18621868
await (self.store_path / key).delete()
@@ -2340,17 +2346,20 @@ def shape(self, value: tuple[int, ...]) -> None:
23402346
self.resize(value)
23412347

23422348
@property
2343-
def chunks(self) -> tuple[int, ...]:
2344-
"""Returns a tuple of integers describing the length of each dimension of a chunk of the array.
2345-
If sharding is used the inner chunk shape is returned.
2349+
def chunks(self) -> tuple[int, ...] | tuple[tuple[int, ...], ...]:
2350+
"""Returns the chunk specification of the Array.
23462351
2347-
Only defined for arrays using using `RegularChunkGrid`.
2348-
If array doesn't use `RegularChunkGrid`, `NotImplementedError` is raised.
2352+
For arrays using RegularChunkGrid: returns a tuple of ints representing
2353+
the uniform chunk shape. If sharding is used, the inner chunk shape is returned.
2354+
2355+
For arrays using RectilinearChunkGrid: returns a tuple of tuples, where
2356+
each inner tuple contains the chunk sizes along that dimension (not RLE encoded).
23492357
23502358
Returns
23512359
-------
2352-
tuple
2353-
A tuple of integers representing the length of each dimension of a chunk.
2360+
tuple[int, ...] | tuple[tuple[int, ...], ...]
2361+
For regular chunks: (chunk_size_dim0, chunk_size_dim1, ...)
2362+
For rectilinear chunks: ((sizes_dim0), (sizes_dim1), ...)
23542363
"""
23552364
return self._async_array.chunks
23562365

@@ -4504,7 +4513,7 @@ async def from_array(
45044513
zarr_format,
45054514
chunk_key_encoding,
45064515
dimension_names,
4507-
) = _parse_keep_array_attr(
4516+
) = _parse_keep_array_attr( # type: ignore[assignment]
45084517
data=data,
45094518
chunks=chunks,
45104519
shards=shards,
@@ -4529,7 +4538,7 @@ async def from_array(
45294538
item_size = zdtype.item_size
45304539

45314540
resolved = resolve_chunk_spec(
4532-
chunks=chunks,
4541+
chunks=cast(ChunksLike, chunks),
45334542
shards=shards,
45344543
shape=data.shape,
45354544
dtype_itemsize=item_size,
@@ -4885,7 +4894,7 @@ async def create_array(
48854894
shape: ShapeLike | None = None,
48864895
dtype: ZDTypeLike | None = None,
48874896
data: np.ndarray[Any, np.dtype[Any]] | None = None,
4888-
chunks: tuple[int, ...] | Sequence[Sequence[int]] | ChunkGrid | Literal["auto"] = "auto",
4897+
chunks: ChunksLike = "auto",
48894898
shards: ShardsLike | None = None,
48904899
filters: FiltersLike = "auto",
48914900
compressors: CompressorsLike = "auto",
@@ -4919,7 +4928,7 @@ async def create_array(
49194928
data : np.ndarray, optional
49204929
Array-like data to use for initializing the array. If this parameter is provided, the
49214930
``shape`` and ``dtype`` parameters must be ``None``.
4922-
chunks : tuple[int, ...] | Sequence[Sequence[int]] | ChunkGrid | Literal["auto"], default="auto"
4931+
chunks : ChunksLike, default="auto"
49234932
Chunk shape of the array. Several formats are supported:
49244933
49254934
- tuple of ints: Creates a RegularChunkGrid with uniform chunks, e.g., ``(10, 10)``
@@ -5107,7 +5116,7 @@ def _parse_keep_array_attr(
51075116
chunk_key_encoding: ChunkKeyEncodingLike | None,
51085117
dimension_names: DimensionNames,
51095118
) -> tuple[
5110-
tuple[int, ...] | Literal["auto"],
5119+
tuple[int, ...] | tuple[tuple[int, ...], ...] | Literal["auto"],
51115120
ShardsLike | None,
51125121
FiltersLike,
51135122
CompressorsLike,
@@ -5120,7 +5129,7 @@ def _parse_keep_array_attr(
51205129
]:
51215130
if isinstance(data, Array):
51225131
if chunks == "keep":
5123-
chunks = data.chunks
5132+
chunks = data.chunks # type: ignore[assignment]
51245133
if shards == "keep":
51255134
shards = data.shards
51265135
if zarr_format is None:
@@ -5174,7 +5183,7 @@ def _parse_keep_array_attr(
51745183
compressors = "auto"
51755184
if serializer == "keep":
51765185
serializer = "auto"
5177-
return (
5186+
return ( # type: ignore[return-value]
51785187
chunks,
51795188
shards,
51805189
filters,
@@ -5588,9 +5597,23 @@ def _iter_shard_regions(
55885597
------
55895598
region: tuple[slice, ...]
55905599
A tuple of slice objects representing the region spanned by each shard in the selection.
5600+
5601+
Raises
5602+
------
5603+
NotImplementedError
5604+
If the array uses RectilinearChunkGrid (variable-sized chunks).
55915605
"""
5606+
chunks = array.chunks
5607+
if not isinstance(chunks[0], int):
5608+
raise NotImplementedError(
5609+
"_iter_shard_regions is not supported for arrays with variable-sized chunks "
5610+
"(RectilinearChunkGrid). Use the chunk_grid API directly for variable chunk access."
5611+
)
5612+
5613+
# After the check above, chunks is tuple[int, ...]
5614+
regular_chunks = cast(tuple[int, ...], chunks)
55925615
if array.shards is None:
5593-
shard_shape = array.chunks
5616+
shard_shape: Sequence[int] = regular_chunks
55945617
else:
55955618
shard_shape = array.shards
55965619

@@ -5606,7 +5629,7 @@ def _iter_chunk_regions(
56065629
selection_shape: Sequence[int] | None = None,
56075630
) -> Iterator[tuple[slice, ...]]:
56085631
"""
5609-
Iterate over the regions spanned by each shard.
5632+
Iterate over the regions spanned by each chunk.
56105633
56115634
These are the smallest regions of the array that are efficient to read concurrently.
56125635
@@ -5622,9 +5645,26 @@ def _iter_chunk_regions(
56225645
Returns
56235646
-------
56245647
region: tuple[slice, ...]
5625-
A tuple of slice objects representing the region spanned by each shard in the selection.
5648+
A tuple of slice objects representing the region spanned by each chunk in the selection.
5649+
5650+
Raises
5651+
------
5652+
NotImplementedError
5653+
If the array uses RectilinearChunkGrid (variable-sized chunks).
56265654
"""
5655+
chunks = array.chunks
5656+
if not isinstance(chunks[0], int):
5657+
raise NotImplementedError(
5658+
"_iter_chunk_regions is not supported for arrays with variable-sized chunks "
5659+
"(RectilinearChunkGrid). Use the chunk_grid API directly for variable chunk access."
5660+
)
56275661

5662+
# After the check above, chunks is tuple[int, ...]
5663+
regular_chunks = cast(tuple[int, ...], chunks)
56285664
return _iter_regions(
5629-
array.shape, array.chunks, origin=origin, selection_shape=selection_shape, trim_excess=True
5665+
array.shape,
5666+
regular_chunks,
5667+
origin=origin,
5668+
selection_shape=selection_shape,
5669+
trim_excess=True,
56305670
)

0 commit comments

Comments
 (0)