From 1ba9ad52a9c9a860aff0f621d5e6d06805063b1c Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 5 Sep 2025 09:11:08 +0100 Subject: [PATCH 1/3] Deprecate group() --- src/zarr/api/asynchronous.py | 1 + src/zarr/api/synchronous.py | 1 + tests/test_api.py | 5 +++-- tests/test_api/test_asynchronous.py | 11 +++++++++-- tests/test_array.py | 2 +- tests/test_group.py | 12 ++++++------ tests/test_metadata/test_consolidated.py | 19 ++++++++++--------- tests/test_store/test_local.py | 2 +- tests/test_tree.py | 4 ++-- tests/test_v2.py | 2 +- 10 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 409601e474..0125c7015c 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -622,6 +622,7 @@ async def array( return z +@deprecated("Use open_group() or create_group() instead") async def group( *, # Note: this is a change from v2 store: StoreLike | None = None, diff --git a/src/zarr/api/synchronous.py b/src/zarr/api/synchronous.py index 1146a6876f..1567bdab2d 100644 --- a/src/zarr/api/synchronous.py +++ b/src/zarr/api/synchronous.py @@ -384,6 +384,7 @@ def array(data: npt.ArrayLike | Array, **kwargs: Any) -> Array: return Array(sync(async_api.array(data=data, **kwargs))) +@deprecated("Use open_group() or create_group() instead") def group( store: StoreLike | None = None, *, diff --git a/tests/test_api.py b/tests/test_api.py index ff969d406f..b0be9f4c53 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -159,7 +159,7 @@ def test_open_normalized_path( ) -> None: node: Group | Array if node_type == "group": - node = group(store=memory_store, path=path) + node = create_group(store=memory_store, path=path) elif node_type == "array": node = create(store=memory_store, path=path, shape=(2,)) @@ -514,7 +514,7 @@ def test_load_local(tmp_path: Path, path: str | None, load_read_only: bool) -> N def test_tree() -> None: pytest.importorskip("rich") - g1 = zarr.group() + g1 = zarr.create_group(store={}) g1.create_group("foo") g3 = g1.create_group("bar") g3.create_group("baz") @@ -1379,6 +1379,7 @@ def test_no_overwrite_array(tmp_path: Path, create_function: Callable, overwrite assert existing_fpath.exists() +@pytest.mark.filterwarnings(r"ignore:Use open_group\(\) or create_group\(\) instead") @pytest.mark.parametrize("create_function", [create_group, group]) @pytest.mark.parametrize("overwrite", [True, False]) def test_no_overwrite_group(tmp_path: Path, create_function: Callable, overwrite: bool) -> None: # type:ignore[type-arg] diff --git a/tests/test_api/test_asynchronous.py b/tests/test_api/test_asynchronous.py index 0f219fd727..9e2ca25ffd 100644 --- a/tests/test_api/test_asynchronous.py +++ b/tests/test_api/test_asynchronous.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import re from dataclasses import dataclass from typing import TYPE_CHECKING @@ -115,8 +116,14 @@ async def test_open_group_new_path(tmp_path: Path) -> None: """ # tmp_path exists, but tmp_path / "test.zarr" will not, which is important for this test path = tmp_path / "test.zarr" - grp = await group(store=path, attributes={"a": 1}) + with pytest.warns( + DeprecationWarning, match=re.escape("Use open_group() or create_group() instead") + ): + grp = await group(store=path, attributes={"a": 1}) assert isinstance(grp, AsyncGroup) # Calling group on an existing store should just open that store - grp = await group(store=path) + with pytest.warns( + DeprecationWarning, match=re.escape("Use open_group() or create_group() instead") + ): + grp = await group(store=path) assert grp.attrs == {"a": 1} diff --git a/tests/test_array.py b/tests/test_array.py index 97aef9319b..6a4e3a87a8 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1742,7 +1742,7 @@ def test_roundtrip_numcodecs() -> None: ] # Create the array with the correct codecs - root = zarr.group(store) + root = zarr.create_group(store) warn_msg = "Numcodecs codecs are not in the Zarr version 3 specification and may not be supported by other zarr implementations." with pytest.warns(UserWarning, match=warn_msg): root.create_array( diff --git a/tests/test_group.py b/tests/test_group.py index 2d9070bd67..bc073320ec 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -1584,7 +1584,7 @@ def test_from_dict_extra_fields(self): class TestInfo: def test_info(self): store = zarr.storage.MemoryStore() - A = zarr.group(store=store, path="A") + A = zarr.create_group(store=store, path="A") B = A.create_group(name="B") B.create_array(name="x", shape=(1,), dtype="uint8") @@ -1624,7 +1624,7 @@ def test_update_attrs() -> None: @pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) def test_delitem_removes_children(store: Store, zarr_format: ZarrFormat) -> None: # https://github.com/zarr-developers/zarr-python/issues/2191 - g1 = zarr.group(store=store, zarr_format=zarr_format) + g1 = zarr.create_group(store=store, zarr_format=zarr_format) g1.create_group("0") g1.create_group("0/0") arr = g1.create_array("0/0/0", shape=(1,), dtype="uint8") @@ -2150,7 +2150,7 @@ def test_group_members_performance(store: Store) -> None: get_latency = 0.1 # use the input store to create some groups - group_create = zarr.group(store=store) + group_create = zarr.create_group(store=store) num_groups = 10 # Create some groups @@ -2159,7 +2159,7 @@ def test_group_members_performance(store: Store) -> None: latency_store = LatencyStore(store, get_latency=get_latency) # create a group with some latency on get operations - group_read = zarr.group(store=latency_store) + group_read = zarr.open_group(store=latency_store) # check how long it takes to iterate over the groups # if .members is sensitive to IO latency, @@ -2181,7 +2181,7 @@ def test_group_members_concurrency_limit(store: MemoryStore) -> None: get_latency = 0.02 # use the input store to create some groups - group_create = zarr.group(store=store) + group_create = zarr.create_group(store=store) num_groups = 10 # Create some groups @@ -2190,7 +2190,7 @@ def test_group_members_concurrency_limit(store: MemoryStore) -> None: latency_store = LatencyStore(store, get_latency=get_latency) # create a group with some latency on get operations - group_read = zarr.group(store=latency_store) + group_read = zarr.open_group(store=latency_store) # check how long it takes to iterate over the groups # if .members is sensitive to IO latency, diff --git a/tests/test_metadata/test_consolidated.py b/tests/test_metadata/test_consolidated.py index 9e8b763ef7..7eaa5f7f27 100644 --- a/tests/test_metadata/test_consolidated.py +++ b/tests/test_metadata/test_consolidated.py @@ -13,9 +13,10 @@ from zarr import AsyncGroup from zarr.api.asynchronous import ( consolidate_metadata, - group, + create_group, open, open_consolidated, + open_group, ) from zarr.core.buffer import cpu, default_buffer_prototype from zarr.core.dtype import parse_dtype @@ -32,7 +33,7 @@ @pytest.fixture async def memory_store_with_hierarchy(memory_store: Store) -> Store: - g = await group(store=memory_store, attributes={"foo": "bar"}) + g = await create_group(store=memory_store, attributes={"foo": "bar"}) dtype = "uint8" await g.create_array(name="air", shape=(1, 2, 3), dtype=dtype) await g.create_array(name="lat", shape=(1,), dtype=dtype) @@ -212,7 +213,7 @@ async def test_consolidated(self, memory_store_with_hierarchy: Store) -> None: ] def test_consolidated_sync(self, memory_store: Store) -> None: - g = zarr.api.synchronous.group(store=memory_store, attributes={"foo": "bar"}) + g = zarr.api.synchronous.create_group(store=memory_store, attributes={"foo": "bar"}) dtype = "uint8" g.create_array(name="air", shape=(1, 2, 3), dtype=dtype) g.create_array(name="lat", shape=(1,), dtype=dtype) @@ -300,7 +301,7 @@ def test_consolidated_sync(self, memory_store: Store) -> None: assert group4.metadata == expected async def test_not_writable_raises(self, memory_store: zarr.storage.MemoryStore) -> None: - await group(store=memory_store, attributes={"foo": "bar"}) + await create_group(store=memory_store, attributes={"foo": "bar"}) read_store = zarr.storage.MemoryStore(store_dict=memory_store._store_dict, read_only=True) with pytest.raises(ValueError, match="does not support writing"): await consolidate_metadata(read_store) @@ -485,7 +486,7 @@ async def test_to_dict_order( self, memory_store: zarr.storage.MemoryStore, zarr_format: ZarrFormat ) -> None: with zarr.config.set(default_zarr_format=zarr_format): - g = await group(store=memory_store) + g = await create_group(store=memory_store) # Create groups in non-lexicographix order dtype = "float32" @@ -602,7 +603,7 @@ async def test_use_consolidated_false( self, memory_store: zarr.storage.MemoryStore, zarr_format: ZarrFormat ) -> None: with zarr.config.set(default_zarr_format=zarr_format): - g = await group(store=memory_store, attributes={"foo": "bar"}) + g = await create_group(store=memory_store, attributes={"foo": "bar"}) await g.create_group(name="a") # test a stale read @@ -648,7 +649,7 @@ async def test_stale_child_metadata_ignored( # https://github.com/zarr-developers/zarr-python/issues/2921 # When consolidating metadata, we should ignore any (possibly stale) metadata # from previous consolidations, *including at child nodes*. - root = await zarr.api.asynchronous.group(store=memory_store, zarr_format=3) + root = await zarr.api.asynchronous.create_group(store=memory_store, zarr_format=3) await root.create_group("foo") await zarr.api.asynchronous.consolidate_metadata(memory_store, path="foo") await root.create_group("foo/bar/spam") @@ -712,7 +713,7 @@ async def test_absolute_path_for_subgroup(self, memory_store: zarr.storage.Memor async def test_consolidated_metadata_encodes_special_chars( memory_store: Store, zarr_format: ZarrFormat, fill_value: float ) -> None: - root = await group(store=memory_store, zarr_format=zarr_format) + root = await create_group(store=memory_store, zarr_format=zarr_format) _time = await root.create_array("time", shape=(12,), dtype=np.float64, fill_value=fill_value) if zarr_format == 3: with pytest.warns( @@ -723,7 +724,7 @@ async def test_consolidated_metadata_encodes_special_chars( else: await zarr.api.asynchronous.consolidate_metadata(memory_store) - root = await group(store=memory_store, zarr_format=zarr_format) + root = await open_group(store=memory_store, zarr_format=zarr_format) root_buffer = root.metadata.to_buffer_dict(default_buffer_prototype()) if zarr_format == 2: diff --git a/tests/test_store/test_local.py b/tests/test_store/test_local.py index 6756bc83d9..8be950a170 100644 --- a/tests/test_store/test_local.py +++ b/tests/test_store/test_local.py @@ -51,7 +51,7 @@ def test_creates_new_directory(self, tmp_path: pathlib.Path) -> None: assert not target.exists() store = self.store_cls(root=target) - zarr.group(store=store) + zarr.create_group(store=store) def test_invalid_root_raises(self) -> None: """ diff --git a/tests/test_tree.py b/tests/test_tree.py index b4a5106998..1e6eca5e8e 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -13,7 +13,7 @@ def test_tree(root_name: Any) -> None: os.environ["OVERRIDE_COLOR_SYSTEM"] = "truecolor" - g = zarr.group(path=root_name) + g = zarr.create_group(store={}, path=root_name) A = g.create_group("A") B = g.create_group("B") C = B.create_group("C") @@ -57,6 +57,6 @@ def test_tree(root_name: Any) -> None: def test_expand_not_implemented() -> None: - g = zarr.group() + g = zarr.create_group(store={}) with pytest.raises(NotImplementedError): g.tree(expand=True) diff --git a/tests/test_v2.py b/tests/test_v2.py index 70e8f2923f..6fe80e5890 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -75,7 +75,7 @@ async def test_v2_encode_decode( dtype: str, expected_dtype: str, fill_value: bytes, fill_value_json: str ) -> None: store = zarr.storage.MemoryStore() - g = zarr.group(store=store, zarr_format=2) + g = zarr.create_group(store=store, zarr_format=2) g.create_array( name="foo", shape=(3,), chunks=(3,), dtype=dtype, fill_value=fill_value, compressor=None ) From 52f2c8901bc5daae375f4ec29ea5ae8134408b64 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 5 Sep 2025 09:59:13 +0100 Subject: [PATCH 2/3] Replace more instances of group() --- docs/developers/contributing.rst | 2 +- docs/quickstart.rst | 4 ++-- docs/user-guide/groups.rst | 2 +- src/zarr/core/group.py | 26 +++++++++++++------------- src/zarr/testing/stateful.py | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/developers/contributing.rst b/docs/developers/contributing.rst index 50bf52730b..6462a8f64f 100644 --- a/docs/developers/contributing.rst +++ b/docs/developers/contributing.rst @@ -31,7 +31,7 @@ a bug report: ```python import zarr - g = zarr.group() + g = zarr.create_group(store={}) # etc. ``` diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 66bdae2a2e..d1914b498c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -98,7 +98,7 @@ Hierarchical Groups Zarr allows you to create hierarchical groups, similar to directories:: >>> # Create nested groups and add arrays - >>> root = zarr.group("data/example-2.zarr") + >>> root = zarr.create_group("data/example-2.zarr") >>> foo = root.create_group(name="foo") >>> bar = root.create_array( ... name="bar", shape=(100, 10), chunks=(10, 10), dtype="f4" @@ -126,7 +126,7 @@ Zarr provides tools for creating a collection of arrays and groups with a single Suppose we want to copy existing groups and arrays into a new storage backend: >>> # Create nested groups and add arrays - >>> root = zarr.group("data/example-3.zarr", attributes={'name': 'root'}) + >>> root = zarr.create_group("data/example-3.zarr", attributes={'name': 'root'}) >>> foo = root.create_group(name="foo") >>> bar = root.create_array( ... name="bar", shape=(100, 10), chunks=(10, 10), dtype="f4" diff --git a/docs/user-guide/groups.rst b/docs/user-guide/groups.rst index a343c3617e..bff851eaba 100644 --- a/docs/user-guide/groups.rst +++ b/docs/user-guide/groups.rst @@ -107,7 +107,7 @@ Diagnostic information about arrays and groups is available via the ``info`` property. E.g.:: >>> store = zarr.storage.MemoryStore() - >>> root = zarr.group(store=store) + >>> root = zarr.create_group(store=store) >>> foo = root.create_group('foo') >>> bar = foo.create_array(name='bar', shape=1000000, chunks=100000, dtype='int64') >>> bar[:] = 42 diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index 15a256fb5d..b48327a459 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -1969,7 +1969,7 @@ def __iter__(self) -> Iterator[str]: Examples -------- >>> import zarr - >>> g1 = zarr.group() + >>> g1 = zarr.create_group(store={}) >>> g2 = g1.create_group('foo') >>> g3 = g1.create_group('bar') >>> d1 = g1.create_array('baz', shape=(10,), chunks=(10,)) @@ -2003,7 +2003,7 @@ def __setitem__(self, key: str, value: Any) -> None: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group["foo"] = zarr.zeros((10,)) >>> group["foo"] @@ -2019,7 +2019,7 @@ async def update_attributes_async(self, new_attributes: dict[str, Any]) -> Group Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> await group.update_attributes_async({"foo": "bar"}) >>> group.attrs.asdict() {'foo': 'bar'} @@ -2120,7 +2120,7 @@ def update_attributes(self, new_attributes: dict[str, Any]) -> Group: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group.update_attributes({"foo": "bar"}) >>> group.attrs.asdict() {'foo': 'bar'} @@ -2245,7 +2245,7 @@ def keys(self) -> Generator[str, None]: Examples -------- >>> import zarr - >>> g1 = zarr.group() + >>> g1 = zarr.create_group(store={}) >>> g2 = g1.create_group('foo') >>> g3 = g1.create_group('bar') >>> d1 = g1.create_array('baz', shape=(10,), chunks=(10,)) @@ -2265,7 +2265,7 @@ def __contains__(self, member: str) -> bool: Examples -------- >>> import zarr - >>> g1 = zarr.group() + >>> g1 = zarr.create_group(store={}) >>> g2 = g1.create_group('foo') >>> d1 = g1.create_array('bar', shape=(10,), chunks=(10,)) >>> 'foo' in g1 @@ -2284,7 +2284,7 @@ def groups(self) -> Generator[tuple[str, Group], None]: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group.create_group("subgroup") >>> for name, subgroup in group.groups(): ... print(name, subgroup) @@ -2299,7 +2299,7 @@ def group_keys(self) -> Generator[str, None]: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group.create_group("subgroup") >>> for name in group.group_keys(): ... print(name) @@ -2314,7 +2314,7 @@ def group_values(self) -> Generator[Group, None]: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group.create_group("subgroup") >>> for subgroup in group.group_values(): ... print(subgroup) @@ -2329,7 +2329,7 @@ def arrays(self) -> Generator[tuple[str, Array], None]: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group.create_array("subarray", shape=(10,), chunks=(10,)) >>> for name, subarray in group.arrays(): ... print(name, subarray) @@ -2344,7 +2344,7 @@ def array_keys(self) -> Generator[str, None]: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group.create_array("subarray", shape=(10,), chunks=(10,)) >>> for name in group.array_keys(): ... print(name) @@ -2360,7 +2360,7 @@ def array_values(self) -> Generator[Array, None]: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> group.create_array("subarray", shape=(10,), chunks=(10,)) >>> for subarray in group.array_values(): ... print(subarray) @@ -2405,7 +2405,7 @@ def create_group(self, name: str, **kwargs: Any) -> Group: Examples -------- >>> import zarr - >>> group = zarr.group() + >>> group = zarr.create_group(store={}) >>> subgroup = group.create_group("subgroup") >>> subgroup diff --git a/src/zarr/testing/stateful.py b/src/zarr/testing/stateful.py index c363c13983..d564af323a 100644 --- a/src/zarr/testing/stateful.py +++ b/src/zarr/testing/stateful.py @@ -88,7 +88,7 @@ def __init__(self, store: Store) -> None: self.store = store self.model = MemoryStore() - zarr.group(store=self.model) + zarr.create_group(store=self.model) # Track state of the hierarchy, these should contain fully qualified paths self.all_groups: set[str] = set() From e7370afe6095299ef49962f54d5aa5825f595797 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 5 Sep 2025 10:01:55 +0100 Subject: [PATCH 3/3] Add release note --- changes/3435.removal.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/3435.removal.rst diff --git a/changes/3435.removal.rst b/changes/3435.removal.rst new file mode 100644 index 0000000000..47ad2c6f01 --- /dev/null +++ b/changes/3435.removal.rst @@ -0,0 +1,2 @@ +`zarr.group`, `zarr.api.synchronous.group`, and `zarr.api.asynchronous.group` are all deprecated and will be removed in the future. +Use the relevant ``open_group()`` or ``create_group()`` functions instead.