Skip to content
Open
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/4401.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `Array.dimension_names` and `AsyncArray.dimension_names`, which return the array's dimension names, or `None` if it has none. Zarr format 2 arrays always return `None`.
44 changes: 42 additions & 2 deletions src/zarr/core/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,26 @@ def shards(self) -> tuple[int, ...] | None:
"""
return self.metadata.shards

@property
def dimension_names(self) -> tuple[str | None, ...] | None:
"""Returns the names of the array's dimensions.

Returns None if the array has no dimension names, which is always the
case for Zarr format 2 arrays.

Dimension names are not required to be unique: two dimensions of the
same array may share a name. The Zarr v3 specification recommends, but
does not require, that non-null names are distinct. See the
[`dimension_names` section of the Zarr v3 core specification](https://github.com/zarr-developers/zarr-specs/blob/ad8fc8df42441c84039c94569980e485e4c09870/docs/v3/core/index.rst#L632-L647).

Returns
-------
tuple[str | None, ...] | None
One name per dimension, where an unnamed dimension is None, or None
if the array has no dimension names.
"""
return self.metadata.dimension_names

@property
def size(self) -> int:
"""Returns the total number of elements in the array
Expand Down Expand Up @@ -2157,6 +2177,26 @@ def shards(self) -> tuple[int, ...] | None:
"""
return self.async_array.shards

@property
def dimension_names(self) -> tuple[str | None, ...] | None:
"""Returns the names of the array's dimensions.

Returns None if the array has no dimension names, which is always the
case for Zarr format 2 arrays.

Dimension names are not required to be unique: two dimensions of the
same array may share a name. The Zarr v3 specification recommends, but
does not require, that non-null names are distinct. See the
[`dimension_names` section of the Zarr v3 core specification](https://github.com/zarr-developers/zarr-specs/blob/ad8fc8df42441c84039c94569980e485e4c09870/docs/v3/core/index.rst#L632-L647).

Returns
-------
tuple[str | None, ...] | None
One name per dimension, where an unnamed dimension is None, or None
if the array has no dimension names.
"""
return self.async_array.dimension_names

@property
def size(self) -> int:
"""Returns the total number of elements in the array.
Expand Down Expand Up @@ -4963,8 +5003,8 @@ def _parse_keep_array_attr(
chunk_key_encoding = {"name": "v2", "separator": data.metadata.dimension_separator}
elif isinstance(data.metadata, ArrayV3Metadata):
chunk_key_encoding = data.metadata.chunk_key_encoding
if dimension_names is None and data.metadata.zarr_format == 3:
dimension_names = data.metadata.dimension_names
if dimension_names is None:
dimension_names = data.dimension_names
if attributes is None:
# Deep copy so nested containers are not shared between the source
# array's in-memory metadata and the new array's.
Expand Down
5 changes: 5 additions & 0 deletions src/zarr/core/metadata/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ def chunk_grid(self) -> ChunkGrid:
def shards(self) -> tuple[int, ...] | None:
return None

@property
def dimension_names(self) -> None:
"""Always `None`: Zarr format 2 has no dimension names."""
return None

def to_buffer_dict(self, prototype: BufferPrototype) -> dict[str, Buffer]:
zarray_dict = self.to_dict()
zattrs_dict = zarray_dict.pop("attributes", {})
Expand Down
37 changes: 36 additions & 1 deletion tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import zarr.api.asynchronous
import zarr.api.synchronous as sync_api
from tests.conftest import json_attributes, skip_object_dtype
from tests.conftest import Expect, json_attributes, skip_object_dtype
from zarr import Array, Group
from zarr.abc.store import Store
from zarr.codecs import (
Expand Down Expand Up @@ -195,6 +195,41 @@ def test_array_name_properties_no_group(
assert arr.basename == ""


@pytest.mark.parametrize(
"case",
[
Expect(input=(2, None), output=None, id="v2"),
Expect(input=(3, None), output=None, id="v3-no-names"),
Expect(input=(3, ["x", "y"]), output=("x", "y"), id="v3-names"),
Expect(input=(3, ["x", None]), output=("x", None), id="v3-partial-names"),
# names are not required to be unique
Expect(input=(3, ["x", "x"]), output=("x", "x"), id="v3-repeated-names"),
],
ids=lambda c: c.id,
)
async def test_array_dimension_names(
case: Expect[tuple[ZarrFormat, list[str | None] | None], tuple[str | None, ...] | None],
) -> None:
"""
`Array.dimension_names` and `AsyncArray.dimension_names` return the array's
dimension names as a tuple, or None when it has none, as for every Zarr
format 2 array. They are preserved when the array is reopened.
"""
zarr_format, dimension_names = case.input
store = MemoryStore()
arr = zarr.create_array(
store=store,
shape=(2, 3),
dtype="i1",
zarr_format=zarr_format,
dimension_names=dimension_names,
)
assert arr.dimension_names == case.output
assert arr.async_array.dimension_names == case.output
reopened = await zarr.api.asynchronous.open_array(store=store)
assert reopened.dimension_names == case.output


@pytest.mark.parametrize("store", ["local", "memory", "zip"], indirect=["store"])
@pytest.mark.parametrize("zarr_format", [2, 3])
def test_array_name_properties_with_group(
Expand Down
Loading