Skip to content

Commit 437d0e1

Browse files
committed
Fix order handling
1 parent d19f3f0 commit 437d0e1

File tree

12 files changed

+80
-100
lines changed

12 files changed

+80
-100
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
@@ -17,7 +17,7 @@
1717
from_array,
1818
get_array_metadata,
1919
)
20-
from zarr.core.array_spec import ArrayConfig, ArrayConfigLike, ArrayConfigParams
20+
from zarr.core.array_spec import ArrayConfigLike, parse_array_config
2121
from zarr.core.buffer import NDArrayLike
2222
from zarr.core.common import (
2323
JSON,
@@ -27,7 +27,6 @@
2727
MemoryOrder,
2828
ZarrFormat,
2929
_default_zarr_format,
30-
_warn_order_kwarg,
3130
_warn_write_empty_chunks_kwarg,
3231
parse_dtype,
3332
)
@@ -1013,8 +1012,6 @@ async def create(
10131012
warnings.warn("object_codec is not yet implemented", RuntimeWarning, stacklevel=2)
10141013
if read_only is not None:
10151014
warnings.warn("read_only is not yet implemented", RuntimeWarning, stacklevel=2)
1016-
if order is not None:
1017-
_warn_order_kwarg()
10181015
if write_empty_chunks is not None:
10191016
_warn_write_empty_chunks_kwarg()
10201017

@@ -1026,26 +1023,17 @@ async def create(
10261023
mode = "a"
10271024
store_path = await make_store_path(store, path=path, mode=mode, storage_options=storage_options)
10281025

1029-
config_dict: ArrayConfigParams = {}
1026+
config_parsed = parse_array_config(config)
10301027

10311028
if write_empty_chunks is not None:
10321029
if config is not None:
10331030
msg = (
10341031
"Both write_empty_chunks and config keyword arguments are set. "
1035-
"This is redundant. When both are set, write_empty_chunks will be ignored and "
1036-
"config will be used."
1032+
"This is redundant. When both are set, write_empty_chunks will be used instead "
1033+
"of the value in config."
10371034
)
10381035
warnings.warn(UserWarning(msg), stacklevel=1)
1039-
config_dict["write_empty_chunks"] = write_empty_chunks
1040-
if order is not None and config is not None:
1041-
msg = (
1042-
"Both order and config keyword arguments are set. "
1043-
"This is redundant. When both are set, order will be ignored and "
1044-
"config will be used."
1045-
)
1046-
warnings.warn(UserWarning(msg), stacklevel=1)
1047-
1048-
config_parsed = ArrayConfig.from_dict(config_dict)
1036+
config_parsed = dataclasses.replace(config_parsed, write_empty_chunks=write_empty_chunks)
10491037

10501038
return await AsyncArray._create(
10511039
store_path,
@@ -1248,8 +1236,6 @@ async def open_array(
12481236

12491237
zarr_format = _handle_zarr_version_or_format(zarr_version=zarr_version, zarr_format=zarr_format)
12501238

1251-
if "order" in kwargs:
1252-
_warn_order_kwarg()
12531239
if "write_empty_chunks" in kwargs:
12541240
_warn_write_empty_chunks_kwarg()
12551241

src/zarr/core/array.py

Lines changed: 18 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,
@@ -721,6 +720,7 @@ async def _create_v3(
721720
attributes: dict[str, JSON] | None = None,
722721
overwrite: bool = False,
723722
) -> AsyncArray[ArrayV3Metadata]:
723+
print(config)
724724
if overwrite:
725725
if store_path.store.supports_deletes:
726726
await store_path.delete_dir()
@@ -4026,6 +4026,7 @@ async def from_array(
40264026
chunk_key_encoding=chunk_key_encoding,
40274027
dimension_names=dimension_names,
40284028
)
4029+
print(order)
40294030
if not hasattr(data, "dtype") or not hasattr(data, "shape"):
40304031
data = np.array(data)
40314032

@@ -4267,10 +4268,8 @@ async def init_array(
42674268
chunks_out = chunk_shape_parsed
42684269
codecs_out = sub_codecs
42694270

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

42754274
meta = AsyncArray._create_metadata_v3(
42764275
shape=shape_parsed,
@@ -4521,15 +4520,26 @@ def _parse_keep_array_attr(
45214520
serializer = "auto"
45224521
if fill_value is None:
45234522
fill_value = data.fill_value
4524-
if order is None:
4523+
4524+
if data.metadata.zarr_format == 2 and zarr_format == 3 and data.order == "F":
4525+
# Can't set order="F" for v3 arrays
4526+
warnings.warn(
4527+
"Zarr format 3 arrays are always stored with order='C'. "
4528+
"The existing order='F' of the source Zarr format 2 array will be ignored.",
4529+
UserWarning,
4530+
stacklevel=2,
4531+
)
4532+
elif order is None and zarr_format == 2:
45254533
order = data.order
4534+
45264535
if chunk_key_encoding is None and zarr_format == data.metadata.zarr_format:
45274536
if isinstance(data.metadata, ArrayV2Metadata):
45284537
chunk_key_encoding = {"name": "v2", "separator": data.metadata.dimension_separator}
45294538
elif isinstance(data.metadata, ArrayV3Metadata):
45304539
chunk_key_encoding = data.metadata.chunk_key_encoding
45314540
if dimension_names is None and data.metadata.zarr_format == 3:
45324541
dimension_names = data.metadata.dimension_names
4542+
print(order)
45334543
else:
45344544
if chunks == "keep":
45354545
chunks = "auto"

src/zarr/core/group.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2417,7 +2417,7 @@ def create_array(
24172417
compressor: CompressorLike = "auto",
24182418
serializer: SerializerLike = "auto",
24192419
fill_value: Any | None = 0,
2420-
order: MemoryOrder | None = "C",
2420+
order: MemoryOrder | None = None,
24212421
attributes: dict[str, JSON] | None = None,
24222422
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
24232423
dimension_names: DimensionNames = None,
@@ -2811,7 +2811,7 @@ def array(
28112811
compressor: CompressorLike = None,
28122812
serializer: SerializerLike = "auto",
28132813
fill_value: Any | None = 0,
2814-
order: MemoryOrder | None = "C",
2814+
order: MemoryOrder | None = None,
28152815
attributes: dict[str, JSON] | None = None,
28162816
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
28172817
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: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -339,34 +339,49 @@ def test_open_with_mode_w_minus(tmp_path: pathlib.Path) -> None:
339339
zarr.open(store=tmp_path, mode="w-")
340340

341341

342-
def test_array_order(zarr_format: ZarrFormat) -> None:
343-
arr = zarr.ones(shape=(2, 2), order=None, zarr_format=zarr_format)
344-
expected = zarr.config.get("array.order")
345-
assert arr.order == expected
346-
347-
vals = np.asarray(arr)
348-
if expected == "C":
349-
assert vals.flags.c_contiguous
350-
elif expected == "F":
351-
assert vals.flags.f_contiguous
352-
else:
353-
raise AssertionError
342+
@pytest.mark.parametrize("order", ["C", "F", None])
343+
@pytest.mark.parametrize("config", [{"order": "C"}, {"order": "F"}, {}], ids=["C", "F", "None"])
344+
def test_array_order(
345+
order: MemoryOrder, config: dict[str, MemoryOrder], zarr_format: ZarrFormat
346+
) -> None:
347+
"""
348+
Check that:
349+
- For v2, memory order is taken from the `order` keyword argument.
350+
- For v3, memory order is taken from `config`, and when order is passed a warning is raised
351+
- The numpy array returned has the expected order
352+
- For v2, the order metadata is set correctly
353+
"""
354+
default_order = zarr.config.get("array.order")
355+
if zarr_format == 3:
356+
if order is not None:
357+
ctx = pytest.warns(
358+
RuntimeWarning,
359+
match="The `order` keyword argument has no effect for Zarr format 3 arrays",
360+
)
361+
else:
362+
ctx = contextlib.nullcontext()
363+
expected_order = config.get("order", default_order)
354364

365+
if zarr_format == 2:
366+
ctx = contextlib.nullcontext()
367+
expected_order = order or config.get("order", default_order)
355368

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

372+
assert arr.order == expected_order
362373
vals = np.asarray(arr)
363-
if order == "C":
374+
if expected_order == "C":
364375
assert vals.flags.c_contiguous
365-
elif order == "F":
376+
elif expected_order == "F":
366377
assert vals.flags.f_contiguous
367378
else:
368379
raise AssertionError
369380

381+
if zarr_format == 2:
382+
assert arr.metadata.zarr_format == 2
383+
assert arr.metadata.order == expected_order
384+
370385

371386
# def test_lazy_loader():
372387
# foo = np.arange(100)

tests/test_array.py

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
import sys
88
from itertools import accumulate
9-
from typing import TYPE_CHECKING, Any, Literal
9+
from typing import Any, Literal
1010
from unittest import mock
1111

1212
import numcodecs
@@ -42,18 +42,15 @@
4242
from zarr.core.buffer.cpu import NDBuffer
4343
from zarr.core.chunk_grids import _auto_partition
4444
from zarr.core.chunk_key_encodings import ChunkKeyEncodingParams
45-
from zarr.core.common import JSON, MemoryOrder, ZarrFormat
45+
from zarr.core.common import JSON, ZarrFormat
4646
from zarr.core.group import AsyncGroup
4747
from zarr.core.indexing import BasicIndexer, ceildiv
48+
from zarr.core.metadata.v2 import ArrayV2Metadata
4849
from zarr.core.metadata.v3 import ArrayV3Metadata, DataType
4950
from zarr.core.sync import sync
5051
from zarr.errors import ContainsArrayError, ContainsGroupError
5152
from zarr.storage import LocalStore, MemoryStore, StorePath
5253

53-
if TYPE_CHECKING:
54-
from zarr.core.array_spec import ArrayConfigLike
55-
from zarr.core.metadata.v2 import ArrayV2Metadata
56-
5754

5855
@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"])
5956
@pytest.mark.parametrize("zarr_format", [2, 3])
@@ -1400,52 +1397,6 @@ async def test_data_ignored_params(store: Store) -> None:
14001397
):
14011398
await create_array(store, data=data, shape=None, dtype=data.dtype, overwrite=True)
14021399

1403-
@staticmethod
1404-
@pytest.mark.parametrize("order", ["C", "F", None])
1405-
@pytest.mark.parametrize("with_config", [True, False])
1406-
def test_order(
1407-
order: MemoryOrder | None,
1408-
with_config: bool,
1409-
zarr_format: ZarrFormat,
1410-
store: MemoryStore,
1411-
) -> None:
1412-
"""
1413-
Test that the arrays generated by array indexing have a memory order defined by the config order
1414-
value, and that for zarr v2 arrays, the ``order`` field in the array metadata is set correctly.
1415-
"""
1416-
config: ArrayConfigLike | None = {}
1417-
if order is None:
1418-
config = {}
1419-
expected = zarr.config.get("array.order")
1420-
else:
1421-
config = {"order": order}
1422-
expected = order
1423-
1424-
if not with_config:
1425-
# Test without passing config parameter
1426-
config = None
1427-
1428-
arr = zarr.create_array(
1429-
store=store,
1430-
shape=(2, 2),
1431-
zarr_format=zarr_format,
1432-
dtype="i4",
1433-
order=order,
1434-
config=config,
1435-
)
1436-
assert arr.order == expected
1437-
if zarr_format == 2:
1438-
assert arr.metadata.zarr_format == 2
1439-
assert arr.metadata.order == expected
1440-
1441-
vals = np.asarray(arr)
1442-
if expected == "C":
1443-
assert vals.flags.c_contiguous
1444-
elif expected == "F":
1445-
assert vals.flags.f_contiguous
1446-
else:
1447-
raise AssertionError
1448-
14491400
@staticmethod
14501401
@pytest.mark.parametrize("write_empty_chunks", [True, False])
14511402
async def test_write_empty_chunks_config(write_empty_chunks: bool, store: Store) -> None:

tests/test_metadata/test_consolidated.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ async def test_use_consolidated_for_children_members(
618618
expected = ["b", "b/c"]
619619
assert result == expected
620620

621+
621622
@pytest.mark.parametrize("fill_value", [np.nan, np.inf, -np.inf])
622623
async def test_consolidated_metadata_encodes_special_chars(
623624
memory_store: Store, zarr_format: ZarrFormat, fill_value: float

0 commit comments

Comments
 (0)