Skip to content

Commit c15a235

Browse files
d-v-bCipher
authored andcommitted
Rename the DimensionNames type to DimensionNamesLike. (zarr-developers#3800)
The reason for this change is the fact that the `DimensionNames` type is does not model the actual type of the `dimension_names` attribute on the Array V3 Metadata class, but rather a wider input type that is ultimately narrowed to that actual type. For this reason, it should use the same `XLike` name convention as the other wide input types that get narrowed to a more restricted type.
1 parent 25ee087 commit c15a235

File tree

9 files changed

+140
-32
lines changed

9 files changed

+140
-32
lines changed

src/zarr/api/asynchronous.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from zarr.core.common import (
2525
JSON,
2626
AccessModeLiteral,
27-
DimensionNames,
27+
DimensionNamesLike,
2828
MemoryOrder,
2929
ZarrFormat,
3030
_default_zarr_format,
@@ -914,7 +914,7 @@ async def create(
914914
| None
915915
) = None,
916916
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
917-
dimension_names: DimensionNames = None,
917+
dimension_names: DimensionNamesLike = None,
918918
storage_options: dict[str, Any] | None = None,
919919
config: ArrayConfigLike | None = None,
920920
**kwargs: Any,

src/zarr/api/synchronous.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from zarr.core.common import (
3434
JSON,
3535
AccessModeLiteral,
36-
DimensionNames,
36+
DimensionNamesLike,
3737
MemoryOrder,
3838
ShapeLike,
3939
ZarrFormat,
@@ -649,7 +649,7 @@ def create(
649649
| None
650650
) = None,
651651
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
652-
dimension_names: DimensionNames = None,
652+
dimension_names: DimensionNamesLike = None,
653653
storage_options: dict[str, Any] | None = None,
654654
config: ArrayConfigLike | None = None,
655655
**kwargs: Any,
@@ -832,7 +832,7 @@ def create_array(
832832
zarr_format: ZarrFormat | None = 3,
833833
attributes: dict[str, JSON] | None = None,
834834
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
835-
dimension_names: DimensionNames = None,
835+
dimension_names: DimensionNamesLike = None,
836836
storage_options: dict[str, Any] | None = None,
837837
overwrite: bool = False,
838838
config: ArrayConfigLike | None = None,
@@ -1003,7 +1003,7 @@ def from_array(
10031003
zarr_format: ZarrFormat | None = None,
10041004
attributes: dict[str, JSON] | None = None,
10051005
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
1006-
dimension_names: DimensionNames = None,
1006+
dimension_names: DimensionNamesLike = None,
10071007
storage_options: dict[str, Any] | None = None,
10081008
overwrite: bool = False,
10091009
config: ArrayConfigLike | None = None,

src/zarr/core/array.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
ZARR_JSON,
5454
ZARRAY_JSON,
5555
ZATTRS_JSON,
56-
DimensionNames,
56+
DimensionNamesLike,
5757
MemoryOrder,
5858
ShapeLike,
5959
ZarrFormat,
@@ -389,7 +389,7 @@ async def create(
389389
| None
390390
) = None,
391391
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
392-
dimension_names: DimensionNames = None,
392+
dimension_names: DimensionNamesLike = None,
393393
# runtime
394394
overwrite: bool = False,
395395
data: npt.ArrayLike | None = None,
@@ -417,7 +417,7 @@ async def create(
417417
| None
418418
) = None,
419419
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
420-
dimension_names: DimensionNames = None,
420+
dimension_names: DimensionNamesLike = None,
421421
# runtime
422422
overwrite: bool = False,
423423
data: npt.ArrayLike | None = None,
@@ -445,7 +445,7 @@ async def create(
445445
| None
446446
) = None,
447447
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
448-
dimension_names: DimensionNames = None,
448+
dimension_names: DimensionNamesLike = None,
449449
# v2 only
450450
chunks: ShapeLike | None = None,
451451
dimension_separator: Literal[".", "/"] | None = None,
@@ -479,7 +479,7 @@ async def create(
479479
| None
480480
) = None,
481481
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
482-
dimension_names: DimensionNames = None,
482+
dimension_names: DimensionNamesLike = None,
483483
# v2 only
484484
chunks: ShapeLike | None = None,
485485
dimension_separator: Literal[".", "/"] | None = None,
@@ -630,7 +630,7 @@ async def _create(
630630
| None
631631
) = None,
632632
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
633-
dimension_names: DimensionNames = None,
633+
dimension_names: DimensionNamesLike = None,
634634
# v2 only
635635
chunks: ShapeLike | None = None,
636636
dimension_separator: Literal[".", "/"] | None = None,
@@ -742,7 +742,7 @@ def _create_metadata_v3(
742742
fill_value: Any | None = DEFAULT_FILL_VALUE,
743743
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
744744
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
745-
dimension_names: DimensionNames = None,
745+
dimension_names: DimensionNamesLike = None,
746746
attributes: dict[str, JSON] | None = None,
747747
) -> ArrayV3Metadata:
748748
"""
@@ -803,7 +803,7 @@ async def _create_v3(
803803
| None
804804
) = None,
805805
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
806-
dimension_names: DimensionNames = None,
806+
dimension_names: DimensionNamesLike = None,
807807
attributes: dict[str, JSON] | None = None,
808808
overwrite: bool = False,
809809
) -> AsyncArrayV3:
@@ -1998,7 +1998,7 @@ def create(
19981998
| None
19991999
) = None,
20002000
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
2001-
dimension_names: DimensionNames = None,
2001+
dimension_names: DimensionNamesLike = None,
20022002
# v2 only
20032003
chunks: tuple[int, ...] | None = None,
20042004
dimension_separator: Literal[".", "/"] | None = None,
@@ -2142,7 +2142,7 @@ def _create(
21422142
| None
21432143
) = None,
21442144
codecs: Iterable[Codec | dict[str, JSON]] | None = None,
2145-
dimension_names: DimensionNames = None,
2145+
dimension_names: DimensionNamesLike = None,
21462146
# v2 only
21472147
chunks: tuple[int, ...] | None = None,
21482148
dimension_separator: Literal[".", "/"] | None = None,
@@ -4266,7 +4266,7 @@ async def from_array(
42664266
zarr_format: ZarrFormat | None = None,
42674267
attributes: dict[str, JSON] | None = None,
42684268
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
4269-
dimension_names: DimensionNames = None,
4269+
dimension_names: DimensionNamesLike = None,
42704270
storage_options: dict[str, Any] | None = None,
42714271
overwrite: bool = False,
42724272
config: ArrayConfigLike | None = None,
@@ -4535,7 +4535,7 @@ async def init_array(
45354535
zarr_format: ZarrFormat | None = 3,
45364536
attributes: dict[str, JSON] | None = None,
45374537
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
4538-
dimension_names: DimensionNames = None,
4538+
dimension_names: DimensionNamesLike = None,
45394539
overwrite: bool = False,
45404540
config: ArrayConfigLike | None = None,
45414541
) -> AnyAsyncArray:
@@ -4751,7 +4751,7 @@ async def create_array(
47514751
zarr_format: ZarrFormat | None = 3,
47524752
attributes: dict[str, JSON] | None = None,
47534753
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
4754-
dimension_names: DimensionNames = None,
4754+
dimension_names: DimensionNamesLike = None,
47554755
storage_options: dict[str, Any] | None = None,
47564756
overwrite: bool = False,
47574757
config: ArrayConfigLike | None = None,
@@ -4935,7 +4935,7 @@ def _parse_keep_array_attr(
49354935
order: MemoryOrder | None,
49364936
zarr_format: ZarrFormat | None,
49374937
chunk_key_encoding: ChunkKeyEncodingLike | None,
4938-
dimension_names: DimensionNames,
4938+
dimension_names: DimensionNamesLike,
49394939
) -> tuple[
49404940
tuple[int, ...] | Literal["auto"],
49414941
ShardsLike | None,
@@ -4946,7 +4946,7 @@ def _parse_keep_array_attr(
49464946
MemoryOrder | None,
49474947
ZarrFormat,
49484948
ChunkKeyEncodingLike | None,
4949-
DimensionNames,
4949+
DimensionNamesLike,
49504950
]:
49514951
if isinstance(data, Array):
49524952
if chunks == "keep":

src/zarr/core/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
MemoryOrder = Literal["C", "F"]
4848
AccessModeLiteral = Literal["r", "r+", "a", "w", "w-"]
4949
ANY_ACCESS_MODE: Final = "r", "r+", "a", "w", "w-"
50-
DimensionNames = Iterable[str | None] | None
50+
DimensionNamesLike = Iterable[str | None] | None
51+
DimensionNames = DimensionNamesLike # for backwards compatibility
5152

5253
TName = TypeVar("TName", bound=str)
5354
TConfig = TypeVar("TConfig", bound=Mapping[str, object])

src/zarr/core/group.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
ZATTRS_JSON,
4141
ZGROUP_JSON,
4242
ZMETADATA_V2_JSON,
43-
DimensionNames,
43+
DimensionNamesLike,
4444
NodeType,
4545
ShapeLike,
4646
ZarrFormat,
@@ -1032,7 +1032,7 @@ async def create_array(
10321032
order: MemoryOrder | None = None,
10331033
attributes: dict[str, JSON] | None = None,
10341034
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
1035-
dimension_names: DimensionNames = None,
1035+
dimension_names: DimensionNamesLike = None,
10361036
storage_options: dict[str, Any] | None = None,
10371037
overwrite: bool = False,
10381038
config: ArrayConfigLike | None = None,
@@ -2483,7 +2483,7 @@ def create(
24832483
order: MemoryOrder | None = None,
24842484
attributes: dict[str, JSON] | None = None,
24852485
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
2486-
dimension_names: DimensionNames = None,
2486+
dimension_names: DimensionNamesLike = None,
24872487
storage_options: dict[str, Any] | None = None,
24882488
overwrite: bool = False,
24892489
config: ArrayConfigLike | None = None,
@@ -2627,7 +2627,7 @@ def create_array(
26272627
order: MemoryOrder | None = None,
26282628
attributes: dict[str, JSON] | None = None,
26292629
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
2630-
dimension_names: DimensionNames = None,
2630+
dimension_names: DimensionNamesLike = None,
26312631
storage_options: dict[str, Any] | None = None,
26322632
overwrite: bool = False,
26332633
config: ArrayConfigLike | None = None,
@@ -3025,7 +3025,7 @@ def array(
30253025
order: MemoryOrder | None = None,
30263026
attributes: dict[str, JSON] | None = None,
30273027
chunk_key_encoding: ChunkKeyEncodingLike | None = None,
3028-
dimension_names: DimensionNames = None,
3028+
dimension_names: DimensionNamesLike = None,
30293029
storage_options: dict[str, Any] | None = None,
30303030
overwrite: bool = False,
30313031
config: ArrayConfigLike | None = None,

src/zarr/core/metadata/v3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from zarr.core.common import (
3434
JSON,
3535
ZARR_JSON,
36-
DimensionNames,
36+
DimensionNamesLike,
3737
NamedConfig,
3838
parse_named_configuration,
3939
parse_shapelike,
@@ -220,7 +220,7 @@ def __init__(
220220
fill_value: object,
221221
codecs: Iterable[Codec | dict[str, JSON] | NamedConfig[str, Any] | str],
222222
attributes: dict[str, JSON] | None,
223-
dimension_names: DimensionNames,
223+
dimension_names: DimensionNamesLike,
224224
storage_transformers: Iterable[dict[str, JSON]] | None = None,
225225
extra_fields: Mapping[str, AllowedExtraField] | None = None,
226226
) -> None:

test_repro.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import asyncio
2+
import zarr
3+
4+
async def list_prefix(store, prefix):
5+
return sorted([k async for k in store.list_prefix(prefix)])
6+
7+
async def run():
8+
print("\n=== Testing with actual data ===")
9+
10+
# Create memory store
11+
print("\nMemoryStore:")
12+
store_mem = zarr.storage.MemoryStore()
13+
root_mem = zarr.open_group(store_mem, mode="w")
14+
root_mem.create_group("0")
15+
root_mem.create_array("0_c", shape=(1,), dtype="i4")
16+
result = await list_prefix(store_mem, "0")
17+
print(f" list_prefix('0'): {result}")
18+
19+
# Also print all keys
20+
all_keys = sorted([k async for k in store_mem.list()])
21+
print(f" all keys: {all_keys}")
22+
23+
# Create local store
24+
print("\nLocalStore:")
25+
import shutil
26+
import tempfile
27+
tmpdir = tempfile.mkdtemp()
28+
store_local = zarr.storage.LocalStore(root=tmpdir)
29+
root_local = zarr.open_group(store_local, mode="w")
30+
root_local.create_group("0")
31+
root_local.create_array("0_c", shape=(1,), dtype="i4")
32+
result = await list_prefix(store_local, "0")
33+
print(f" list_prefix('0'): {result}")
34+
35+
# Also print all keys
36+
all_keys = sorted([k async for k in store_local.list()])
37+
print(f" all keys: {all_keys}")
38+
39+
# Cleanup
40+
shutil.rmtree(tmpdir)
41+
42+
print("\n=== The Issue ===")
43+
print("When calling list_prefix('0'):")
44+
print("- MemoryStore returns both '0/zarr.json' and '0_c/zarr.json'")
45+
print("- LocalStore returns only '0/zarr.json'")
46+
print("\nThe divergence is in how they interpret the prefix:")
47+
print("- MemoryStore: Uses string.startswith() which matches '0_c' because it starts with '0'")
48+
print("- LocalStore: Treats '0' as a directory path, so it doesn't match '0_c'")
49+
50+
asyncio.run(run())

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from zarr.core.chunk_grids import RegularChunkGrid, _auto_partition
2626
from zarr.core.common import (
2727
JSON,
28-
DimensionNames,
28+
DimensionNamesLike,
2929
MemoryOrder,
3030
ShapeLike,
3131
ZarrFormat,
@@ -313,7 +313,7 @@ def create_array_metadata(
313313
zarr_format: ZarrFormat,
314314
attributes: dict[str, JSON] | None = None,
315315
chunk_key_encoding: ChunkKeyEncoding | ChunkKeyEncodingLike | None = None,
316-
dimension_names: DimensionNames = None,
316+
dimension_names: DimensionNamesLike = None,
317317
) -> ArrayV2Metadata | ArrayV3Metadata:
318318
"""
319319
Create array metadata
@@ -452,7 +452,7 @@ def meta_from_array(
452452
zarr_format: ZarrFormat = 3,
453453
attributes: dict[str, JSON] | None = None,
454454
chunk_key_encoding: ChunkKeyEncoding | ChunkKeyEncodingLike | None = None,
455-
dimension_names: DimensionNames = None,
455+
dimension_names: DimensionNamesLike = None,
456456
) -> ArrayV3Metadata | ArrayV2Metadata:
457457
"""
458458
Create array metadata from an array
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Tests for scalar indexing (Issue #3741)."""
2+
3+
import numpy as np
4+
import pytest
5+
6+
import zarr
7+
8+
9+
class TestScalarIndexing:
10+
"""Test that scalar indexing returns numpy scalars, matching numpy behavior."""
11+
12+
def test_1d_scalar_indexing(self):
13+
"""Test scalar indexing on 1-D array returns numpy scalar."""
14+
arr_zarr = zarr.array([1, 2, 3, 4, 5], dtype="int64")
15+
arr_numpy = np.array([1, 2, 3, 4, 5], dtype="int64")
16+
17+
result_zarr = arr_zarr[0]
18+
result_numpy = arr_numpy[0]
19+
20+
assert type(result_zarr) == type(result_numpy)
21+
assert result_zarr == result_numpy
22+
assert not isinstance(result_zarr, np.ndarray)
23+
assert isinstance(result_zarr, np.generic)
24+
25+
def test_2d_scalar_indexing(self):
26+
"""Test scalar indexing on 2-D array returns numpy scalar."""
27+
arr_zarr = zarr.array([[1, 2, 3], [4, 5, 6]], dtype="int64")
28+
arr_numpy = np.array([[1, 2, 3], [4, 5, 6]], dtype="int64")
29+
30+
result_zarr = arr_zarr[0, 0]
31+
result_numpy = arr_numpy[0, 0]
32+
33+
assert type(result_zarr) == type(result_numpy)
34+
assert result_zarr == result_numpy
35+
assert not isinstance(result_zarr, np.ndarray)
36+
37+
def test_slice_indexing_returns_array(self):
38+
"""Test that slice indexing still returns arrays."""
39+
arr_zarr = zarr.array([1, 2, 3, 4, 5])
40+
result = arr_zarr[0:2]
41+
42+
assert isinstance(result, np.ndarray)
43+
assert result.ndim == 1
44+
assert len(result) == 2
45+
46+
def test_partial_scalar_indexing_on_2d(self):
47+
"""Test partial scalar indexing on 2-D array returns 1-D array."""
48+
arr_zarr = zarr.array([[1, 2, 3], [4, 5, 6]], dtype="int64")
49+
arr_numpy = np.array([[1, 2, 3], [4, 5, 6]], dtype="int64")
50+
51+
result_zarr = arr_zarr[0]
52+
result_numpy = arr_numpy[0]
53+
54+
assert type(result_zarr) == type(result_numpy)
55+
assert isinstance(result_zarr, np.ndarray)
56+
assert result_zarr.ndim == 1
57+
np.testing.assert_array_equal(result_zarr, result_numpy)

0 commit comments

Comments
 (0)