Skip to content

Commit 626b45d

Browse files
committed
Fix order handling
1 parent 11d488d commit 626b45d

File tree

11 files changed

+78
-99
lines changed

11 files changed

+78
-99
lines changed

changes/xxx1.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed the error message when passing both ``config`` and ``write_emtpy_chunks`` arguments to reflect the current behaviour (``write_empty_chunks`` takes precedence).

changes/xxx2.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Creating a Zarr format 2 array with the ``order`` keyword argument no longer raises a warning.

changes/xxx3.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Creating a Zarr format 3 array with the ``order`` argument now conistently ignores this argument and raises a warning.

changes/xxx4.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
When using ``from_array`` to copy a Zarr format 2 array to a Zarr format 3 array, if the memory order of the input array is ``"F"`` a warning is raised and the order ignored.
2+
This is because Zarr format 3 arrays are always stored in "C" order.

changes/xxxx.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The ``config`` argument to `zarr.create` (and functions that create arrays) is now used - previously it had no effect.

src/zarr/api/asynchronous.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from_array,
1919
get_array_metadata,
2020
)
21-
from zarr.core.array_spec import ArrayConfig, ArrayConfigLike, ArrayConfigParams
21+
from zarr.core.array_spec import ArrayConfigLike, parse_array_config
2222
from zarr.core.buffer import NDArrayLike
2323
from zarr.core.common import (
2424
JSON,
@@ -28,7 +28,6 @@
2828
MemoryOrder,
2929
ZarrFormat,
3030
_default_zarr_format,
31-
_warn_order_kwarg,
3231
_warn_write_empty_chunks_kwarg,
3332
parse_dtype,
3433
)
@@ -1028,8 +1027,6 @@ async def create(
10281027
warnings.warn("object_codec is not yet implemented", RuntimeWarning, stacklevel=2)
10291028
if read_only is not None:
10301029
warnings.warn("read_only is not yet implemented", RuntimeWarning, stacklevel=2)
1031-
if order is not None:
1032-
_warn_order_kwarg()
10331030
if write_empty_chunks is not None:
10341031
_warn_write_empty_chunks_kwarg()
10351032

@@ -1041,26 +1038,17 @@ async def create(
10411038
mode = "a"
10421039
store_path = await make_store_path(store, path=path, mode=mode, storage_options=storage_options)
10431040

1044-
config_dict: ArrayConfigParams = {}
1041+
config_parsed = parse_array_config(config)
10451042

10461043
if write_empty_chunks is not None:
10471044
if config is not None:
10481045
msg = (
10491046
"Both write_empty_chunks and config keyword arguments are set. "
1050-
"This is redundant. When both are set, write_empty_chunks will be ignored and "
1051-
"config will be used."
1047+
"This is redundant. When both are set, write_empty_chunks will be used instead "
1048+
"of the value in config."
10521049
)
10531050
warnings.warn(UserWarning(msg), stacklevel=1)
1054-
config_dict["write_empty_chunks"] = write_empty_chunks
1055-
if order is not None and config is not None:
1056-
msg = (
1057-
"Both order and config keyword arguments are set. "
1058-
"This is redundant. When both are set, order will be ignored and "
1059-
"config will be used."
1060-
)
1061-
warnings.warn(UserWarning(msg), stacklevel=1)
1062-
1063-
config_parsed = ArrayConfig.from_dict(config_dict)
1051+
config_parsed = dataclasses.replace(config_parsed, write_empty_chunks=write_empty_chunks)
10641052

10651053
return await AsyncArray._create(
10661054
store_path,
@@ -1263,8 +1251,6 @@ async def open_array(
12631251

12641252
zarr_format = _handle_zarr_version_or_format(zarr_version=zarr_version, zarr_format=zarr_format)
12651253

1266-
if "order" in kwargs:
1267-
_warn_order_kwarg()
12681254
if "write_empty_chunks" in kwargs:
12691255
_warn_write_empty_chunks_kwarg()
12701256

src/zarr/core/array.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
_warn_order_kwarg,
6363
concurrent_map,
6464
parse_dtype,
65-
parse_order,
6665
parse_shapelike,
6766
product,
6867
)
@@ -603,7 +602,6 @@ async def _create(
603602

604603
if order is not None:
605604
_warn_order_kwarg()
606-
config_parsed = replace(config_parsed, order=order)
607605

608606
result = await cls._create_v3(
609607
store_path,
@@ -631,9 +629,10 @@ async def _create(
631629
raise ValueError("dimension_names cannot be used for arrays with zarr_format 2.")
632630

633631
if order is None:
634-
order_parsed = parse_order(zarr_config.get("array.order"))
632+
order_parsed = config_parsed.order
635633
else:
636634
order_parsed = order
635+
config_parsed = replace(config_parsed, order=order)
637636

638637
result = await cls._create_v2(
639638
store_path,
@@ -4267,10 +4266,8 @@ async def init_array(
42674266
chunks_out = chunk_shape_parsed
42684267
codecs_out = sub_codecs
42694268

4270-
if config is None:
4271-
config = {}
4272-
if order is not None and isinstance(config, dict):
4273-
config["order"] = config.get("order", order)
4269+
if order is not None:
4270+
_warn_order_kwarg()
42744271

42754272
meta = AsyncArray._create_metadata_v3(
42764273
shape=shape_parsed,
@@ -4521,8 +4518,18 @@ def _parse_keep_array_attr(
45214518
serializer = "auto"
45224519
if fill_value is None:
45234520
fill_value = data.fill_value
4524-
if order is None:
4521+
4522+
if data.metadata.zarr_format == 2 and zarr_format == 3 and data.order == "F":
4523+
# Can't set order="F" for v3 arrays
4524+
warnings.warn(
4525+
"Zarr format 3 arrays are always stored with order='C'. "
4526+
"The existing order='F' of the source Zarr format 2 array will be ignored.",
4527+
UserWarning,
4528+
stacklevel=2,
4529+
)
4530+
elif order is None and zarr_format == 2:
45254531
order = data.order
4532+
45264533
if chunk_key_encoding is None and zarr_format == data.metadata.zarr_format:
45274534
if isinstance(data.metadata, ArrayV2Metadata):
45284535
chunk_key_encoding = {"name": "v2", "separator": data.metadata.dimension_separator}

src/zarr/core/group.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2428,7 +2428,7 @@ def create_array(
24282428
compressor: CompressorLike = "auto",
24292429
serializer: SerializerLike = "auto",
24302430
fill_value: Any | None = 0,
2431-
order: MemoryOrder | None = "C",
2431+
order: MemoryOrder | None = None,
24322432
attributes: dict[str, JSON] | None = None,
24332433
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
24342434
dimension_names: DimensionNames = None,
@@ -2822,7 +2822,7 @@ def array(
28222822
compressor: CompressorLike = None,
28232823
serializer: SerializerLike = "auto",
28242824
fill_value: Any | None = 0,
2825-
order: MemoryOrder | None = "C",
2825+
order: MemoryOrder | None = None,
28262826
attributes: dict[str, JSON] | None = None,
28272827
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
28282828
dimension_names: DimensionNames = None,

test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import zarr
2+
3+
# Write fortran style array
4+
group = zarr.group(store={})
5+
array = group.create_array(
6+
name="example",
7+
shape=(128, 128),
8+
dtype="float32",
9+
order="F",
10+
dimension_names=("row", "col"),
11+
)

tests/test_api.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -340,34 +340,52 @@ def test_open_with_mode_w_minus(tmp_path: pathlib.Path) -> None:
340340
zarr.open(store=tmp_path, mode="w-")
341341

342342

343-
def test_array_order(zarr_format: ZarrFormat) -> None:
344-
arr = zarr.ones(shape=(2, 2), order=None, zarr_format=zarr_format)
345-
expected = zarr.config.get("array.order")
346-
assert arr.order == expected
343+
@pytest.mark.parametrize("order", ["C", "F", None])
344+
@pytest.mark.parametrize("config", [{"order": "C"}, {"order": "F"}, {}], ids=["C", "F", "None"])
345+
def test_array_order(
346+
order: MemoryOrder | None, config: dict[str, MemoryOrder | None], zarr_format: ZarrFormat
347+
) -> None:
348+
"""
349+
Check that:
350+
- For v2, memory order is taken from the `order` keyword argument.
351+
- For v3, memory order is taken from `config`, and when order is passed a warning is raised
352+
- The numpy array returned has the expected order
353+
- For v2, the order metadata is set correctly
354+
"""
355+
default_order = zarr.config.get("array.order")
356+
ctx: contextlib.AbstractContextManager # type: ignore[type-arg]
347357

348-
vals = np.asarray(arr)
349-
if expected == "C":
350-
assert vals.flags.c_contiguous
351-
elif expected == "F":
352-
assert vals.flags.f_contiguous
353-
else:
354-
raise AssertionError
358+
if zarr_format == 3:
359+
if order is None:
360+
ctx = contextlib.nullcontext()
361+
else:
362+
ctx = pytest.warns(
363+
RuntimeWarning,
364+
match="The `order` keyword argument has no effect for Zarr format 3 arrays",
365+
)
355366

367+
expected_order = config.get("order", default_order)
356368

357-
@pytest.mark.parametrize("order", ["C", "F"])
358-
def test_array_order_warns(order: MemoryOrder | None, zarr_format: ZarrFormat) -> None:
359-
with pytest.warns(RuntimeWarning, match="The `order` keyword argument .*"):
360-
arr = zarr.ones(shape=(2, 2), order=order, zarr_format=zarr_format)
361-
assert arr.order == order
369+
if zarr_format == 2:
370+
ctx = contextlib.nullcontext()
371+
expected_order = order or config.get("order", default_order)
362372

373+
with ctx:
374+
arr = zarr.ones(shape=(2, 2), order=order, zarr_format=zarr_format, config=config)
375+
376+
assert arr.order == expected_order
363377
vals = np.asarray(arr)
364-
if order == "C":
378+
if expected_order == "C":
365379
assert vals.flags.c_contiguous
366-
elif order == "F":
380+
elif expected_order == "F":
367381
assert vals.flags.f_contiguous
368382
else:
369383
raise AssertionError
370384

385+
if zarr_format == 2:
386+
assert arr.metadata.zarr_format == 2
387+
assert arr.metadata.order == expected_order
388+
371389

372390
# def test_lazy_loader():
373391
# foo = np.arange(100)

0 commit comments

Comments
 (0)