diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 06f7338755..f289663f53 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -27,6 +27,7 @@ from zarr.abc.numcodec import Numcodec, _is_numcodec from zarr.codecs._v2 import V2Codec from zarr.codecs.bytes import BytesCodec +from zarr.codecs.transpose import TransposeCodec from zarr.codecs.vlen_utf8 import VLenBytesCodec, VLenUTF8Codec from zarr.codecs.zstd import ZstdCodec from zarr.core._info import ArrayInfo @@ -4674,6 +4675,8 @@ async def init_array( attributes=attributes, ) else: + if order is not None and config is None: + _warn_order_kwarg() array_array, array_bytes, bytes_bytes = _parse_chunk_encoding_v3( compressors=compressors, filters=filters, @@ -4681,6 +4684,15 @@ async def init_array( dtype=zdtype, ) sub_codecs = cast("tuple[Codec, ...]", (*array_array, array_bytes, *bytes_bytes)) + + if order is not None and not any(isinstance(codec, TransposeCodec) for codec in sub_codecs): + sub_codecs = ( + *array_array, + TransposeCodec(order=tuple(reversed(range(len(shape_parsed))))), + array_bytes, + *bytes_bytes, + ) + config = replace(parse_array_config(config), order=order) # A stored chunk grid passed as chunks= / shards= (e.g. by the "keep" # path of from_array) becomes the stored grid verbatim, so the new # array preserves the source grid exactly — including trailing edges @@ -4710,8 +4722,6 @@ async def init_array( else: codecs_out = sub_codecs - if order is not None: - _warn_order_kwarg() meta = AsyncArray._create_metadata_v3( shape=shape_parsed, dtype=zdtype, diff --git a/tests/test_codecs/test_transpose.py b/tests/test_codecs/test_transpose.py index 949bb72a62..25cb392c1d 100644 --- a/tests/test_codecs/test_transpose.py +++ b/tests/test_codecs/test_transpose.py @@ -61,6 +61,32 @@ async def test_transpose( assert read_data.flags["C_CONTIGUOUS"] +@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) +def test_create_array_v3_order_f_preserves_fortran_output(store: Store) -> None: + data = np.arange(12, dtype="float32").reshape((3, 4), order="F") + spath = StorePath(store, "transpose_from_order") + + with pytest.warns( + RuntimeWarning, match="The `order` keyword argument has no effect for Zarr format 3 arrays" + ): + a = zarr.create_array( + spath, + shape=data.shape, + chunks=data.shape, + dtype=data.dtype, + order="F", + ) + a[:] = data + + reopened = zarr.open_array(spath).with_config({"order": "F"}) + read_data = np.asarray(reopened[:]) + + assert np.array_equal(read_data, data) + assert read_data.flags["F_CONTIGUOUS"] + assert not read_data.flags["C_CONTIGUOUS"] + assert any(isinstance(codec, TransposeCodec) for codec in reopened.filters) + + @pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) @pytest.mark.parametrize("order", [[1, 2, 0], [1, 2, 3, 0], [3, 2, 4, 0, 1]]) def test_transpose_non_self_inverse(store: Store, order: list[int]) -> None: