Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/zarr/storage/_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,19 @@ async def delete_dir(self, prefix: str) -> None:

async def delete(self, key: str) -> None:
# docstring inherited
# we choose to only raise NotImplementedError here if the key exists
# this allows the array/group APIs to avoid the overhead of existence checks
# If key is present it is replaced by an empty byte literal as a way of representing it as deleted
self._check_writable()
if await self.exists(key):
raise NotImplementedError
keyinfo = zipfile.ZipInfo(filename=key, date_time=time.localtime(time.time())[:6])
keyinfo.compress_type = self.compression
if keyinfo.filename[-1] == os.sep:
keyinfo.external_attr = 0o40775 << 16 # drwxrwxr-x
keyinfo.external_attr |= 0x10
else:
keyinfo.external_attr = 0o644 << 16 # ?rw-r--r--

with self._lock:
self._zf.writestr(keyinfo, b"")

async def exists(self, key: str) -> bool:
# docstring inherited
Expand Down
11 changes: 10 additions & 1 deletion src/zarr/testing/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from abc import abstractmethod
from typing import TYPE_CHECKING, Generic, TypeVar

from zarr.storage import WrapperStore
from zarr.storage import WrapperStore, ZipStore

if TYPE_CHECKING:
from typing import Any
Expand Down Expand Up @@ -340,6 +340,15 @@ async def test_delete(self, store: S) -> None:
await store.delete("foo/zarr.json")
assert not await store.exists("foo/zarr.json")

async def test_delete_zip_store(self, store: S) -> None:
if not isinstance(store, ZipStore):
pytest.skip("store is not a ZipStore")
await store.set("foo/zarr.json", self.buffer_cls.from_bytes(b"bar"))
assert await store.exists("foo/zarr.json")
await store.delete("foo/zarr.json")
result = await store.get("foo/zarr.json", default_buffer_prototype())
assert result == self.buffer_cls.from_bytes(b"")

async def test_delete_dir(self, store: S) -> None:
if not store.supports_deletes:
pytest.skip("store does not support deletes")
Expand Down
4 changes: 2 additions & 2 deletions tests/test_store/test_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ def test_api_integration(self, store: ZipStore) -> None:

# TODO: assigning an entire chunk to fill value ends up deleting the chunk which is not supported
# a work around will be needed here.
with pytest.raises(NotImplementedError):
z[0:10, 0:10] = 99

z[0:10, 0:10] = 99

bar = root.create_group("bar", attributes={"hello": "world"})
assert "hello" in dict(bar.attrs)
Expand Down