diff --git a/pyproject.toml b/pyproject.toml index 886cd5a0b..41c2006b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,8 @@ test = [ "flask", "requests", "mypy", - "hypothesis" + "hypothesis", + "universal-pathlib", ] jupyter = [ diff --git a/src/zarr/_compat.py b/src/zarr/_compat.py new file mode 100644 index 000000000..52d96005c --- /dev/null +++ b/src/zarr/_compat.py @@ -0,0 +1,68 @@ +import warnings +from collections.abc import Callable +from functools import wraps +from inspect import Parameter, signature +from typing import Any, TypeVar + +T = TypeVar("T") + +# Based off https://github.com/scikit-learn/scikit-learn/blob/e87b32a81c70abed8f2e97483758eb64df8255e9/sklearn/utils/validation.py#L63 + + +def _deprecate_positional_args( + func: Callable[..., T] | None = None, *, version: str = "3.1.0" +) -> Callable[..., T]: + """Decorator for methods that issues warnings for positional arguments. + + Using the keyword-only argument syntax in pep 3102, arguments after the + * will issue a warning when passed as a positional argument. + + Parameters + ---------- + func : callable, default=None + Function to check arguments on. + version : callable, default="3.1.0" + The version when positional arguments will result in error. + """ + + def _inner_deprecate_positional_args(f: Callable[..., T]) -> Callable[..., T]: + sig = signature(f) + kwonly_args = [] + all_args = [] + + for name, param in sig.parameters.items(): + if param.kind == Parameter.POSITIONAL_OR_KEYWORD: + all_args.append(name) + elif param.kind == Parameter.KEYWORD_ONLY: + kwonly_args.append(name) + + @wraps(f) + def inner_f(*args: Any, **kwargs: Any) -> T: + extra_args = len(args) - len(all_args) + if extra_args <= 0: + return f(*args, **kwargs) + + # extra_args > 0 + args_msg = [ + f"{name}={arg}" + for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:], strict=False) + ] + formatted_args_msg = ", ".join(args_msg) + warnings.warn( + ( + f"Pass {formatted_args_msg} as keyword args. From version " + f"{version} passing these as positional arguments " + "will result in an error" + ), + FutureWarning, + stacklevel=2, + ) + kwargs.update(zip(sig.parameters, args, strict=False)) + return f(**kwargs) + + return inner_f + + if func is not None: + return _inner_deprecate_positional_args(func) + + return _inner_deprecate_positional_args # type: ignore[return-value] diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 58278172f..5fbb38c5e 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -503,7 +503,7 @@ async def group( try: return await AsyncGroup.open(store=store_path, zarr_format=zarr_format) except (KeyError, FileNotFoundError): - return await AsyncGroup.create( + return await AsyncGroup.from_store( store=store_path, zarr_format=zarr_format or _default_zarr_version(), exists_ok=overwrite, @@ -512,8 +512,8 @@ async def group( async def open_group( - *, # Note: this is a change from v2 store: StoreLike | None = None, + *, # Note: this is a change from v2 mode: AccessModeLiteral | None = None, cache_attrs: bool | None = None, # not used, default changed synchronizer: Any = None, # not used @@ -590,7 +590,7 @@ async def open_group( try: return await AsyncGroup.open(store_path, zarr_format=zarr_format) except (KeyError, FileNotFoundError): - return await AsyncGroup.create( + return await AsyncGroup.from_store( store_path, zarr_format=zarr_format or _default_zarr_version(), exists_ok=True, diff --git a/src/zarr/api/synchronous.py b/src/zarr/api/synchronous.py index 129f90193..bc4a7bfaf 100644 --- a/src/zarr/api/synchronous.py +++ b/src/zarr/api/synchronous.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any import zarr.api.asynchronous as async_api +from zarr._compat import _deprecate_positional_args from zarr.core.array import Array, AsyncArray from zarr.core.group import Group from zarr.core.sync import sync @@ -63,9 +64,10 @@ def load( return sync(async_api.load(store=store, zarr_version=zarr_version, path=path)) +@_deprecate_positional_args def open( - *, store: StoreLike | None = None, + *, mode: AccessModeLiteral | None = None, # type and value changed zarr_version: ZarrFormat | None = None, # deprecated zarr_format: ZarrFormat | None = None, @@ -107,6 +109,7 @@ def save( ) +@_deprecate_positional_args def save_array( store: StoreLike, arr: NDArrayLike, @@ -159,9 +162,10 @@ def array(data: NDArrayLike, **kwargs: Any) -> Array: return Array(sync(async_api.array(data=data, **kwargs))) +@_deprecate_positional_args def group( - *, # Note: this is a change from v2 store: StoreLike | None = None, + *, # Note: this is a change from v2 overwrite: bool = False, chunk_store: StoreLike | None = None, # not used in async_api cache_attrs: bool | None = None, # default changed, not used in async_api @@ -190,9 +194,10 @@ def group( ) +@_deprecate_positional_args def open_group( - *, # Note: this is a change from v2 store: StoreLike | None = None, + *, # Note: this is a change from v2 mode: AccessModeLiteral | None = None, # not used in async api cache_attrs: bool | None = None, # default changed, not used in async api synchronizer: Any = None, # not used in async api diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 2cf6d6940..b825ca4ca 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -8,6 +8,8 @@ import numpy as np import numpy.typing as npt +from zarr._compat import _deprecate_positional_args +from zarr.abc.codec import Codec, CodecPipeline from zarr.abc.store import set_or_delete from zarr.codecs import BytesCodec from zarr.codecs._v2 import V2Compressor, V2Filters @@ -621,6 +623,7 @@ class Array: _async_array: AsyncArray @classmethod + @_deprecate_positional_args def create( cls, store: StoreLike, @@ -1016,6 +1019,7 @@ def __setitem__(self, selection: Selection, value: npt.ArrayLike) -> None: else: self.set_basic_selection(cast(BasicSelection, pure_selection), value, fields=fields) + @_deprecate_positional_args def get_basic_selection( self, selection: BasicSelection = Ellipsis, @@ -1139,6 +1143,7 @@ def get_basic_selection( ) ) + @_deprecate_positional_args def set_basic_selection( self, selection: BasicSelection, @@ -1234,6 +1239,7 @@ def set_basic_selection( indexer = BasicIndexer(selection, self.shape, self.metadata.chunk_grid) sync(self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype)) + @_deprecate_positional_args def get_orthogonal_selection( self, selection: OrthogonalSelection, @@ -1358,6 +1364,7 @@ def get_orthogonal_selection( ) ) + @_deprecate_positional_args def set_orthogonal_selection( self, selection: OrthogonalSelection, @@ -1468,6 +1475,7 @@ def set_orthogonal_selection( self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype) ) + @_deprecate_positional_args def get_mask_selection( self, mask: MaskSelection, @@ -1550,6 +1558,7 @@ def get_mask_selection( ) ) + @_deprecate_positional_args def set_mask_selection( self, mask: MaskSelection, @@ -1628,6 +1637,7 @@ def set_mask_selection( indexer = MaskIndexer(mask, self.shape, self.metadata.chunk_grid) sync(self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype)) + @_deprecate_positional_args def get_coordinate_selection( self, selection: CoordinateSelection, @@ -1717,6 +1727,7 @@ def get_coordinate_selection( out_array = np.array(out_array).reshape(indexer.sel_shape) return out_array + @_deprecate_positional_args def set_coordinate_selection( self, selection: CoordinateSelection, @@ -1806,6 +1817,7 @@ def set_coordinate_selection( sync(self._async_array._set_selection(indexer, value, fields=fields, prototype=prototype)) + @_deprecate_positional_args def get_block_selection( self, selection: BasicSelection, @@ -1904,6 +1916,7 @@ def get_block_selection( ) ) + @_deprecate_positional_args def set_block_selection( self, selection: BasicSelection, diff --git a/src/zarr/core/attributes.py b/src/zarr/core/attributes.py index 09677f7bd..62ff5fc93 100644 --- a/src/zarr/core/attributes.py +++ b/src/zarr/core/attributes.py @@ -35,3 +35,19 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self._obj.metadata.attributes) + + def put(self, d: dict[str, JSON]) -> None: + """ + Overwrite all attributes with the values from `d`. + + Equivalent to the following pseudo-code, but performed atomically. + + .. code-block:: python + + >>> attrs = {"a": 1, "b": 2} + >>> attrs.clear() + >>> attrs.update({"a": 3", "c": 4}) + >>> attrs + {'a': 3, 'c': 4} + """ + self._obj = self._obj.update_attributes(d) diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index a45c7e1df..7c56707a4 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -12,7 +12,7 @@ import zarr.api.asynchronous as async_api from zarr.abc.metadata import Metadata -from zarr.abc.store import set_or_delete +from zarr.abc.store import Store, set_or_delete from zarr.core.array import Array, AsyncArray from zarr.core.attributes import Attributes from zarr.core.buffer import default_buffer_prototype @@ -126,7 +126,7 @@ class AsyncGroup: store_path: StorePath @classmethod - async def create( + async def from_store( cls, store: StoreLike, *, @@ -312,6 +312,21 @@ def attrs(self) -> dict[str, Any]: def info(self) -> None: raise NotImplementedError + @property + def store(self) -> Store: + return self.store_path.store + + @property + def read_only(self) -> bool: + # Backwards compatibility for 2.x + return self.store_path.store.mode.readonly + + @property + def synchronizer(self) -> None: + # Backwards compatibility for 2.x + # Not implemented in 3.x yet. + return None + async def create_group( self, name: str, @@ -320,7 +335,7 @@ async def create_group( attributes: dict[str, Any] | None = None, ) -> AsyncGroup: attributes = attributes or {} - return await type(self).create( + return await type(self).from_store( self.store_path / name, attributes=attributes, exists_ok=exists_ok, @@ -752,7 +767,7 @@ class Group(SyncMixin): _async_group: AsyncGroup @classmethod - def create( + def from_store( cls, store: StoreLike, *, @@ -762,7 +777,7 @@ def create( ) -> Group: attributes = attributes or {} obj = sync( - AsyncGroup.create( + AsyncGroup.from_store( store, attributes=attributes, exists_ok=exists_ok, @@ -843,6 +858,22 @@ def attrs(self) -> Attributes: def info(self) -> None: raise NotImplementedError + @property + def store(self) -> Store: + # Backwards compatibility for 2.x + return self._async_group.store + + @property + def read_only(self) -> bool: + # Backwards compatibility for 2.x + return self._async_group.read_only + + @property + def synchronizer(self) -> None: + # Backwards compatibility for 2.x + # Not implemented in 3.x yet. + return self._async_group.synchronizer + def update_attributes(self, new_attributes: dict[str, Any]) -> Group: self._sync(self._async_group.update_attributes(new_attributes)) return self @@ -913,6 +944,10 @@ def require_groups(self, *names: str) -> tuple[Group, ...]: """Convenience method to require multiple groups in a single call.""" return tuple(map(Group, self._sync(self._async_group.require_groups(*names)))) + def create(self, *args: Any, **kwargs: Any) -> Array: + # Backwards compatibility for 2.x + return self.create_array(*args, **kwargs) + def create_array( self, name: str, diff --git a/src/zarr/testing/strategies.py b/src/zarr/testing/strategies.py index 83de3d92c..2f4ca8a69 100644 --- a/src/zarr/testing/strategies.py +++ b/src/zarr/testing/strategies.py @@ -99,7 +99,7 @@ def arrays( expected_attrs = {} if attributes is None else attributes array_path = path + ("/" if not path.endswith("/") else "") + name - root = Group.create(store) + root = Group.from_store(store) fill_value_args: tuple[Any, ...] = tuple() if nparray.dtype.kind == "M": m = re.search(r"\[(.+)\]", nparray.dtype.str) diff --git a/tests/v3/conftest.py b/tests/v3/conftest.py index 41cd35934..d1ac41075 100644 --- a/tests/v3/conftest.py +++ b/tests/v3/conftest.py @@ -89,7 +89,7 @@ async def async_group(request: pytest.FixtureRequest, tmpdir: LEGACY_PATH) -> As param: AsyncGroupRequest = request.param store = await parse_store(param.store, str(tmpdir)) - agroup = await AsyncGroup.create( + agroup = await AsyncGroup.from_store( store, attributes=param.attributes, zarr_format=param.zarr_format, diff --git a/tests/v3/test_api.py b/tests/v3/test_api.py index ddfab587c..728638c87 100644 --- a/tests/v3/test_api.py +++ b/tests/v3/test_api.py @@ -1,4 +1,5 @@ import pathlib +import warnings import numpy as np import pytest @@ -7,7 +8,7 @@ import zarr from zarr import Array, Group from zarr.abc.store import Store -from zarr.api.synchronous import create, load, open, open_group, save, save_array, save_group +from zarr.api.synchronous import create, group, load, open, open_group, save, save_array, save_group from zarr.core.common import ZarrFormat from zarr.store.memory import MemoryStore @@ -108,7 +109,7 @@ def test_save_errors() -> None: save_group("data/group.zarr") with pytest.raises(TypeError): # no array provided - save_array("data/group.zarr") # type: ignore[call-arg] + save_array("data/group.zarr") with pytest.raises(ValueError): # no arrays provided save("data/group.zarr") @@ -878,3 +879,37 @@ def test_tree() -> None: # # bad option # with pytest.raises(TypeError): # copy(source["foo"], dest, dry_run=True, log=True) + + +def test_open_positional_args_deprecated() -> None: + store = MemoryStore({}, mode="w") + with pytest.warns(FutureWarning, match="pass"): + open(store, "w", shape=(1,)) + + +def test_save_array_positional_args_deprecated() -> None: + store = MemoryStore({}, mode="w") + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", message="zarr_version is deprecated", category=DeprecationWarning + ) + with pytest.warns(FutureWarning, match="pass"): + save_array( + store, + np.ones( + 1, + ), + 3, + ) + + +def test_group_positional_args_deprecated() -> None: + store = MemoryStore({}, mode="w") + with pytest.warns(FutureWarning, match="pass"): + group(store, True) + + +def test_open_group_positional_args_deprecated() -> None: + store = MemoryStore({}, mode="w") + with pytest.warns(FutureWarning, match="pass"): + open_group(store, "w") diff --git a/tests/v3/test_array.py b/tests/v3/test_array.py index b7beb63b1..fe5c782a1 100644 --- a/tests/v3/test_array.py +++ b/tests/v3/test_array.py @@ -5,6 +5,7 @@ import pytest from zarr import Array, AsyncArray, Group +from zarr.core.buffer.cpu import NDBuffer from zarr.core.common import ZarrFormat from zarr.errors import ContainsArrayError, ContainsGroupError from zarr.store import LocalStore, MemoryStore @@ -25,7 +26,7 @@ def test_array_creation_existing_node( Check that an existing array or group is handled as expected during array creation. """ spath = StorePath(store) - group = Group.create(spath, zarr_format=zarr_format) + group = Group.from_store(spath, zarr_format=zarr_format) expected_exception: type[ContainsArrayError] | type[ContainsGroupError] if extant_node == "array": expected_exception = ContainsArrayError @@ -76,7 +77,7 @@ def test_array_name_properties_no_group( def test_array_name_properties_with_group( store: LocalStore | MemoryStore, zarr_format: ZarrFormat ) -> None: - root = Group.create(store=store, zarr_format=zarr_format) + root = Group.from_store(store=store, zarr_format=zarr_format) foo = root.create_array("foo", shape=(100,), chunks=(10,), dtype="i4") assert foo.path == "foo" assert foo.name == "/foo" @@ -138,6 +139,47 @@ def test_array_v3_fill_value(store: MemoryStore, fill_value: int, dtype_str: str assert arr.fill_value.dtype == arr.dtype +def test_create_positional_args_deprecated() -> None: + store = MemoryStore({}, mode="w") + with pytest.warns(FutureWarning, match="Pass"): + Array.create(store, (2, 2), dtype="f8") + + +def test_selection_positional_args_deprecated() -> None: + store = MemoryStore({}, mode="w") + arr = Array.create(store, shape=(2, 2), dtype="f8") + + with pytest.warns(FutureWarning, match="Pass out"): + arr.get_basic_selection(..., NDBuffer(array=np.empty((2, 2)))) + + with pytest.warns(FutureWarning, match="Pass fields"): + arr.set_basic_selection(..., 1, None) + + with pytest.warns(FutureWarning, match="Pass out"): + arr.get_orthogonal_selection(..., NDBuffer(array=np.empty((2, 2)))) + + with pytest.warns(FutureWarning, match="Pass"): + arr.set_orthogonal_selection(..., 1, None) + + with pytest.warns(FutureWarning, match="Pass"): + arr.get_mask_selection(np.zeros((2, 2), dtype=bool), NDBuffer(array=np.empty((0,)))) + + with pytest.warns(FutureWarning, match="Pass"): + arr.set_mask_selection(np.zeros((2, 2), dtype=bool), 1, None) + + with pytest.warns(FutureWarning, match="Pass"): + arr.get_coordinate_selection(([0, 1], [0, 1]), NDBuffer(array=np.empty((2,)))) + + with pytest.warns(FutureWarning, match="Pass"): + arr.set_coordinate_selection(([0, 1], [0, 1]), 1, None) + + with pytest.warns(FutureWarning, match="Pass"): + arr.get_block_selection((0, slice(None)), NDBuffer(array=np.empty((2, 2)))) + + with pytest.warns(FutureWarning, match="Pass"): + arr.set_block_selection((0, slice(None)), 1, None) + + @pytest.mark.parametrize("store", ["memory"], indirect=True) async def test_array_v3_nan_fill_value(store: MemoryStore) -> None: shape = (10,) diff --git a/tests/v3/test_attributes.py b/tests/v3/test_attributes.py new file mode 100644 index 000000000..65b6a02e8 --- /dev/null +++ b/tests/v3/test_attributes.py @@ -0,0 +1,13 @@ +import zarr.core +import zarr.core.attributes +import zarr.store + + +def test_put() -> None: + store = zarr.store.MemoryStore({}, mode="w") + attrs = zarr.core.attributes.Attributes( + zarr.Group.from_store(store, attributes={"a": 1, "b": 2}) + ) + attrs.put({"a": 3, "c": 4}) + expected = {"a": 3, "c": 4} + assert dict(attrs) == expected diff --git a/tests/v3/test_buffer.py b/tests/v3/test_buffer.py index 5a313dc1a..cde3f8578 100644 --- a/tests/v3/test_buffer.py +++ b/tests/v3/test_buffer.py @@ -32,6 +32,10 @@ cp = None +if TYPE_CHECKING: + import types + + def test_nd_array_like(xp: types.ModuleType) -> None: ary = xp.arange(10) assert isinstance(ary, ArrayLike) diff --git a/tests/v3/test_group.py b/tests/v3/test_group.py index 6e75294b7..c8310f33e 100644 --- a/tests/v3/test_group.py +++ b/tests/v3/test_group.py @@ -51,7 +51,7 @@ def test_group_init(store: Store, zarr_format: ZarrFormat) -> None: """ Test that initializing a group from an asyncgroup works. """ - agroup = sync(AsyncGroup.create(store=store, zarr_format=zarr_format)) + agroup = sync(AsyncGroup.from_store(store=store, zarr_format=zarr_format)) group = Group(agroup) assert group._async_group == agroup @@ -60,7 +60,7 @@ def test_group_name_properties(store: Store, zarr_format: ZarrFormat) -> None: """ Test basic properties of groups """ - root = Group.create(store=store, zarr_format=zarr_format) + root = Group.from_store(store=store, zarr_format=zarr_format) assert root.path == "" assert root.name == "/" assert root.basename == "" @@ -178,16 +178,18 @@ def test_group(store: Store, zarr_format: ZarrFormat) -> None: def test_group_create(store: Store, exists_ok: bool, zarr_format: ZarrFormat) -> None: """ - Test that `Group.create` works as expected. + Test that `Group.from_store` works as expected. """ attributes = {"foo": 100} - group = Group.create(store, attributes=attributes, zarr_format=zarr_format, exists_ok=exists_ok) + group = Group.from_store( + store, attributes=attributes, zarr_format=zarr_format, exists_ok=exists_ok + ) assert group.attrs == attributes if not exists_ok: with pytest.raises(ContainsGroupError): - group = Group.create( + group = Group.from_store( store, attributes=attributes, exists_ok=exists_ok, zarr_format=zarr_format ) @@ -203,7 +205,7 @@ def test_group_open(store: Store, zarr_format: ZarrFormat, exists_ok: bool) -> N # create the group attrs = {"path": "foo"} - group_created = Group.create( + group_created = Group.from_store( store, attributes=attrs, zarr_format=zarr_format, exists_ok=exists_ok ) assert group_created.attrs == attrs @@ -214,9 +216,9 @@ def test_group_open(store: Store, zarr_format: ZarrFormat, exists_ok: bool) -> N new_attrs = {"path": "bar"} if not exists_ok: with pytest.raises(ContainsGroupError): - Group.create(store, attributes=attrs, zarr_format=zarr_format, exists_ok=exists_ok) + Group.from_store(store, attributes=attrs, zarr_format=zarr_format, exists_ok=exists_ok) else: - group_created_again = Group.create( + group_created_again = Group.from_store( store, attributes=new_attrs, zarr_format=zarr_format, exists_ok=exists_ok ) assert group_created_again.attrs == new_attrs @@ -229,7 +231,7 @@ def test_group_getitem(store: Store, zarr_format: ZarrFormat) -> None: Test the `Group.__getitem__` method. """ - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) subgroup = group.create_group(name="subgroup") subarray = group.create_array(name="subarray", shape=(10,), chunk_shape=(10,)) @@ -246,7 +248,7 @@ def test_group_delitem(store: Store, zarr_format: ZarrFormat) -> None: if not store.supports_deletes: pytest.skip("store does not support deletes") - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) subgroup = group.create_group(name="subgroup") subarray = group.create_array(name="subarray", shape=(10,), chunk_shape=(10,)) @@ -267,7 +269,7 @@ def test_group_iter(store: Store, zarr_format: ZarrFormat) -> None: Test the `Group.__iter__` method. """ - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) with pytest.raises(NotImplementedError): [x for x in group] # type: ignore @@ -277,7 +279,7 @@ def test_group_len(store: Store, zarr_format: ZarrFormat) -> None: Test the `Group.__len__` method. """ - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) with pytest.raises(NotImplementedError): len(group) # type: ignore @@ -286,7 +288,7 @@ def test_group_setitem(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__setitem__` method. """ - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) with pytest.raises(NotImplementedError): group["key"] = 10 @@ -295,14 +297,14 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None: """ Test the `Group.__contains__` method """ - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) assert "foo" not in group _ = group.create_group(name="foo") assert "foo" in group def test_group_child_iterators(store: Store, zarr_format: ZarrFormat): - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) expected_group_keys = ["g0", "g1"] expected_group_values = [group.create_group(name=name) for name in expected_group_keys] expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False)) @@ -330,7 +332,7 @@ def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: Test the behavior of `Group.update_attributes` """ attrs = {"foo": 100} - group = Group.create(store, zarr_format=zarr_format, attributes=attrs) + group = Group.from_store(store, zarr_format=zarr_format, attributes=attrs) assert group.attrs == attrs new_attrs = {"bar": 100} new_group = group.update_attributes(new_attrs) @@ -342,7 +344,7 @@ async def test_group_update_attributes_async(store: Store, zarr_format: ZarrForm Test the behavior of `Group.update_attributes_async` """ attrs = {"foo": 100} - group = Group.create(store, zarr_format=zarr_format, attributes=attrs) + group = Group.from_store(store, zarr_format=zarr_format, attributes=attrs) assert group.attrs == attrs new_attrs = {"bar": 100} new_group = await group.update_attributes_async(new_attrs) @@ -357,9 +359,9 @@ def test_group_create_array( method: Literal["create_array", "array"], ) -> None: """ - Test `Group.create_array` + Test `Group.from_store` """ - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) shape = (10, 10) dtype = "uint8" data = np.arange(np.prod(shape)).reshape(shape).astype(dtype) @@ -388,7 +390,7 @@ def test_group_array_creation( store: Store, zarr_format: ZarrFormat, ): - group = Group.create(store, zarr_format=zarr_format) + group = Group.from_store(store, zarr_format=zarr_format) shape = (10, 10) empty_array = group.empty(name="empty", shape=shape) assert isinstance(empty_array, Array) @@ -465,7 +467,7 @@ def test_group_creation_existing_node( Check that an existing array or group is handled as expected during group creation. """ spath = StorePath(store) - group = Group.create(spath, zarr_format=zarr_format) + group = Group.from_store(spath, zarr_format=zarr_format) expected_exception: type[ContainsArrayError] | type[ContainsGroupError] attributes: dict[str, JSON] = {"old": True} @@ -481,7 +483,7 @@ def test_group_creation_existing_node( new_attributes = {"new": True} if exists_ok: - node_new = Group.create( + node_new = Group.from_store( spath / "extant", attributes=new_attributes, zarr_format=zarr_format, @@ -490,7 +492,7 @@ def test_group_creation_existing_node( assert node_new.attrs == new_attributes else: with pytest.raises(expected_exception): - node_new = Group.create( + node_new = Group.from_store( spath / "extant", attributes=new_attributes, zarr_format=zarr_format, @@ -504,11 +506,11 @@ async def test_asyncgroup_create( zarr_format: ZarrFormat, ) -> None: """ - Test that `AsyncGroup.create` works as expected. + Test that `AsyncGroup.from_store` works as expected. """ spath = StorePath(store=store) attributes = {"foo": 100} - agroup = await AsyncGroup.create( + agroup = await AsyncGroup.from_store( store, attributes=attributes, exists_ok=exists_ok, @@ -520,7 +522,7 @@ async def test_asyncgroup_create( if not exists_ok: with pytest.raises(ContainsGroupError): - agroup = await AsyncGroup.create( + agroup = await AsyncGroup.from_store( spath, attributes=attributes, exists_ok=exists_ok, @@ -532,7 +534,7 @@ async def test_asyncgroup_create( spath / collision_name, shape=(10,), dtype="uint8", zarr_format=zarr_format ) with pytest.raises(ContainsArrayError): - _ = await AsyncGroup.create( + _ = await AsyncGroup.from_store( StorePath(store=store) / collision_name, attributes=attributes, exists_ok=exists_ok, @@ -542,13 +544,13 @@ async def test_asyncgroup_create( async def test_asyncgroup_attrs(store: Store, zarr_format: ZarrFormat) -> None: attributes = {"foo": 100} - agroup = await AsyncGroup.create(store, zarr_format=zarr_format, attributes=attributes) + agroup = await AsyncGroup.from_store(store, zarr_format=zarr_format, attributes=attributes) assert agroup.attrs == agroup.metadata.attributes == attributes async def test_asyncgroup_info(store: Store, zarr_format: ZarrFormat) -> None: - agroup = await AsyncGroup.create( # noqa + agroup = await AsyncGroup.from_store( # noqa store, zarr_format=zarr_format, ) @@ -564,7 +566,7 @@ async def test_asyncgroup_open( Create an `AsyncGroup`, then ensure that we can open it using `AsyncGroup.open` """ attributes = {"foo": 100} - group_w = await AsyncGroup.create( + group_w = await AsyncGroup.from_store( store=store, attributes=attributes, exists_ok=False, @@ -581,7 +583,7 @@ async def test_asyncgroup_open_wrong_format( store: Store, zarr_format: ZarrFormat, ) -> None: - _ = await AsyncGroup.create(store=store, exists_ok=False, zarr_format=zarr_format) + _ = await AsyncGroup.from_store(store=store, exists_ok=False, zarr_format=zarr_format) zarr_format_wrong: ZarrFormat # try opening with the wrong zarr format if zarr_format == 3: @@ -624,7 +626,7 @@ async def test_asyncgroup_getitem(store: Store, zarr_format: ZarrFormat) -> None Create an `AsyncGroup`, then create members of that group, and ensure that we can access those members via the `AsyncGroup.getitem` method. """ - agroup = await AsyncGroup.create(store=store, zarr_format=zarr_format) + agroup = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) array_name = "sub_array" sub_array = await agroup.create_array( @@ -645,7 +647,7 @@ async def test_asyncgroup_delitem(store: Store, zarr_format: ZarrFormat) -> None if not store.supports_deletes: pytest.skip("store does not support deletes") - agroup = await AsyncGroup.create(store=store, zarr_format=zarr_format) + agroup = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) array_name = "sub_array" _ = await agroup.create_array( name=array_name, shape=(10,), dtype="uint8", chunk_shape=(2,), attributes={"foo": 100} @@ -677,7 +679,7 @@ async def test_asyncgroup_create_group( store: Store, zarr_format: ZarrFormat, ) -> None: - agroup = await AsyncGroup.create(store=store, zarr_format=zarr_format) + agroup = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) sub_node_path = "sub_group" attributes = {"foo": 999} subnode = await agroup.create_group(name=sub_node_path, attributes=attributes) @@ -697,11 +699,11 @@ async def test_asyncgroup_create_array( specified in create_array are present on the resulting array. """ - agroup = await AsyncGroup.create(store=store, zarr_format=zarr_format) + agroup = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) if not exists_ok: with pytest.raises(ContainsGroupError): - agroup = await AsyncGroup.create(store=store, zarr_format=zarr_format) + agroup = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) shape = (10,) dtype = "uint8" @@ -734,7 +736,7 @@ async def test_asyncgroup_update_attributes(store: Store, zarr_format: ZarrForma """ attributes_old = {"foo": 10} attributes_new = {"baz": "new"} - agroup = await AsyncGroup.create( + agroup = await AsyncGroup.from_store( store=store, zarr_format=zarr_format, attributes=attributes_old ) @@ -745,7 +747,7 @@ async def test_asyncgroup_update_attributes(store: Store, zarr_format: ZarrForma @pytest.mark.parametrize("store", ("local",), indirect=["store"]) @pytest.mark.parametrize("zarr_format", (2, 3)) async def test_serializable_async_group(store: LocalStore, zarr_format: ZarrFormat) -> None: - expected = await AsyncGroup.create( + expected = await AsyncGroup.from_store( store=store, attributes={"foo": 999}, zarr_format=zarr_format ) p = pickle.dumps(expected) @@ -756,7 +758,7 @@ async def test_serializable_async_group(store: LocalStore, zarr_format: ZarrForm @pytest.mark.parametrize("store", ("local",), indirect=["store"]) @pytest.mark.parametrize("zarr_format", (2, 3)) def test_serializable_sync_group(store: LocalStore, zarr_format: ZarrFormat) -> None: - expected = Group.create(store=store, attributes={"foo": 999}, zarr_format=zarr_format) + expected = Group.from_store(store=store, attributes={"foo": 999}, zarr_format=zarr_format) p = pickle.dumps(expected) actual = pickle.loads(p) @@ -817,7 +819,7 @@ async def test_group_members_async(store: LocalStore | MemoryStore) -> None: async def test_require_group(store: LocalStore | MemoryStore, zarr_format: ZarrFormat) -> None: - root = await AsyncGroup.create(store=store, zarr_format=zarr_format) + root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) # create foo group _ = await root.create_group("foo", attributes={"foo": 100}) @@ -845,7 +847,7 @@ async def test_require_group(store: LocalStore | MemoryStore, zarr_format: ZarrF async def test_require_groups(store: LocalStore | MemoryStore, zarr_format: ZarrFormat) -> None: - root = await AsyncGroup.create(store=store, zarr_format=zarr_format) + root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) # create foo group _ = await root.create_group("foo", attributes={"foo": 100}) # create bar group @@ -866,7 +868,7 @@ async def test_require_groups(store: LocalStore | MemoryStore, zarr_format: Zarr async def test_create_dataset(store: LocalStore | MemoryStore, zarr_format: ZarrFormat) -> None: - root = await AsyncGroup.create(store=store, zarr_format=zarr_format) + root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) with pytest.warns(DeprecationWarning): foo = await root.create_dataset("foo", shape=(10,), dtype="uint8") assert foo.shape == (10,) @@ -880,7 +882,7 @@ async def test_create_dataset(store: LocalStore | MemoryStore, zarr_format: Zarr async def test_require_array(store: LocalStore | MemoryStore, zarr_format: ZarrFormat) -> None: - root = await AsyncGroup.create(store=store, zarr_format=zarr_format) + root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) foo1 = await root.require_array("foo", shape=(10,), dtype="i8", attributes={"foo": 101}) assert foo1.attrs == {"foo": 101} foo2 = await root.require_array("foo", shape=(10,), dtype="i8") diff --git a/tests/v3/test_sync.py b/tests/v3/test_sync.py index 22834747e..864c9e01c 100644 --- a/tests/v3/test_sync.py +++ b/tests/v3/test_sync.py @@ -4,7 +4,9 @@ import pytest +import zarr from zarr.core.sync import SyncError, SyncMixin, _get_lock, _get_loop, sync +from zarr.store.memory import MemoryStore @pytest.fixture(params=[True, False]) @@ -121,3 +123,9 @@ def bar(self) -> list[int]: foo = SyncFoo(async_foo) assert foo.foo() == "foo" assert foo.bar() == list(range(10)) + + +def test_open_positional_args_deprecate(): + store = MemoryStore({}, mode="w") + with pytest.warns(FutureWarning, match="pass"): + zarr.open(store, "w", shape=(1,))