Skip to content

Commit 4f4a15a

Browse files
authored
Merge branch 'main' into store-move
2 parents 12ab2fd + 0b97e78 commit 4f4a15a

12 files changed

Lines changed: 110 additions & 66 deletions

File tree

changes/2950.bufgix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Specifying the memory order of Zarr format 2 arrays using the ``order`` keyword argument has been fixed.

changes/2998.bugfix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix structured `dtype` fill value serialization for consolidated metadata

changes/3027.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Simplified scalar indexing of size-1 arrays.

src/zarr/api/asynchronous.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,15 +1040,13 @@ async def create(
10401040
)
10411041
warnings.warn(UserWarning(msg), stacklevel=1)
10421042
config_dict["write_empty_chunks"] = write_empty_chunks
1043-
if order is not None:
1044-
if config is not None:
1045-
msg = (
1046-
"Both order and config keyword arguments are set. "
1047-
"This is redundant. When both are set, order will be ignored and "
1048-
"config will be used."
1049-
)
1050-
warnings.warn(UserWarning(msg), stacklevel=1)
1051-
config_dict["order"] = order
1043+
if order is not None and config is not None:
1044+
msg = (
1045+
"Both order and config keyword arguments are set. "
1046+
"This is redundant. When both are set, order will be ignored and "
1047+
"config will be used."
1048+
)
1049+
warnings.warn(UserWarning(msg), stacklevel=1)
10521050

10531051
config_parsed = ArrayConfig.from_dict(config_dict)
10541052

@@ -1062,6 +1060,7 @@ async def create(
10621060
overwrite=overwrite,
10631061
filters=filters,
10641062
dimension_separator=dimension_separator,
1063+
order=order,
10651064
zarr_format=zarr_format,
10661065
chunk_shape=chunk_shape,
10671066
chunk_key_encoding=chunk_key_encoding,

src/zarr/core/array.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ async def _create(
609609

610610
if order is not None:
611611
_warn_order_kwarg()
612+
config_parsed = replace(config_parsed, order=order)
612613

613614
result = await cls._create_v3(
614615
store_path,
@@ -1044,7 +1045,10 @@ def order(self) -> MemoryOrder:
10441045
bool
10451046
Memory order of the array
10461047
"""
1047-
return self._config.order
1048+
if self.metadata.zarr_format == 2:
1049+
return self.metadata.order
1050+
else:
1051+
return self._config.order
10481052

10491053
@property
10501054
def attrs(self) -> dict[str, JSON]:
@@ -1276,14 +1280,14 @@ async def _get_selection(
12761280
out_buffer = prototype.nd_buffer.create(
12771281
shape=indexer.shape,
12781282
dtype=out_dtype,
1279-
order=self._config.order,
1283+
order=self.order,
12801284
fill_value=self.metadata.fill_value,
12811285
)
12821286
if product(indexer.shape) > 0:
12831287
# need to use the order from the metadata for v2
12841288
_config = self._config
12851289
if self.metadata.zarr_format == 2:
1286-
_config = replace(_config, order=self.metadata.order)
1290+
_config = replace(_config, order=self.order)
12871291

12881292
# reading chunks and decoding them
12891293
await self.codec_pipeline.read(
@@ -4256,6 +4260,11 @@ async def init_array(
42564260
chunks_out = chunk_shape_parsed
42574261
codecs_out = sub_codecs
42584262

4263+
if config is None:
4264+
config = {}
4265+
if order is not None and isinstance(config, dict):
4266+
config["order"] = config.get("order", order)
4267+
42594268
meta = AsyncArray._create_metadata_v3(
42604269
shape=shape_parsed,
42614270
dtype=dtype_parsed,

src/zarr/core/buffer/core.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -427,16 +427,7 @@ def as_scalar(self) -> ScalarType:
427427
"""Returns the buffer as a scalar value"""
428428
if self._data.size != 1:
429429
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
430+
return cast(ScalarType, self.as_numpy_array()[()])
440431

441432
@property
442433
def dtype(self) -> np.dtype[Any]:

src/zarr/core/group.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import base64
45
import itertools
56
import json
67
import logging
@@ -358,7 +359,13 @@ def to_buffer_dict(self, prototype: BufferPrototype) -> dict[str, Buffer]:
358359
d[f"{k}/{ZATTRS_JSON}"] = _replace_special_floats(attrs)
359360
if "shape" in v:
360361
# it's an array
361-
d[f"{k}/{ZARRAY_JSON}"] = _replace_special_floats(v)
362+
if isinstance(v.get("fill_value", None), np.void):
363+
v["fill_value"] = base64.standard_b64encode(
364+
cast(bytes, v["fill_value"])
365+
).decode("ascii")
366+
else:
367+
v = _replace_special_floats(v)
368+
d[f"{k}/{ZARRAY_JSON}"] = v
362369
else:
363370
d[f"{k}/{ZGROUP_JSON}"] = {
364371
"zarr_format": self.zarr_format,

tests/test_api.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,13 +326,12 @@ def test_array_order(zarr_format: ZarrFormat) -> None:
326326
def test_array_order_warns(order: MemoryOrder | None, zarr_format: ZarrFormat) -> None:
327327
with pytest.warns(RuntimeWarning, match="The `order` keyword argument .*"):
328328
arr = zarr.ones(shape=(2, 2), order=order, zarr_format=zarr_format)
329-
expected = order or zarr.config.get("array.order")
330-
assert arr.order == expected
329+
assert arr.order == order
331330

332331
vals = np.asarray(arr)
333-
if expected == "C":
332+
if order == "C":
334333
assert vals.flags.c_contiguous
335-
elif expected == "F":
334+
elif order == "F":
336335
assert vals.flags.f_contiguous
337336
else:
338337
raise AssertionError

tests/test_array.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,39 +1262,43 @@ async def test_data_ignored_params(store: Store) -> None:
12621262
await create_array(store, data=data, shape=None, dtype=data.dtype, overwrite=True)
12631263

12641264
@staticmethod
1265-
@pytest.mark.parametrize("order_config", ["C", "F", None])
1265+
@pytest.mark.parametrize("order", ["C", "F", None])
1266+
@pytest.mark.parametrize("with_config", [True, False])
12661267
def test_order(
1267-
order_config: MemoryOrder | None,
1268+
order: MemoryOrder | None,
1269+
with_config: bool,
12681270
zarr_format: ZarrFormat,
12691271
store: MemoryStore,
12701272
) -> None:
12711273
"""
12721274
Test that the arrays generated by array indexing have a memory order defined by the config order
12731275
value, and that for zarr v2 arrays, the ``order`` field in the array metadata is set correctly.
12741276
"""
1275-
config: ArrayConfigLike = {}
1276-
if order_config is None:
1277+
config: ArrayConfigLike | None = {}
1278+
if order is None:
12771279
config = {}
12781280
expected = zarr.config.get("array.order")
12791281
else:
1280-
config = {"order": order_config}
1281-
expected = order_config
1282+
config = {"order": order}
1283+
expected = order
1284+
1285+
if not with_config:
1286+
# Test without passing config parameter
1287+
config = None
1288+
1289+
arr = zarr.create_array(
1290+
store=store,
1291+
shape=(2, 2),
1292+
zarr_format=zarr_format,
1293+
dtype="i4",
1294+
order=order,
1295+
config=config,
1296+
)
1297+
assert arr.order == expected
12821298
if zarr_format == 2:
1283-
arr = zarr.create_array(
1284-
store=store,
1285-
shape=(2, 2),
1286-
zarr_format=zarr_format,
1287-
dtype="i4",
1288-
order=expected,
1289-
config=config,
1290-
)
1291-
# guard for type checking
12921299
assert arr.metadata.zarr_format == 2
12931300
assert arr.metadata.order == expected
1294-
else:
1295-
arr = zarr.create_array(
1296-
store=store, shape=(2, 2), zarr_format=zarr_format, dtype="i4", config=config
1297-
)
1301+
12981302
vals = np.asarray(arr)
12991303
if expected == "C":
13001304
assert vals.flags.c_contiguous

tests/test_buffer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,9 @@ def test_numpy_buffer_prototype() -> None:
155155
assert isinstance(ndbuffer.as_ndarray_like(), np.ndarray)
156156
with pytest.raises(ValueError, match="Buffer does not contain a single scalar value"):
157157
ndbuffer.as_scalar()
158+
159+
160+
# TODO: the same test for other buffer classes
161+
def test_cpu_buffer_as_scalar() -> None:
162+
buf = cpu.buffer_prototype.nd_buffer.create(shape=(), dtype="int64")
163+
assert buf.as_scalar() == buf.as_ndarray_like()[()] # type: ignore[index]

0 commit comments

Comments
 (0)