Skip to content

Commit 8edfafa

Browse files
committed
feat(api): declare mode and data on create
`create` accepted `mode` and `data` only through `**kwargs`: `mode` was popped for opening the store and `data` reached `AsyncArray._create`. Both are now named, documented parameters of the sync and async `create`, and `**kwargs` is gone, so an unknown keyword raises a TypeError from `create` itself. Assisted-by: ClaudeCode:claude-opus-5-5
1 parent 9677081 commit 8edfafa

4 files changed

Lines changed: 83 additions & 7 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`zarr.create` and `zarr.api.asynchronous.create` now declare and document their `mode` and `data` parameters. Both parameters were accepted before but were not part of the signature. `create` no longer takes arbitrary keyword arguments, so an unknown keyword raises a `TypeError` naming `create` instead of the private `AsyncArray._create`.

‎src/zarr/api/asynchronous.py‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,8 @@ async def create(
922922
dimension_names: DimensionNamesLike = None,
923923
storage_options: dict[str, Any] | None = None,
924924
config: ArrayConfigLike | None = None,
925-
**kwargs: Any,
925+
mode: AccessModeLiteral | None = None,
926+
data: npt.ArrayLike | None = None,
926927
) -> AnyAsyncArray:
927928
"""Create an array.
928929
@@ -1042,6 +1043,15 @@ async def create(
10421043
config : ArrayConfigLike, optional
10431044
Runtime configuration of the array. If provided, will override the
10441045
default values from `zarr.config.array`.
1046+
mode : {'r', 'r+', 'a', 'w', 'w-'}, optional
1047+
The access mode used to open `store`. The default, `None`, is `'a'`.
1048+
With `'w'`, everything stored under `path` is deleted before the array is
1049+
created. With `'w-'`, creation fails if anything is stored under `path`.
1050+
With `'r'`, the store is opened read-only and creation fails.
1051+
Ignored if `store` is a `StorePath`, which is already open.
1052+
data : array-like, optional
1053+
Values written into the new array after it is created, as `array[...] = data`.
1054+
`data` does not determine the shape or data type of the array.
10451055
10461056
Returns
10471057
-------
@@ -1066,7 +1076,6 @@ async def create(
10661076
if write_empty_chunks is not None:
10671077
_warn_write_empty_chunks_kwarg()
10681078

1069-
mode = kwargs.pop("mode", None)
10701079
if mode is None:
10711080
mode = "a"
10721081
store_path = await make_store_path(store, path=path, mode=mode, storage_options=storage_options)
@@ -1102,7 +1111,7 @@ async def create(
11021111
dimension_names=dimension_names,
11031112
attributes=attributes,
11041113
config=config_parsed,
1105-
**kwargs,
1114+
data=data,
11061115
)
11071116

11081117

‎src/zarr/api/synchronous.py‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,6 @@ def create_group(
630630
)
631631

632632

633-
# TODO: add type annotations for kwargs
634633
def create(
635634
shape: tuple[int, ...] | int,
636635
*, # Note: this is a change from v2
@@ -666,7 +665,8 @@ def create(
666665
dimension_names: DimensionNamesLike = None,
667666
storage_options: dict[str, Any] | None = None,
668667
config: ArrayConfigLike | None = None,
669-
**kwargs: Any,
668+
mode: AccessModeLiteral | None = None,
669+
data: npt.ArrayLike | None = None,
670670
) -> AnyArray:
671671
"""Create an array.
672672
@@ -786,6 +786,15 @@ def create(
786786
config : ArrayConfigLike, optional
787787
Runtime configuration of the array. If provided, will override the
788788
default values from `zarr.config.array`.
789+
mode : {'r', 'r+', 'a', 'w', 'w-'}, optional
790+
The access mode used to open `store`. The default, `None`, is `'a'`.
791+
With `'w'`, everything stored under `path` is deleted before the array is
792+
created. With `'w-'`, creation fails if anything is stored under `path`.
793+
With `'r'`, the store is opened read-only and creation fails.
794+
Ignored if `store` is a `StorePath`, which is already open.
795+
data : array-like, optional
796+
Values written into the new array after it is created, as `array[...] = data`.
797+
`data` does not determine the shape or data type of the array.
789798
790799
Returns
791800
-------
@@ -822,7 +831,8 @@ def create(
822831
dimension_names=dimension_names,
823832
storage_options=storage_options,
824833
config=config,
825-
**kwargs,
834+
mode=mode,
835+
data=data,
826836
)
827837
)
828838
)

‎tests/test_api.py‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from pathlib import Path
1616

1717
from zarr.abc.store import Store
18-
from zarr.core.common import JSON, MemoryOrder, ZarrFormat
18+
from zarr.core.common import JSON, AccessModeLiteral, MemoryOrder, ZarrFormat
1919
from zarr.types import AnyArray
2020

2121
import contextlib
@@ -46,6 +46,7 @@
4646
from zarr.core.buffer import NDArrayLike
4747
from zarr.errors import (
4848
ArrayNotFoundError,
49+
ContainsArrayError,
4950
MetadataValidationError,
5051
ZarrDeprecationWarning,
5152
ZarrUserWarning,
@@ -84,6 +85,61 @@ def test_create(memory_store: Store) -> None:
8485
z = create(shape=(400, 100), chunks=(16, 16.5), store=store, overwrite=True) # type: ignore[arg-type]
8586

8687

88+
@pytest.mark.parametrize(
89+
("mode", "existing"),
90+
[(None, False), ("a", False), ("r+", False), ("w", False), ("w", True), ("w-", False)],
91+
)
92+
@pytest.mark.parametrize("data", [None, np.arange(3, dtype="float64")])
93+
def test_create_mode_data(mode: AccessModeLiteral | None, existing: bool, data: Any) -> None:
94+
"""
95+
`create` opens the store with `mode` and writes `data` into the new array.
96+
"""
97+
store = MemoryStore()
98+
if existing:
99+
create_array(store, shape=(2,), dtype="int8")
100+
z = create(shape=(3,), store=store, mode=mode, data=data)
101+
assert z.shape == (3,)
102+
assert z.dtype == np.dtype("float64")
103+
expected = np.zeros(3) if data is None else data
104+
assert_array_equal(z[:], expected)
105+
106+
107+
def test_create_mode_a_existing_array() -> None:
108+
"""
109+
`create` with mode `'a'` refuses to replace an existing array.
110+
"""
111+
store = MemoryStore()
112+
create_array(store, shape=(2,), dtype="int8")
113+
with pytest.raises(ContainsArrayError):
114+
create(shape=(3,), store=store, mode="a")
115+
116+
117+
def test_create_mode_w_minus_existing_data() -> None:
118+
"""
119+
`create` with mode `'w-'` refuses to write where data is stored.
120+
"""
121+
store = MemoryStore()
122+
create_array(store, shape=(2,), dtype="int8")
123+
with pytest.raises(FileExistsError, match="mode 'w-'"):
124+
create(shape=(3,), store=store, mode="w-")
125+
126+
127+
def test_create_mode_r() -> None:
128+
"""
129+
`create` with mode `'r'` opens the store read-only, so creation fails.
130+
"""
131+
with pytest.raises(ValueError, match="read-only"):
132+
create(shape=(3,), store=MemoryStore(), mode="r")
133+
134+
135+
def test_create_unknown_keyword() -> None:
136+
"""
137+
`create` rejects keyword arguments it does not declare, naming itself in the error.
138+
"""
139+
with pytest.raises(TypeError, match=r"^create\(\) got an unexpected keyword argument 'typo'"):
140+
create(shape=(3,), typo=1) # type: ignore[call-arg]
141+
142+
87143
@pytest.mark.parametrize(
88144
"func",
89145
[

0 commit comments

Comments
 (0)