From 8017d6b357a72a3b9ac553daa88f5ab013768f50 Mon Sep 17 00:00:00 2001 From: Kenneth Li Date: Wed, 30 Jul 2025 12:28:37 -0400 Subject: [PATCH 1/4] obstore delete_dir --- src/zarr/storage/_obstore.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/zarr/storage/_obstore.py b/src/zarr/storage/_obstore.py index e1469a991e..39699f6662 100644 --- a/src/zarr/storage/_obstore.py +++ b/src/zarr/storage/_obstore.py @@ -13,6 +13,7 @@ Store, SuffixByteRequest, ) +from zarr.core.common import concurrent_map from zarr.core.config import config if TYPE_CHECKING: @@ -196,6 +197,15 @@ async def delete(self, key: str) -> None: with contextlib.suppress(FileNotFoundError): await obs.delete_async(self.store, key) + async def delete_dir(self, prefix: str) -> None: + # docstring inherited + self._check_writable() + if prefix != "" and not prefix.endswith("/"): + prefix += "/" + + keys = [(k,) async for k in self.list_prefix(prefix)] + await concurrent_map(keys, self.delete, limit=config.get("async.concurrency")) + @property def supports_partial_writes(self) -> bool: # docstring inherited From 9ebbd5806f84c1333de3b4dc6f00e4eb70dc34aa Mon Sep 17 00:00:00 2001 From: Kenneth Li Date: Wed, 30 Jul 2025 12:34:09 -0400 Subject: [PATCH 2/4] add cl --- changes/3310.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/3310.feature.rst diff --git a/changes/3310.feature.rst b/changes/3310.feature.rst new file mode 100644 index 0000000000..b21d3219fc --- /dev/null +++ b/changes/3310.feature.rst @@ -0,0 +1 @@ +Add obstore implementation of delete_dir. From 146297c8ed74e7757f87036cf5d1e370473c825e Mon Sep 17 00:00:00 2001 From: Kenneth Li Date: Wed, 30 Jul 2025 12:55:52 -0400 Subject: [PATCH 3/4] add a delete test --- tests/test_store/test_object.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_store/test_object.py b/tests/test_store/test_object.py index d8b89e56b7..73681d54d4 100644 --- a/tests/test_store/test_object.py +++ b/tests/test_store/test_object.py @@ -90,6 +90,17 @@ async def test_store_getsize_prefix(self, store: ObjectStore) -> None: total_size = await store.getsize_prefix("c") assert total_size == len(buf) * 2 + async def test_store_delete(self, store: ObjectStore) -> None: + assert store.supports_deletes + buf = cpu.Buffer.from_bytes(b"\x01\x02\x03\x04") + await store.set("foo/1", buf) + await store.set("foo/2", buf) + await store.delete("foo/1") + assert not await store.exists("foo/1") + assert await store.exists("foo/2") + await store.delete_dir("foo") # FileNotFoundErrors are suppressed + assert not await store.exists("foo/2") + @pytest.mark.slow_hypothesis def test_zarr_hierarchy(): From 2e99a4d4cfe07dcd6316e263904d528b7d323491 Mon Sep 17 00:00:00 2001 From: Kenneth Li Date: Thu, 31 Jul 2025 11:01:45 -0400 Subject: [PATCH 4/4] use obstore list and collect_async --- src/zarr/storage/_obstore.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/zarr/storage/_obstore.py b/src/zarr/storage/_obstore.py index 39699f6662..a311442555 100644 --- a/src/zarr/storage/_obstore.py +++ b/src/zarr/storage/_obstore.py @@ -199,11 +199,14 @@ async def delete(self, key: str) -> None: async def delete_dir(self, prefix: str) -> None: # docstring inherited + import obstore as obs + self._check_writable() if prefix != "" and not prefix.endswith("/"): prefix += "/" - keys = [(k,) async for k in self.list_prefix(prefix)] + metas = await obs.list(self.store, prefix).collect_async() + keys = [(m["path"],) for m in metas] await concurrent_map(keys, self.delete, limit=config.get("async.concurrency")) @property