Skip to content

Commit 8de5baf

Browse files
authored
Merge branch 'main' into creation-from-other-zarr
2 parents 8436218 + 06f7796 commit 8de5baf

File tree

12 files changed

+142
-67
lines changed

12 files changed

+142
-67
lines changed

changes/2718.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0-dimensional arrays are now returning a scalar. Therefore, the return type of ``__getitem__`` changed
2+
to NDArrayLikeOrScalar. This change is to make the behavior of 0-dimensional arrays consistent with
3+
``numpy`` scalars.

src/zarr/api/asynchronous.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from collections.abc import Iterable
3939

4040
from zarr.abc.codec import Codec
41+
from zarr.core.buffer import NDArrayLikeOrScalar
4142
from zarr.core.chunk_key_encodings import ChunkKeyEncoding
4243
from zarr.storage import StoreLike
4344

@@ -239,7 +240,7 @@ async def load(
239240
path: str | None = None,
240241
zarr_format: ZarrFormat | None = None,
241242
zarr_version: ZarrFormat | None = None,
242-
) -> NDArrayLike | dict[str, NDArrayLike]:
243+
) -> NDArrayLikeOrScalar | dict[str, NDArrayLikeOrScalar]:
243244
"""Load data from an array or group into memory.
244245
245246
Parameters

src/zarr/api/synchronous.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
ShardsLike,
2828
)
2929
from zarr.core.array_spec import ArrayConfigLike
30-
from zarr.core.buffer import NDArrayLike
30+
from zarr.core.buffer import NDArrayLike, NDArrayLikeOrScalar
3131
from zarr.core.chunk_key_encodings import ChunkKeyEncoding, ChunkKeyEncodingLike
3232
from zarr.core.common import (
3333
JSON,
@@ -122,7 +122,7 @@ def load(
122122
path: str | None = None,
123123
zarr_format: ZarrFormat | None = None,
124124
zarr_version: ZarrFormat | None = None,
125-
) -> NDArrayLike | dict[str, NDArrayLike]:
125+
) -> NDArrayLikeOrScalar | dict[str, NDArrayLikeOrScalar]:
126126
"""Load data from an array or group into memory.
127127
128128
Parameters

src/zarr/core/array.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from zarr.core.buffer import (
3737
BufferPrototype,
3838
NDArrayLike,
39+
NDArrayLikeOrScalar,
3940
NDBuffer,
4041
default_buffer_prototype,
4142
)
@@ -1257,7 +1258,7 @@ async def _get_selection(
12571258
prototype: BufferPrototype,
12581259
out: NDBuffer | None = None,
12591260
fields: Fields | None = None,
1260-
) -> NDArrayLike:
1261+
) -> NDArrayLikeOrScalar:
12611262
# check fields are sensible
12621263
out_dtype = check_fields(fields, self.dtype)
12631264

@@ -1299,14 +1300,16 @@ async def _get_selection(
12991300
out_buffer,
13001301
drop_axes=indexer.drop_axes,
13011302
)
1303+
if isinstance(indexer, BasicIndexer) and indexer.shape == ():
1304+
return out_buffer.as_scalar()
13021305
return out_buffer.as_ndarray_like()
13031306

13041307
async def getitem(
13051308
self,
13061309
selection: BasicSelection,
13071310
*,
13081311
prototype: BufferPrototype | None = None,
1309-
) -> NDArrayLike:
1312+
) -> NDArrayLikeOrScalar:
13101313
"""
13111314
Asynchronous function that retrieves a subset of the array's data based on the provided selection.
13121315
@@ -1319,7 +1322,7 @@ async def getitem(
13191322
13201323
Returns
13211324
-------
1322-
NDArrayLike
1325+
NDArrayLikeOrScalar
13231326
The retrieved subset of the array's data.
13241327
13251328
Examples
@@ -2269,14 +2272,15 @@ def __array__(
22692272
msg = "`copy=False` is not supported. This method always creates a copy."
22702273
raise ValueError(msg)
22712274

2272-
arr_np = self[...]
2275+
arr = self[...]
2276+
arr_np: NDArrayLike = np.array(arr, dtype=dtype)
22732277

22742278
if dtype is not None:
22752279
arr_np = arr_np.astype(dtype)
22762280

22772281
return arr_np
22782282

2279-
def __getitem__(self, selection: Selection) -> NDArrayLike:
2283+
def __getitem__(self, selection: Selection) -> NDArrayLikeOrScalar:
22802284
"""Retrieve data for an item or region of the array.
22812285
22822286
Parameters
@@ -2287,8 +2291,8 @@ def __getitem__(self, selection: Selection) -> NDArrayLike:
22872291
22882292
Returns
22892293
-------
2290-
NDArrayLike
2291-
An array-like containing the data for the requested region.
2294+
NDArrayLikeOrScalar
2295+
An array-like or scalar containing the data for the requested region.
22922296
22932297
Examples
22942298
--------
@@ -2534,7 +2538,7 @@ def get_basic_selection(
25342538
out: NDBuffer | None = None,
25352539
prototype: BufferPrototype | None = None,
25362540
fields: Fields | None = None,
2537-
) -> NDArrayLike:
2541+
) -> NDArrayLikeOrScalar:
25382542
"""Retrieve data for an item or region of the array.
25392543
25402544
Parameters
@@ -2552,8 +2556,8 @@ def get_basic_selection(
25522556
25532557
Returns
25542558
-------
2555-
NDArrayLike
2556-
An array-like containing the data for the requested region.
2559+
NDArrayLikeOrScalar
2560+
An array-like or scalar containing the data for the requested region.
25572561
25582562
Examples
25592563
--------
@@ -2754,7 +2758,7 @@ def get_orthogonal_selection(
27542758
out: NDBuffer | None = None,
27552759
fields: Fields | None = None,
27562760
prototype: BufferPrototype | None = None,
2757-
) -> NDArrayLike:
2761+
) -> NDArrayLikeOrScalar:
27582762
"""Retrieve data by making a selection for each dimension of the array. For
27592763
example, if an array has 2 dimensions, allows selecting specific rows and/or
27602764
columns. The selection for each dimension can be either an integer (indexing a
@@ -2776,8 +2780,8 @@ def get_orthogonal_selection(
27762780
27772781
Returns
27782782
-------
2779-
NDArrayLike
2780-
An array-like containing the data for the requested selection.
2783+
NDArrayLikeOrScalar
2784+
An array-like or scalar containing the data for the requested selection.
27812785
27822786
Examples
27832787
--------
@@ -2990,7 +2994,7 @@ def get_mask_selection(
29902994
out: NDBuffer | None = None,
29912995
fields: Fields | None = None,
29922996
prototype: BufferPrototype | None = None,
2993-
) -> NDArrayLike:
2997+
) -> NDArrayLikeOrScalar:
29942998
"""Retrieve a selection of individual items, by providing a Boolean array of the
29952999
same shape as the array against which the selection is being made, where True
29963000
values indicate a selected item.
@@ -3010,8 +3014,8 @@ def get_mask_selection(
30103014
30113015
Returns
30123016
-------
3013-
NDArrayLike
3014-
An array-like containing the data for the requested selection.
3017+
NDArrayLikeOrScalar
3018+
An array-like or scalar containing the data for the requested selection.
30153019
30163020
Examples
30173021
--------
@@ -3152,7 +3156,7 @@ def get_coordinate_selection(
31523156
out: NDBuffer | None = None,
31533157
fields: Fields | None = None,
31543158
prototype: BufferPrototype | None = None,
3155-
) -> NDArrayLike:
3159+
) -> NDArrayLikeOrScalar:
31563160
"""Retrieve a selection of individual items, by providing the indices
31573161
(coordinates) for each selected item.
31583162
@@ -3170,8 +3174,8 @@ def get_coordinate_selection(
31703174
31713175
Returns
31723176
-------
3173-
NDArrayLike
3174-
An array-like containing the data for the requested coordinate selection.
3177+
NDArrayLikeOrScalar
3178+
An array-like or scalar containing the data for the requested coordinate selection.
31753179
31763180
Examples
31773181
--------
@@ -3340,7 +3344,7 @@ def get_block_selection(
33403344
out: NDBuffer | None = None,
33413345
fields: Fields | None = None,
33423346
prototype: BufferPrototype | None = None,
3343-
) -> NDArrayLike:
3347+
) -> NDArrayLikeOrScalar:
33443348
"""Retrieve a selection of individual items, by providing the indices
33453349
(coordinates) for each selected item.
33463350
@@ -3358,8 +3362,8 @@ def get_block_selection(
33583362
33593363
Returns
33603364
-------
3361-
NDArrayLike
3362-
An array-like containing the data for the requested block selection.
3365+
NDArrayLikeOrScalar
3366+
An array-like or scalar containing the data for the requested block selection.
33633367
33643368
Examples
33653369
--------

src/zarr/core/buffer/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Buffer,
44
BufferPrototype,
55
NDArrayLike,
6+
NDArrayLikeOrScalar,
67
NDBuffer,
78
default_buffer_prototype,
89
)
@@ -13,6 +14,7 @@
1314
"Buffer",
1415
"BufferPrototype",
1516
"NDArrayLike",
17+
"NDArrayLikeOrScalar",
1618
"NDBuffer",
1719
"default_buffer_prototype",
1820
"numpy_buffer_prototype",

src/zarr/core/buffer/core.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ def __eq__(self, other: object) -> Self: # type: ignore[explicit-override, over
105105
"""
106106

107107

108+
ScalarType = int | float | complex | bytes | str | bool | np.generic
109+
NDArrayLikeOrScalar = ScalarType | NDArrayLike
110+
111+
108112
def check_item_key_is_1d_contiguous(key: Any) -> None:
109113
"""Raises error if `key` isn't a 1d contiguous slice"""
110114
if not isinstance(key, slice):
@@ -419,6 +423,21 @@ def as_numpy_array(self) -> npt.NDArray[Any]:
419423
"""
420424
...
421425

426+
def as_scalar(self) -> ScalarType:
427+
"""Returns the buffer as a scalar value"""
428+
if self._data.size != 1:
429+
raise ValueError("Buffer does not contain a single scalar value")
430+
item = self.as_numpy_array().item()
431+
scalar: ScalarType
432+
433+
if np.issubdtype(self.dtype, np.datetime64):
434+
unit: str = np.datetime_data(self.dtype)[0] # Extract the unit (e.g., 'Y', 'D', etc.)
435+
scalar = np.datetime64(item, unit)
436+
else:
437+
scalar = self.dtype.type(item) # Regular conversion for non-datetime types
438+
439+
return scalar
440+
422441
@property
423442
def dtype(self) -> np.dtype[Any]:
424443
return self._data.dtype

src/zarr/core/indexing.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
if TYPE_CHECKING:
3131
from zarr.core.array import Array
32-
from zarr.core.buffer import NDArrayLike
32+
from zarr.core.buffer import NDArrayLikeOrScalar
3333
from zarr.core.chunk_grids import ChunkGrid
3434
from zarr.core.common import ChunkCoords
3535

@@ -937,7 +937,7 @@ class OIndex:
937937
array: Array
938938

939939
# TODO: develop Array generic and move zarr.Array[np.intp] | zarr.Array[np.bool_] to ArrayOfIntOrBool
940-
def __getitem__(self, selection: OrthogonalSelection | Array) -> NDArrayLike:
940+
def __getitem__(self, selection: OrthogonalSelection | Array) -> NDArrayLikeOrScalar:
941941
from zarr.core.array import Array
942942

943943
# if input is a Zarr array, we materialize it now.
@@ -1046,7 +1046,7 @@ def __iter__(self) -> Iterator[ChunkProjection]:
10461046
class BlockIndex:
10471047
array: Array
10481048

1049-
def __getitem__(self, selection: BasicSelection) -> NDArrayLike:
1049+
def __getitem__(self, selection: BasicSelection) -> NDArrayLikeOrScalar:
10501050
fields, new_selection = pop_fields(selection)
10511051
new_selection = ensure_tuple(new_selection)
10521052
new_selection = replace_lists(new_selection)
@@ -1236,7 +1236,9 @@ class VIndex:
12361236
array: Array
12371237

12381238
# TODO: develop Array generic and move zarr.Array[np.intp] | zarr.Array[np.bool_] to ArrayOfIntOrBool
1239-
def __getitem__(self, selection: CoordinateSelection | MaskSelection | Array) -> NDArrayLike:
1239+
def __getitem__(
1240+
self, selection: CoordinateSelection | MaskSelection | Array
1241+
) -> NDArrayLikeOrScalar:
12401242
from zarr.core.array import Array
12411243

12421244
# if input is a Zarr array, we materialize it now.

tests/test_api.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
save_array,
3333
save_group,
3434
)
35+
from zarr.core.buffer import NDArrayLike
3536
from zarr.errors import MetadataValidationError
3637
from zarr.storage import MemoryStore
3738
from zarr.storage._utils import normalize_path
@@ -244,7 +245,9 @@ def test_open_with_mode_r(tmp_path: pathlib.Path) -> None:
244245
z2 = zarr.open(store=tmp_path, mode="r")
245246
assert isinstance(z2, Array)
246247
assert z2.fill_value == 1
247-
assert (z2[:] == 1).all()
248+
result = z2[:]
249+
assert isinstance(result, NDArrayLike)
250+
assert (result == 1).all()
248251
with pytest.raises(ValueError):
249252
z2[:] = 3
250253

@@ -256,7 +259,9 @@ def test_open_with_mode_r_plus(tmp_path: pathlib.Path) -> None:
256259
zarr.ones(store=tmp_path, shape=(3, 3))
257260
z2 = zarr.open(store=tmp_path, mode="r+")
258261
assert isinstance(z2, Array)
259-
assert (z2[:] == 1).all()
262+
result = z2[:]
263+
assert isinstance(result, NDArrayLike)
264+
assert (result == 1).all()
260265
z2[:] = 3
261266

262267

@@ -272,7 +277,9 @@ async def test_open_with_mode_a(tmp_path: pathlib.Path) -> None:
272277
arr[...] = 1
273278
z2 = zarr.open(store=tmp_path, mode="a")
274279
assert isinstance(z2, Array)
275-
assert (z2[:] == 1).all()
280+
result = z2[:]
281+
assert isinstance(result, NDArrayLike)
282+
assert (result == 1).all()
276283
z2[:] = 3
277284

278285

@@ -284,7 +291,9 @@ def test_open_with_mode_w(tmp_path: pathlib.Path) -> None:
284291
arr[...] = 3
285292
z2 = zarr.open(store=tmp_path, mode="w", shape=(3, 3))
286293
assert isinstance(z2, Array)
287-
assert not (z2[:] == 3).all()
294+
result = z2[:]
295+
assert isinstance(result, NDArrayLike)
296+
assert not (result == 3).all()
288297
z2[:] = 3
289298

290299

@@ -1134,7 +1143,9 @@ def test_open_array_with_mode_r_plus(store: Store) -> None:
11341143
zarr.ones(store=store, shape=(3, 3))
11351144
z2 = zarr.open_array(store=store, mode="r+")
11361145
assert isinstance(z2, Array)
1137-
assert (z2[:] == 1).all()
1146+
result = z2[:]
1147+
assert isinstance(result, NDArrayLike)
1148+
assert (result == 1).all()
11381149
z2[:] = 3
11391150

11401151

0 commit comments

Comments
 (0)