From 2af64ca5392cac128352c9cd2608e266e7818d45 Mon Sep 17 00:00:00 2001 From: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:22:35 -0700 Subject: [PATCH 1/2] fix: let open_like create arrays by default --- changes/3352.bugfix.md | 1 + src/zarr/api/asynchronous.py | 2 ++ tests/test_api.py | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 changes/3352.bugfix.md diff --git a/changes/3352.bugfix.md b/changes/3352.bugfix.md new file mode 100644 index 0000000000..5ae761497a --- /dev/null +++ b/changes/3352.bugfix.md @@ -0,0 +1 @@ +Fix `zarr.api.asynchronous.open_like` so it can create a new array by default when the target path does not already exist. diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index c751d6a31c..2721647970 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -1300,6 +1300,8 @@ async def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyAsyncArray: The opened array. """ like_kwargs = _like_args(a) | kwargs + if like_kwargs.get("mode") is None: + like_kwargs["mode"] = "a" return await open_array(path=path, **like_kwargs) # type: ignore[arg-type] diff --git a/tests/test_api.py b/tests/test_api.py index 1b4414ae63..a7de5aede3 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -168,6 +168,33 @@ async def test_array_like_creation( assert np.all(Array(new_arr)[:] == expect_fill) +@pytest.mark.parametrize("mode_kwargs", [{}, {"mode": None}]) +async def test_open_like_creates_array_by_default( + zarr_format: ZarrFormat, mode_kwargs: dict[str, None] +) -> None: + ref_arr = zarr.create_array( + store={}, + shape=(11, 12), + dtype="uint8", + chunks=(11, 12), + zarr_format=zarr_format, + fill_value=100, + ) + + new_arr = await zarr.api.asynchronous.open_like( + ref_arr, + path="foo", + store={}, + zarr_format=zarr_format, + **mode_kwargs, + ) + + assert new_arr.shape == ref_arr.shape + assert new_arr.chunks == ref_arr.chunks + assert new_arr.dtype == ref_arr.dtype + assert np.all(Array(new_arr)[:] == ref_arr.fill_value) + + # TODO: parametrize over everything this function takes @pytest.mark.parametrize("store", ["memory"], indirect=True) def test_create_array(store: Store, zarr_format: ZarrFormat) -> None: From 595400dcbf3027d1983b6d9f5b1e96cdd797617b Mon Sep 17 00:00:00 2001 From: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com> Date: Tue, 21 Jul 2026 05:11:29 +0200 Subject: [PATCH 2/2] test: cover open_like read-only mode --- changes/3352.bugfix.md | 4 +++- src/zarr/api/asynchronous.py | 4 +++- src/zarr/api/synchronous.py | 6 ++++-- tests/test_api.py | 20 ++++++++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/changes/3352.bugfix.md b/changes/3352.bugfix.md index 5ae761497a..7461486776 100644 --- a/changes/3352.bugfix.md +++ b/changes/3352.bugfix.md @@ -1 +1,3 @@ -Fix `zarr.api.asynchronous.open_like` so it can create a new array by default when the target path does not already exist. +Fix `zarr.api.asynchronous.open_like` so it can create a new array by default when the +target path does not already exist. It now defaults to `mode="a"`; when using a read-only +store to open an existing array, pass `mode="r"` explicitly. diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 2721647970..f5e614a051 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -1292,7 +1292,9 @@ async def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyAsyncArray: path : str The path to the new array. **kwargs - Any keyword arguments to pass to the array constructor. + Additional keyword arguments passed to `open_array`. + If `mode` is omitted or `None`, it defaults to `"a"`. Pass `mode="r"` when + opening an existing array from a read-only store. Returns ------- diff --git a/src/zarr/api/synchronous.py b/src/zarr/api/synchronous.py index 3231837a04..dc12d5f7af 100644 --- a/src/zarr/api/synchronous.py +++ b/src/zarr/api/synchronous.py @@ -1399,11 +1399,13 @@ def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyArray: path : str The path to the new array. **kwargs - Any keyword arguments to pass to the array constructor. + Additional keyword arguments passed to `open_array`. + If `mode` is omitted or `None`, it defaults to `"a"`. Pass `mode="r"` when + opening an existing array from a read-only store. Returns ------- - AsyncArray + Array The opened array. """ return Array(sync(async_api.open_like(a, path=path, **kwargs))) diff --git a/tests/test_api.py b/tests/test_api.py index a7de5aede3..cbe8ea3b44 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -195,6 +195,26 @@ async def test_open_like_creates_array_by_default( assert np.all(Array(new_arr)[:] == ref_arr.fill_value) +async def test_open_like_default_mode_rejects_read_only_store( + zarr_format: ZarrFormat, +) -> None: + ref_arr = zarr.create_array( + store={}, + shape=(11, 12), + dtype="uint8", + chunks=(11, 12), + zarr_format=zarr_format, + ) + + with pytest.raises(ValueError, match="Store is read-only but mode is 'a'"): + await zarr.api.asynchronous.open_like( + ref_arr, + path="foo", + store=MemoryStore(read_only=True), + zarr_format=zarr_format, + ) + + # TODO: parametrize over everything this function takes @pytest.mark.parametrize("store", ["memory"], indirect=True) def test_create_array(store: Store, zarr_format: ZarrFormat) -> None: