Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/4412.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`Group.empty`, `zeros`, `ones`, `full` and their `*_like` versions now create arrays in the zarr format of the group. Previously a zarr v2 group got zarr v3 arrays, which were written into the group without becoming members of it. Creating an array like an array of the other zarr format (for example `zarr.zeros_like(v2_array)`) no longer fails. The new array uses the default codecs of its own format, and top-level `*_like` functions inherit the zarr format of the source array unless `zarr_format` is passed.
85 changes: 64 additions & 21 deletions src/zarr/api/asynchronous.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,18 @@ class _LikeArgs(TypedDict):
compressor: NotRequired[CompressorLikev2]
codecs: NotRequired[tuple[Codec, ...]]
fill_value: NotRequired[Any]
zarr_format: NotRequired[ZarrFormat]


def _like_args(a: ArrayLike) -> _LikeArgs:
"""Set default values for shape and chunks if they are not present in the array-like object"""
def _like_args(a: ArrayLike, zarr_format: ZarrFormat | None) -> _LikeArgs:
"""
Arguments for creating an array like `a` in `zarr_format`.

If `a` is a zarr array, the new array has the zarr format of `a` unless `zarr_format`
requests another. The storage settings of `a` (memory order and codecs) are specific to
its zarr format, so they are copied only when the new array has the format of `a`.
Otherwise the new array uses the defaults of its own format.
"""
new: _LikeArgs = {}

shape, chunks = _get_shape_chunks(a)
Expand All @@ -166,21 +173,27 @@ def _like_args(a: ArrayLike) -> _LikeArgs:

if isinstance(a, AsyncArray | Array):
new["fill_value"] = a.metadata.fill_value
if isinstance(a.metadata, ArrayV2Metadata):
new["order"] = a.order
new["compressor"] = a.metadata.compressor
new["filters"] = a.metadata.filters
else:
# TODO: Remove type: ignore statement when type inference improves.
# mypy cannot correctly infer the type of a.metadata here for some reason.
new["codecs"] = a.metadata.codecs
if zarr_format is None:
zarr_format = a.metadata.zarr_format
if a.metadata.zarr_format == zarr_format:
if isinstance(a.metadata, ArrayV2Metadata):
new["order"] = a.order
new["compressor"] = a.metadata.compressor
new["filters"] = a.metadata.filters
else:
# TODO: Remove type: ignore statement when type inference improves.
# mypy cannot correctly infer the type of a.metadata here for some reason.
new["codecs"] = a.metadata.codecs

else:
# TODO: set default values compressor/codecs
# to do this, we may need to evaluate if this is a v2 or v3 array
# new["compressor"] = "default"
pass

if zarr_format is not None:
new["zarr_format"] = zarr_format

return new


Expand Down Expand Up @@ -1146,14 +1159,19 @@ async def empty(shape: tuple[int, ...], **kwargs: Any) -> AnyAsyncArray:
return await create(shape=shape, **kwargs)


async def empty_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
async def empty_like(
a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any
) -> AnyAsyncArray:
"""Create an empty array like `a`. The contents will be filled with the
array's fill value or zeros if no fill value is provided.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`create`][zarr.api.asynchronous.create].

Expand All @@ -1168,7 +1186,7 @@ async def empty_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
retrieve data from an empty Zarr array, any values may be returned,
and these are not guaranteed to be stable from one access to the next.
"""
like_kwargs = _like_args(a) | kwargs
like_kwargs = _like_args(a, zarr_format) | kwargs
return await empty(**like_kwargs) # type: ignore[arg-type]


Expand All @@ -1195,13 +1213,18 @@ async def full(shape: tuple[int, ...], fill_value: Any, **kwargs: Any) -> AnyAsy


# TODO: add type annotations for kwargs
async def full_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
async def full_like(
a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any
) -> AnyAsyncArray:
"""Create a filled array like `a`.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`zarr.api.asynchronous.create`][].

Expand All @@ -1210,7 +1233,7 @@ async def full_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
Array
The new array.
"""
like_kwargs = _like_args(a) | kwargs
like_kwargs = _like_args(a, zarr_format) | kwargs
return await full(**like_kwargs) # type: ignore[arg-type]


Expand All @@ -1233,13 +1256,18 @@ async def ones(shape: tuple[int, ...], **kwargs: Any) -> AnyAsyncArray:
return await create(shape=shape, fill_value=1, **kwargs)


async def ones_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
async def ones_like(
a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any
) -> AnyAsyncArray:
"""Create an array of ones like `a`.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`zarr.api.asynchronous.create`][].

Expand All @@ -1248,7 +1276,7 @@ async def ones_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
Array
The new array.
"""
like_args = _like_args(a)
like_args = _like_args(a, zarr_format)
# `ones` supplies its own fill_value, so drop any inherited from `a`.
like_args.pop("fill_value", None)
like_kwargs = like_args | kwargs
Expand Down Expand Up @@ -1309,7 +1337,9 @@ async def open_array(
raise ArrayNotFoundError(msg) from err


async def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyAsyncArray:
async def open_like(
a: ArrayLike, path: str, *, zarr_format: ZarrFormat | None = None, **kwargs: Any
) -> AnyAsyncArray:
"""Open a persistent array like `a`.

Parameters
Expand All @@ -1318,6 +1348,10 @@ async def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyAsyncArray:
The shape and data-type of a define these same attributes of the returned array.
path : str
The path to the new array.
zarr_format : {2, 3, None}, optional
The zarr format of the array to open or create. If `None` (default), an existing
array of either format is opened, and a missing one is created in the default zarr
format. The zarr format of `a` is not inherited.
**kwargs
Additional keyword arguments passed to `open_array`.
If `mode` is omitted or `None`, it defaults to `"a"`. Pass `mode="r"` when
Expand All @@ -1328,10 +1362,14 @@ async def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyAsyncArray:
AsyncArray
The opened array.
"""
like_kwargs = _like_args(a) | kwargs
# The zarr format of `a` is not inherited: `open_array` would then look for an existing
# array in only that format.
like_args = _like_args(a, zarr_format or _default_zarr_format())
like_args.pop("zarr_format")
like_kwargs = like_args | kwargs
if like_kwargs.get("mode") is None:
like_kwargs["mode"] = "a"
return await open_array(path=path, **like_kwargs) # type: ignore[arg-type]
return await open_array(path=path, zarr_format=zarr_format, **like_kwargs) # type: ignore[arg-type]


async def zeros(shape: tuple[int, ...], **kwargs: Any) -> AnyAsyncArray:
Expand All @@ -1353,13 +1391,18 @@ async def zeros(shape: tuple[int, ...], **kwargs: Any) -> AnyAsyncArray:
return await create(shape=shape, fill_value=0, **kwargs)


async def zeros_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
async def zeros_like(
a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any
) -> AnyAsyncArray:
"""Create an array of zeros like `a`.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`create`][zarr.api.asynchronous.create].

Expand All @@ -1368,7 +1411,7 @@ async def zeros_like(a: ArrayLike, **kwargs: Any) -> AnyAsyncArray:
Array
The new array.
"""
like_args = _like_args(a)
like_args = _like_args(a, zarr_format)
# `zeros` supplies its own fill_value, so drop any inherited from `a`.
like_args.pop("fill_value", None)
like_kwargs = like_args | kwargs
Expand Down
38 changes: 28 additions & 10 deletions src/zarr/api/synchronous.py
Original file line number Diff line number Diff line change
Expand Up @@ -1278,14 +1278,17 @@ def empty(shape: tuple[int, ...], **kwargs: Any) -> AnyArray:

# TODO: move ArrayLike to common module
# TODO: add type annotations for kwargs
def empty_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
def empty_like(a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any) -> AnyArray:
"""Create an empty array like another array. The contents will be filled with the
array's fill value or zeros if no fill value is provided.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`create`][zarr.api.asynchronous.create].

Expand All @@ -1300,7 +1303,7 @@ def empty_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
retrieve data from an empty Zarr array, any values may be returned,
and these are not guaranteed to be stable from one access to the next.
"""
return Array(sync(async_api.empty_like(a, **kwargs)))
return Array(sync(async_api.empty_like(a, zarr_format=zarr_format, **kwargs)))


# TODO: add type annotations for kwargs and fill_value
Expand All @@ -1326,13 +1329,16 @@ def full(shape: tuple[int, ...], fill_value: Any, **kwargs: Any) -> AnyArray:

# TODO: move ArrayLike to common module
# TODO: add type annotations for kwargs
def full_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
def full_like(a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any) -> AnyArray:
"""Create a filled array like another array.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`zarr.api.asynchronous.create`][].

Expand All @@ -1341,7 +1347,7 @@ def full_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
Array
The new array.
"""
return Array(sync(async_api.full_like(a, **kwargs)))
return Array(sync(async_api.full_like(a, zarr_format=zarr_format, **kwargs)))


# TODO: add type annotations for kwargs
Expand All @@ -1364,13 +1370,16 @@ def ones(shape: tuple[int, ...], **kwargs: Any) -> AnyArray:


# TODO: add type annotations for kwargs
def ones_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
def ones_like(a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any) -> AnyArray:
"""Create an array of ones like another array.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`zarr.api.asynchronous.create`][].

Expand All @@ -1379,7 +1388,7 @@ def ones_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
Array
The new array.
"""
return Array(sync(async_api.ones_like(a, **kwargs)))
return Array(sync(async_api.ones_like(a, zarr_format=zarr_format, **kwargs)))


# TODO: update this once async_api.open_array is fully implemented
Expand Down Expand Up @@ -1429,7 +1438,9 @@ def open_array(


# TODO: add type annotations for kwargs
def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyArray:
def open_like(
a: ArrayLike, path: str, *, zarr_format: ZarrFormat | None = None, **kwargs: Any
) -> AnyArray:
"""Open a persistent array like another array.

Parameters
Expand All @@ -1438,6 +1449,10 @@ def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyArray:
The shape and data-type of a define these same attributes of the returned array.
path : str
The path to the new array.
zarr_format : {2, 3, None}, optional
The zarr format of the array to open or create. If `None` (default), an existing
array of either format is opened, and a missing one is created in the default zarr
format. The zarr format of `a` is not inherited.
**kwargs
Additional keyword arguments passed to `open_array`.
If `mode` is omitted or `None`, it defaults to `"a"`. Pass `mode="r"` when
Expand All @@ -1448,7 +1463,7 @@ def open_like(a: ArrayLike, path: str, **kwargs: Any) -> AnyArray:
Array
The opened array.
"""
return Array(sync(async_api.open_like(a, path=path, **kwargs)))
return Array(sync(async_api.open_like(a, path=path, zarr_format=zarr_format, **kwargs)))


# TODO: add type annotations for kwargs
Expand All @@ -1471,13 +1486,16 @@ def zeros(shape: tuple[int, ...], **kwargs: Any) -> AnyArray:


# TODO: add type annotations for kwargs
def zeros_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
def zeros_like(a: ArrayLike, *, zarr_format: ZarrFormat | None = None, **kwargs: Any) -> AnyArray:
"""Create an array of zeros like another array.

Parameters
----------
a : array-like
The array to create an empty array like.
zarr_format : {2, 3, None}, optional
The zarr format of the new array. If `None` (default), the zarr format of `a` if it
is a zarr array, otherwise the default zarr format.
**kwargs
Keyword arguments passed to [`create`][zarr.api.asynchronous.create].

Expand All @@ -1486,4 +1504,4 @@ def zeros_like(a: ArrayLike, **kwargs: Any) -> AnyArray:
Array
The new array.
"""
return Array(sync(async_api.zeros_like(a, **kwargs)))
return Array(sync(async_api.zeros_like(a, zarr_format=zarr_format, **kwargs)))
Loading
Loading