Skip to content

Commit 0ad8f6d

Browse files
committed
Fix order handling
Fix imports
1 parent 9d97b24 commit 0ad8f6d

File tree

11 files changed

+76
-94
lines changed

11 files changed

+76
-94
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
@@ -19,7 +19,7 @@
1919
from_array,
2020
get_array_metadata,
2121
)
22-
from zarr.core.array_spec import ArrayConfig, ArrayConfigLike, ArrayConfigParams
22+
from zarr.core.array_spec import ArrayConfigLike, parse_array_config
2323
from zarr.core.buffer import NDArrayLike
2424
from zarr.core.common import (
2525
JSON,
@@ -29,7 +29,6 @@
2929
MemoryOrder,
3030
ZarrFormat,
3131
_default_zarr_format,
32-
_warn_order_kwarg,
3332
_warn_write_empty_chunks_kwarg,
3433
)
3534
from zarr.core.dtype import ZDTypeLike, get_data_type_from_native_dtype
@@ -1026,8 +1025,6 @@ async def create(
10261025
if meta_array is not None:
10271026
warnings.warn("meta_array is not yet implemented", RuntimeWarning, stacklevel=2)
10281027

1029-
if order is not None:
1030-
_warn_order_kwarg()
10311028
if write_empty_chunks is not None:
10321029
_warn_write_empty_chunks_kwarg()
10331030

@@ -1036,26 +1033,17 @@ async def create(
10361033
mode = "a"
10371034
store_path = await make_store_path(store, path=path, mode=mode, storage_options=storage_options)
10381035

1039-
config_dict: ArrayConfigParams = {}
1036+
config_parsed = parse_array_config(config)
10401037

10411038
if write_empty_chunks is not None:
10421039
if config is not None:
10431040
msg = (
10441041
"Both write_empty_chunks and config keyword arguments are set. "
1045-
"This is redundant. When both are set, write_empty_chunks will be ignored and "
1046-
"config will be used."
1042+
"This is redundant. When both are set, write_empty_chunks will be used instead "
1043+
"of the value in config."
10471044
)
10481045
warnings.warn(UserWarning(msg), stacklevel=1)
1049-
config_dict["write_empty_chunks"] = write_empty_chunks
1050-
if order is not None and config is not None:
1051-
msg = (
1052-
"Both order and config keyword arguments are set. "
1053-
"This is redundant. When both are set, order will be ignored and "
1054-
"config will be used."
1055-
)
1056-
warnings.warn(UserWarning(msg), stacklevel=1)
1057-
1058-
config_parsed = ArrayConfig.from_dict(config_dict)
1046+
config_parsed = dataclasses.replace(config_parsed, write_empty_chunks=write_empty_chunks)
10591047

10601048
return await AsyncArray._create(
10611049
store_path,
@@ -1258,8 +1246,6 @@ async def open_array(
12581246

12591247
zarr_format = _handle_zarr_version_or_format(zarr_version=zarr_version, zarr_format=zarr_format)
12601248

1261-
if "order" in kwargs:
1262-
_warn_order_kwarg()
12631249
if "write_empty_chunks" in kwargs:
12641250
_warn_write_empty_chunks_kwarg()
12651251

src/zarr/core/array.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
_default_zarr_format,
6262
_warn_order_kwarg,
6363
concurrent_map,
64-
parse_order,
6564
parse_shapelike,
6665
product,
6766
)
@@ -636,7 +635,6 @@ async def _create(
636635

637636
if order is not None:
638637
_warn_order_kwarg()
639-
config_parsed = replace(config_parsed, order=order)
640638

641639
result = await cls._create_v3(
642640
store_path,
@@ -664,9 +662,10 @@ async def _create(
664662
raise ValueError("dimension_names cannot be used for arrays with zarr_format 2.")
665663

666664
if order is None:
667-
order_parsed = parse_order(zarr_config.get("array.order"))
665+
order_parsed = config_parsed.order
668666
else:
669667
order_parsed = order
668+
config_parsed = replace(config_parsed, order=order)
670669

671670
result = await cls._create_v2(
672671
store_path,
@@ -4320,10 +4319,8 @@ async def init_array(
43204319
chunks_out = chunk_shape_parsed
43214320
codecs_out = sub_codecs
43224321

4323-
if config is None:
4324-
config = {}
4325-
if order is not None and isinstance(config, dict):
4326-
config["order"] = config.get("order", order)
4322+
if order is not None:
4323+
_warn_order_kwarg()
43274324

43284325
meta = AsyncArray._create_metadata_v3(
43294326
shape=shape_parsed,
@@ -4574,8 +4571,18 @@ def _parse_keep_array_attr(
45744571
serializer = "auto"
45754572
if fill_value is None:
45764573
fill_value = data.fill_value
4577-
if order is None:
4574+
4575+
if data.metadata.zarr_format == 2 and zarr_format == 3 and data.order == "F":
4576+
# Can't set order="F" for v3 arrays
4577+
warnings.warn(
4578+
"Zarr format 3 arrays are always stored with order='C'. "
4579+
"The existing order='F' of the source Zarr format 2 array will be ignored.",
4580+
UserWarning,
4581+
stacklevel=2,
4582+
)
4583+
elif order is None and zarr_format == 2:
45784584
order = data.order
4585+
45794586
if chunk_key_encoding is None and zarr_format == data.metadata.zarr_format:
45804587
if isinstance(data.metadata, ArrayV2Metadata):
45814588
chunk_key_encoding = {"name": "v2", "separator": data.metadata.dimension_separator}

src/zarr/core/group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2837,7 +2837,7 @@ def array(
28372837
compressor: CompressorLike = None,
28382838
serializer: SerializerLike = "auto",
28392839
fill_value: Any | None = DEFAULT_FILL_VALUE,
2840-
order: MemoryOrder | None = "C",
2840+
order: MemoryOrder | None = None,
28412841
attributes: dict[str, JSON] | None = None,
28422842
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
28432843
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
@@ -343,34 +343,52 @@ def test_open_with_mode_w_minus(tmp_path: pathlib.Path) -> None:
343343
zarr.open(store=tmp_path, mode="w-")
344344

345345

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

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

370+
expected_order = config.get("order", default_order)
359371

360-
@pytest.mark.parametrize("order", ["C", "F"])
361-
def test_array_order_warns(order: MemoryOrder | None, zarr_format: ZarrFormat) -> None:
362-
with pytest.warns(RuntimeWarning, match="The `order` keyword argument .*"):
363-
arr = zarr.ones(shape=(2, 2), order=order, zarr_format=zarr_format)
364-
assert arr.order == order
372+
if zarr_format == 2:
373+
ctx = contextlib.nullcontext()
374+
expected_order = order or config.get("order", default_order)
365375

376+
with ctx:
377+
arr = zarr.ones(shape=(2, 2), order=order, zarr_format=zarr_format, config=config)
378+
379+
assert arr.order == expected_order
366380
vals = np.asarray(arr)
367-
if order == "C":
381+
if expected_order == "C":
368382
assert vals.flags.c_contiguous
369-
elif order == "F":
383+
elif expected_order == "F":
370384
assert vals.flags.f_contiguous
371385
else:
372386
raise AssertionError
373387

388+
if zarr_format == 2:
389+
assert arr.metadata.zarr_format == 2
390+
assert arr.metadata.order == expected_order
391+
374392

375393
# def test_lazy_loader():
376394
# foo = np.arange(100)

0 commit comments

Comments
 (0)