Skip to content

Commit 4a18c5a

Browse files
authored
fix(array): thread order parameter through to Array.__init__ (#2417)
1 parent 1e8c0a8 commit 4a18c5a

File tree

7 files changed

+67
-36
lines changed

7 files changed

+67
-36
lines changed

src/zarr/api/asynchronous.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ async def create(
712712
dtype: npt.DTypeLike | None = None,
713713
compressor: dict[str, JSON] | None = None, # TODO: default and type change
714714
fill_value: Any | None = 0, # TODO: need type
715-
order: MemoryOrder | None = None, # TODO: default change
715+
order: MemoryOrder | None = None,
716716
store: str | StoreLike | None = None,
717717
synchronizer: Any | None = None,
718718
overwrite: bool = False,
@@ -761,6 +761,7 @@ async def create(
761761
Default value to use for uninitialized portions of the array.
762762
order : {'C', 'F'}, optional
763763
Memory layout to be used within each chunk.
764+
Default is set in Zarr's config (`array.order`).
764765
store : Store or str
765766
Store or path to directory in file system or name of zip file.
766767
synchronizer : object, optional
@@ -834,12 +835,6 @@ async def create(
834835
else:
835836
chunk_shape = shape
836837

837-
if order is not None:
838-
warnings.warn(
839-
"order is deprecated, use config `array.order` instead",
840-
DeprecationWarning,
841-
stacklevel=2,
842-
)
843838
if synchronizer is not None:
844839
warnings.warn("synchronizer is not yet implemented", RuntimeWarning, stacklevel=2)
845840
if chunk_store is not None:
@@ -889,6 +884,7 @@ async def create(
889884
codecs=codecs,
890885
dimension_names=dimension_names,
891886
attributes=attributes,
887+
order=order,
892888
**kwargs,
893889
)
894890

src/zarr/core/array.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ZARRAY_JSON,
3434
ZATTRS_JSON,
3535
ChunkCoords,
36+
MemoryOrder,
3637
ShapeLike,
3738
ZarrFormat,
3839
concurrent_map,
@@ -203,29 +204,29 @@ class AsyncArray(Generic[T_ArrayMetadata]):
203204
metadata: T_ArrayMetadata
204205
store_path: StorePath
205206
codec_pipeline: CodecPipeline = field(init=False)
206-
order: Literal["C", "F"]
207+
order: MemoryOrder
207208

208209
@overload
209210
def __init__(
210211
self: AsyncArray[ArrayV2Metadata],
211212
metadata: ArrayV2Metadata | ArrayV2MetadataDict,
212213
store_path: StorePath,
213-
order: Literal["C", "F"] | None = None,
214+
order: MemoryOrder | None = None,
214215
) -> None: ...
215216

216217
@overload
217218
def __init__(
218219
self: AsyncArray[ArrayV3Metadata],
219220
metadata: ArrayV3Metadata | ArrayV3MetadataDict,
220221
store_path: StorePath,
221-
order: Literal["C", "F"] | None = None,
222+
order: MemoryOrder | None = None,
222223
) -> None: ...
223224

224225
def __init__(
225226
self,
226227
metadata: ArrayMetadata | ArrayMetadataDict,
227228
store_path: StorePath,
228-
order: Literal["C", "F"] | None = None,
229+
order: MemoryOrder | None = None,
229230
) -> None:
230231
if isinstance(metadata, dict):
231232
zarr_format = metadata["zarr_format"]
@@ -261,7 +262,7 @@ async def create(
261262
attributes: dict[str, JSON] | None = None,
262263
chunks: ShapeLike | None = None,
263264
dimension_separator: Literal[".", "/"] | None = None,
264-
order: Literal["C", "F"] | None = None,
265+
order: MemoryOrder | None = None,
265266
filters: list[dict[str, JSON]] | None = None,
266267
compressor: dict[str, JSON] | None = None,
267268
# runtime
@@ -350,7 +351,7 @@ async def create(
350351
# v2 only
351352
chunks: ShapeLike | None = None,
352353
dimension_separator: Literal[".", "/"] | None = None,
353-
order: Literal["C", "F"] | None = None,
354+
order: MemoryOrder | None = None,
354355
filters: list[dict[str, JSON]] | None = None,
355356
compressor: dict[str, JSON] | None = None,
356357
# runtime
@@ -382,7 +383,7 @@ async def create(
382383
# v2 only
383384
chunks: ShapeLike | None = None,
384385
dimension_separator: Literal[".", "/"] | None = None,
385-
order: Literal["C", "F"] | None = None,
386+
order: MemoryOrder | None = None,
386387
filters: list[dict[str, JSON]] | None = None,
387388
compressor: dict[str, JSON] | None = None,
388389
# runtime
@@ -422,7 +423,6 @@ async def create(
422423
V2 only. V3 arrays cannot have a dimension separator.
423424
order : Literal["C", "F"], optional
424425
The order of the array (default is None).
425-
V2 only. V3 arrays should not have 'order' parameter.
426426
filters : list[dict[str, JSON]], optional
427427
The filters used to compress the data (default is None).
428428
V2 only. V3 arrays should not have 'filters' parameter.
@@ -471,10 +471,6 @@ async def create(
471471
raise ValueError(
472472
"dimension_separator cannot be used for arrays with version 3. Use chunk_key_encoding instead."
473473
)
474-
if order is not None:
475-
raise ValueError(
476-
"order cannot be used for arrays with version 3. Use a transpose codec instead."
477-
)
478474
if filters is not None:
479475
raise ValueError(
480476
"filters cannot be used for arrays with version 3. Use array-to-array codecs instead."
@@ -494,6 +490,7 @@ async def create(
494490
dimension_names=dimension_names,
495491
attributes=attributes,
496492
exists_ok=exists_ok,
493+
order=order,
497494
)
498495
elif zarr_format == 2:
499496
if dtype is str or dtype == "str":
@@ -545,6 +542,7 @@ async def _create_v3(
545542
dtype: npt.DTypeLike,
546543
chunk_shape: ChunkCoords,
547544
fill_value: Any | None = None,
545+
order: MemoryOrder | None = None,
548546
chunk_key_encoding: (
549547
ChunkKeyEncoding
550548
| tuple[Literal["default"], Literal[".", "/"]]
@@ -588,7 +586,7 @@ async def _create_v3(
588586
attributes=attributes or {},
589587
)
590588

591-
array = cls(metadata=metadata, store_path=store_path)
589+
array = cls(metadata=metadata, store_path=store_path, order=order)
592590
await array._save_metadata(metadata, ensure_parents=True)
593591
return array
594592

@@ -602,16 +600,17 @@ async def _create_v2(
602600
chunks: ChunkCoords,
603601
dimension_separator: Literal[".", "/"] | None = None,
604602
fill_value: None | float = None,
605-
order: Literal["C", "F"] | None = None,
603+
order: MemoryOrder | None = None,
606604
filters: list[dict[str, JSON]] | None = None,
607605
compressor: dict[str, JSON] | None = None,
608606
attributes: dict[str, JSON] | None = None,
609607
exists_ok: bool = False,
610608
) -> AsyncArray[ArrayV2Metadata]:
611609
if not exists_ok:
612610
await ensure_no_existing_node(store_path, zarr_format=2)
611+
613612
if order is None:
614-
order = "C"
613+
order = parse_indexing_order(config.get("array.order"))
615614

616615
if dimension_separator is None:
617616
dimension_separator = "."
@@ -627,7 +626,7 @@ async def _create_v2(
627626
filters=filters,
628627
attributes=attributes,
629628
)
630-
array = cls(metadata=metadata, store_path=store_path)
629+
array = cls(metadata=metadata, store_path=store_path, order=order)
631630
await array._save_metadata(metadata, ensure_parents=True)
632631
return array
633632

@@ -1179,7 +1178,7 @@ def create(
11791178
# v2 only
11801179
chunks: ChunkCoords | None = None,
11811180
dimension_separator: Literal[".", "/"] | None = None,
1182-
order: Literal["C", "F"] | None = None,
1181+
order: MemoryOrder | None = None,
11831182
filters: list[dict[str, JSON]] | None = None,
11841183
compressor: dict[str, JSON] | None = None,
11851184
# runtime
@@ -1370,7 +1369,7 @@ def store_path(self) -> StorePath:
13701369
return self._async_array.store_path
13711370

13721371
@property
1373-
def order(self) -> Literal["C", "F"]:
1372+
def order(self) -> MemoryOrder:
13741373
return self._async_array.order
13751374

13761375
@property

src/zarr/core/array_spec.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from typing import TYPE_CHECKING, Any, Literal
4+
from typing import TYPE_CHECKING, Any
55

66
import numpy as np
77

8-
from zarr.core.common import parse_fill_value, parse_order, parse_shapelike
8+
from zarr.core.common import MemoryOrder, parse_fill_value, parse_order, parse_shapelike
99

1010
if TYPE_CHECKING:
1111
from zarr.core.buffer import BufferPrototype
@@ -17,15 +17,15 @@ class ArraySpec:
1717
shape: ChunkCoords
1818
dtype: np.dtype[Any]
1919
fill_value: Any
20-
order: Literal["C", "F"]
20+
order: MemoryOrder
2121
prototype: BufferPrototype
2222

2323
def __init__(
2424
self,
2525
shape: ChunkCoords,
2626
dtype: np.dtype[Any],
2727
fill_value: Any,
28-
order: Literal["C", "F"],
28+
order: MemoryOrder,
2929
prototype: BufferPrototype,
3030
) -> None:
3131
shape_parsed = parse_shapelike(shape)

src/zarr/core/metadata/v2.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from zarr.core.array_spec import ArraySpec
2626
from zarr.core.chunk_grids import RegularChunkGrid
2727
from zarr.core.chunk_key_encodings import parse_separator
28-
from zarr.core.common import ZARRAY_JSON, ZATTRS_JSON, parse_shapelike
28+
from zarr.core.common import ZARRAY_JSON, ZATTRS_JSON, MemoryOrder, parse_shapelike
2929
from zarr.core.config import config, parse_indexing_order
3030
from zarr.core.metadata.common import parse_attributes
3131

@@ -45,7 +45,7 @@ class ArrayV2Metadata(Metadata):
4545
chunks: tuple[int, ...]
4646
dtype: np.dtype[Any]
4747
fill_value: None | int | float | str | bytes = 0
48-
order: Literal["C", "F"] = "C"
48+
order: MemoryOrder = "C"
4949
filters: tuple[numcodecs.abc.Codec, ...] | None = None
5050
dimension_separator: Literal[".", "/"] = "."
5151
compressor: numcodecs.abc.Codec | None = None
@@ -59,7 +59,7 @@ def __init__(
5959
dtype: npt.DTypeLike,
6060
chunks: ChunkCoords,
6161
fill_value: Any,
62-
order: Literal["C", "F"],
62+
order: MemoryOrder,
6363
dimension_separator: Literal[".", "/"] = ".",
6464
compressor: numcodecs.abc.Codec | dict[str, JSON] | None = None,
6565
filters: Iterable[numcodecs.abc.Codec | dict[str, JSON]] | None = None,
@@ -185,7 +185,7 @@ def to_dict(self) -> dict[str, JSON]:
185185
return zarray_dict
186186

187187
def get_chunk_spec(
188-
self, _chunk_coords: ChunkCoords, order: Literal["C", "F"], prototype: BufferPrototype
188+
self, _chunk_coords: ChunkCoords, order: MemoryOrder, prototype: BufferPrototype
189189
) -> ArraySpec:
190190
return ArraySpec(
191191
shape=self.chunks,

src/zarr/core/metadata/v3.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
JSON,
3232
ZARR_JSON,
3333
ChunkCoords,
34+
MemoryOrder,
3435
parse_named_configuration,
3536
parse_shapelike,
3637
)
@@ -289,7 +290,7 @@ def ndim(self) -> int:
289290
return len(self.shape)
290291

291292
def get_chunk_spec(
292-
self, _chunk_coords: ChunkCoords, order: Literal["C", "F"], prototype: BufferPrototype
293+
self, _chunk_coords: ChunkCoords, order: MemoryOrder, prototype: BufferPrototype
293294
) -> ArraySpec:
294295
assert isinstance(
295296
self.chunk_grid, RegularChunkGrid

tests/test_api.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
save_array,
2222
save_group,
2323
)
24-
from zarr.core.common import ZarrFormat
24+
from zarr.core.common import MemoryOrder, ZarrFormat
2525
from zarr.errors import MetadataValidationError
2626
from zarr.storage._utils import normalize_path
2727
from zarr.storage.memory import MemoryStore
@@ -206,6 +206,22 @@ def test_open_with_mode_w_minus(tmp_path: pathlib.Path) -> None:
206206
zarr.open(store=tmp_path, mode="w-")
207207

208208

209+
@pytest.mark.parametrize("order", ["C", "F", None])
210+
@pytest.mark.parametrize("zarr_format", [2, 3])
211+
def test_array_order(order: MemoryOrder | None, zarr_format: ZarrFormat) -> None:
212+
arr = zarr.ones(shape=(2, 2), order=order, zarr_format=zarr_format)
213+
expected = order or zarr.config.get("array.order")
214+
assert arr.order == expected
215+
216+
vals = np.asarray(arr)
217+
if expected == "C":
218+
assert vals.flags.c_contiguous
219+
elif expected == "F":
220+
assert vals.flags.f_contiguous
221+
else:
222+
raise AssertionError
223+
224+
209225
# def test_lazy_loader():
210226
# foo = np.arange(100)
211227
# bar = np.arange(100, 0, -1)

tests/test_array.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from zarr.codecs import BytesCodec, VLenBytesCodec
1111
from zarr.core.array import chunks_initialized
1212
from zarr.core.buffer.cpu import NDBuffer
13-
from zarr.core.common import JSON, ZarrFormat
13+
from zarr.core.common import JSON, MemoryOrder, ZarrFormat
1414
from zarr.core.group import AsyncGroup
1515
from zarr.core.indexing import ceildiv
1616
from zarr.core.sync import sync
@@ -417,3 +417,22 @@ def test_update_attrs(zarr_format: int) -> None:
417417

418418
arr2 = zarr.open_array(store=store, zarr_format=zarr_format)
419419
assert arr2.attrs["foo"] == "bar"
420+
421+
422+
@pytest.mark.parametrize("order", ["C", "F", None])
423+
@pytest.mark.parametrize("zarr_format", [2, 3])
424+
@pytest.mark.parametrize("store", ["memory"], indirect=True)
425+
def test_array_create_order(
426+
order: MemoryOrder | None, zarr_format: int, store: MemoryStore
427+
) -> None:
428+
arr = Array.create(store=store, shape=(2, 2), order=order, zarr_format=zarr_format, dtype="i4")
429+
expected = order or zarr.config.get("array.order")
430+
assert arr.order == expected
431+
432+
vals = np.asarray(arr)
433+
if expected == "C":
434+
assert vals.flags.c_contiguous
435+
elif expected == "F":
436+
assert vals.flags.f_contiguous
437+
else:
438+
raise AssertionError

0 commit comments

Comments
 (0)