Skip to content

Commit 44ad6c7

Browse files
committed
deprecate positional args
1 parent 94933b3 commit 44ad6c7

File tree

6 files changed

+167
-2
lines changed

6 files changed

+167
-2
lines changed

src/zarr/_compat.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import warnings
2+
from collections.abc import Callable
3+
from functools import wraps
4+
from inspect import Parameter, signature
5+
from typing import Any, TypeVar
6+
7+
T = TypeVar("T")
8+
9+
10+
def _deprecate_positional_args(
11+
func: Callable[..., T] | None = None, *, version: str = "1.3"
12+
) -> Callable[..., T]:
13+
"""Decorator for methods that issues warnings for positional arguments.
14+
15+
Using the keyword-only argument syntax in pep 3102, arguments after the
16+
* will issue a warning when passed as a positional argument.
17+
18+
Parameters
19+
----------
20+
func : callable, default=None
21+
Function to check arguments on.
22+
version : callable, default="1.3"
23+
The version when positional arguments will result in error.
24+
"""
25+
26+
def _inner_deprecate_positional_args(f: Callable[..., T]) -> Callable[..., T]:
27+
sig = signature(f)
28+
kwonly_args = []
29+
all_args = []
30+
31+
for name, param in sig.parameters.items():
32+
if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
33+
all_args.append(name)
34+
elif param.kind == Parameter.KEYWORD_ONLY:
35+
kwonly_args.append(name)
36+
37+
@wraps(f)
38+
def inner_f(*args: Any, **kwargs: Any) -> T:
39+
extra_args = len(args) - len(all_args)
40+
if extra_args <= 0:
41+
return f(*args, **kwargs)
42+
43+
# extra_args > 0
44+
args_msg = [
45+
f"{name}={arg}"
46+
for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:], strict=False)
47+
]
48+
formatted_args_msg = ", ".join(args_msg)
49+
warnings.warn(
50+
(
51+
f"Pass {formatted_args_msg} as keyword args. From version "
52+
f"{version} passing these as positional arguments "
53+
"will result in an error"
54+
),
55+
FutureWarning,
56+
stacklevel=2,
57+
)
58+
kwargs.update(zip(sig.parameters, args, strict=False))
59+
return f(**kwargs)
60+
61+
return inner_f
62+
63+
if func is not None:
64+
return _inner_deprecate_positional_args(func)
65+
66+
return _inner_deprecate_positional_args # type: ignore[return-value]

src/zarr/api/synchronous.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Any
44

55
import zarr.api.asynchronous as async_api
6+
from zarr._compat import _deprecate_positional_args
67
from zarr.core.array import Array, AsyncArray
78
from zarr.core.buffer import NDArrayLike
89
from zarr.core.common import JSON, AccessModeLiteral, ChunkCoords, ZarrFormat
@@ -61,6 +62,7 @@ def load(
6162
return sync(async_api.load(store=store, zarr_version=zarr_version, path=path))
6263

6364

65+
@_deprecate_positional_args
6466
def open(
6567
store: StoreLike | None = None,
6668
*,
@@ -105,6 +107,7 @@ def save(
105107
)
106108

107109

110+
@_deprecate_positional_args
108111
def save_array(
109112
store: StoreLike,
110113
arr: NDArrayLike,
@@ -155,9 +158,10 @@ def array(data: NDArrayLike, **kwargs: Any) -> Array:
155158
return Array(sync(async_api.array(data=data, **kwargs)))
156159

157160

161+
@_deprecate_positional_args
158162
def group(
159-
*, # Note: this is a change from v2
160163
store: StoreLike | None = None,
164+
*, # Note: this is a change from v2
161165
overwrite: bool = False,
162166
chunk_store: StoreLike | None = None, # not used in async_api
163167
cache_attrs: bool | None = None, # default changed, not used in async_api
@@ -186,6 +190,7 @@ def group(
186190
)
187191

188192

193+
@_deprecate_positional_args
189194
def open_group(
190195
store: StoreLike | None = None,
191196
*, # Note: this is a change from v2

src/zarr/core/array.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import numpy as np
1010
import numpy.typing as npt
1111

12+
from zarr._compat import _deprecate_positional_args
1213
from zarr.abc.codec import Codec, CodecPipeline
1314
from zarr.abc.store import set_or_delete
1415
from zarr.codecs import BytesCodec
@@ -604,6 +605,7 @@ class Array:
604605
_async_array: AsyncArray
605606

606607
@classmethod
608+
@_deprecate_positional_args
607609
def create(
608610
cls,
609611
store: StoreLike,
@@ -999,6 +1001,7 @@ def __setitem__(self, selection: Selection, value: npt.ArrayLike) -> None:
9991001
else:
10001002
self.set_basic_selection(cast(BasicSelection, pure_selection), value, fields=fields)
10011003

1004+
@_deprecate_positional_args
10021005
def get_basic_selection(
10031006
self,
10041007
selection: BasicSelection = Ellipsis,
@@ -1122,6 +1125,7 @@ def get_basic_selection(
11221125
)
11231126
)
11241127

1128+
@_deprecate_positional_args
11251129
def set_basic_selection(
11261130
self,
11271131
selection: BasicSelection,
@@ -1217,6 +1221,7 @@ def set_basic_selection(
12171221
indexer = BasicIndexer(selection, self.shape, self.metadata.chunk_grid)
12181222
sync(self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype))
12191223

1224+
@_deprecate_positional_args
12201225
def get_orthogonal_selection(
12211226
self,
12221227
selection: OrthogonalSelection,
@@ -1341,6 +1346,7 @@ def get_orthogonal_selection(
13411346
)
13421347
)
13431348

1349+
@_deprecate_positional_args
13441350
def set_orthogonal_selection(
13451351
self,
13461352
selection: OrthogonalSelection,
@@ -1451,6 +1457,7 @@ def set_orthogonal_selection(
14511457
self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype)
14521458
)
14531459

1460+
@_deprecate_positional_args
14541461
def get_mask_selection(
14551462
self,
14561463
mask: MaskSelection,
@@ -1533,6 +1540,7 @@ def get_mask_selection(
15331540
)
15341541
)
15351542

1543+
@_deprecate_positional_args
15361544
def set_mask_selection(
15371545
self,
15381546
mask: MaskSelection,
@@ -1611,6 +1619,7 @@ def set_mask_selection(
16111619
indexer = MaskIndexer(mask, self.shape, self.metadata.chunk_grid)
16121620
sync(self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype))
16131621

1622+
@_deprecate_positional_args
16141623
def get_coordinate_selection(
16151624
self,
16161625
selection: CoordinateSelection,
@@ -1700,6 +1709,7 @@ def get_coordinate_selection(
17001709
out_array = np.array(out_array).reshape(indexer.sel_shape)
17011710
return out_array
17021711

1712+
@_deprecate_positional_args
17031713
def set_coordinate_selection(
17041714
self,
17051715
selection: CoordinateSelection,
@@ -1789,6 +1799,7 @@ def set_coordinate_selection(
17891799

17901800
sync(self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype))
17911801

1802+
@_deprecate_positional_args
17921803
def get_block_selection(
17931804
self,
17941805
selection: BasicSelection,
@@ -1887,6 +1898,7 @@ def get_block_selection(
18871898
)
18881899
)
18891900

1901+
@_deprecate_positional_args
18901902
def set_block_selection(
18911903
self,
18921904
selection: BasicSelection,

tests/v3/test_api.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
import numpy as np
24
import pytest
35
from numpy.testing import assert_array_equal
@@ -6,7 +8,8 @@
68
import zarr
79
from zarr import Array, Group
810
from zarr.abc.store import Store
9-
from zarr.api.synchronous import create, load, open, open_group, save, save_array, save_group
11+
from zarr.api.synchronous import create, group, load, open, open_group, save, save_array, save_group
12+
from zarr.store.memory import MemoryStore
1013

1114

1215
def test_create_array(memory_store: Store) -> None:
@@ -849,3 +852,37 @@ def test_tree() -> None:
849852
# # bad option
850853
# with pytest.raises(TypeError):
851854
# copy(source["foo"], dest, dry_run=True, log=True)
855+
856+
857+
def test_open_positional_args_deprecated():
858+
store = MemoryStore({}, mode="w")
859+
with pytest.warns(FutureWarning, match="pass"):
860+
open(store, "w", shape=(1,))
861+
862+
863+
def test_save_array_positional_args_deprecated():
864+
store = MemoryStore({}, mode="w")
865+
with warnings.catch_warnings():
866+
warnings.filterwarnings(
867+
"ignore", message="zarr_version is deprecated", category=DeprecationWarning
868+
)
869+
with pytest.warns(FutureWarning, match="pass"):
870+
save_array(
871+
store,
872+
np.ones(
873+
1,
874+
),
875+
3,
876+
)
877+
878+
879+
def test_group_positional_args_deprecated():
880+
store = MemoryStore({}, mode="w")
881+
with pytest.warns(FutureWarning, match="pass"):
882+
group(store, True)
883+
884+
885+
def test_open_group_positional_args_deprecated():
886+
store = MemoryStore({}, mode="w")
887+
with pytest.warns(FutureWarning, match="pass"):
888+
open_group(store, "w")

tests/v3/test_array.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from zarr import Array, Group
7+
from zarr.core.buffer import NDBuffer
78
from zarr.core.common import ZarrFormat
89
from zarr.errors import ContainsArrayError, ContainsGroupError
910
from zarr.store import LocalStore, MemoryStore
@@ -135,3 +136,44 @@ def test_array_v3_fill_value(store: MemoryStore, fill_value: int, dtype_str: str
135136

136137
assert arr.fill_value == np.dtype(dtype_str).type(fill_value)
137138
assert arr.fill_value.dtype == arr.dtype
139+
140+
141+
def test_create_positional_args_deprecated():
142+
store = MemoryStore({}, mode="w")
143+
with pytest.warns(FutureWarning, match="Pass"):
144+
Array.create(store, (2, 2), dtype="f8")
145+
146+
147+
def test_selection_positional_args_deprecated():
148+
store = MemoryStore({}, mode="w")
149+
arr = Array.create(store, shape=(2, 2), dtype="f8")
150+
151+
with pytest.warns(FutureWarning, match="Pass out"):
152+
arr.get_basic_selection(..., NDBuffer(array=np.empty((2, 2))))
153+
154+
with pytest.warns(FutureWarning, match="Pass fields"):
155+
arr.set_basic_selection(..., 1, None)
156+
157+
with pytest.warns(FutureWarning, match="Pass out"):
158+
arr.get_orthogonal_selection(..., NDBuffer(array=np.empty((2, 2))))
159+
160+
with pytest.warns(FutureWarning, match="Pass"):
161+
arr.set_orthogonal_selection(..., 1, None)
162+
163+
with pytest.warns(FutureWarning, match="Pass"):
164+
arr.get_mask_selection(np.zeros((2, 2), dtype=bool), NDBuffer(array=np.empty((0,))))
165+
166+
with pytest.warns(FutureWarning, match="Pass"):
167+
arr.set_mask_selection(np.zeros((2, 2), dtype=bool), 1, None)
168+
169+
with pytest.warns(FutureWarning, match="Pass"):
170+
arr.get_coordinate_selection(([0, 1], [0, 1]), NDBuffer(array=np.empty((2,))))
171+
172+
with pytest.warns(FutureWarning, match="Pass"):
173+
arr.set_coordinate_selection(([0, 1], [0, 1]), 1, None)
174+
175+
with pytest.warns(FutureWarning, match="Pass"):
176+
arr.get_block_selection((0, slice(None)), NDBuffer(array=np.empty((2, 2))))
177+
178+
with pytest.warns(FutureWarning, match="Pass"):
179+
arr.set_block_selection((0, slice(None)), 1, None)

tests/v3/test_sync.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,6 @@ def bar(self) -> list[int]:
122122
foo = SyncFoo(async_foo)
123123
assert foo.foo() == "foo"
124124
assert foo.bar() == list(range(10))
125+
126+
127+
def test_open_positional_args_deprecate(): ...

0 commit comments

Comments
 (0)