diff --git a/changes/2758.bugfix.rst b/changes/2758.bugfix.rst new file mode 100644 index 0000000000..6b80f8a626 --- /dev/null +++ b/changes/2758.bugfix.rst @@ -0,0 +1 @@ +Fix zip-store path checking for stores with directories listed as files. \ No newline at end of file diff --git a/src/zarr/storage/_zip.py b/src/zarr/storage/_zip.py index 51bb702c27..bbfe6c67aa 100644 --- a/src/zarr/storage/_zip.py +++ b/src/zarr/storage/_zip.py @@ -283,7 +283,7 @@ async def list_dir(self, prefix: str) -> AsyncIterator[str]: yield key else: for key in keys: - if key.startswith(prefix + "/") and key != prefix: + if key.startswith(prefix + "/") and key.strip("/") != prefix: k = key.removeprefix(prefix + "/").split("/")[0] if k not in seen: seen.add(k) diff --git a/tests/test_store/test_zip.py b/tests/test_store/test_zip.py index a83327d99a..839656108b 100644 --- a/tests/test_store/test_zip.py +++ b/tests/test_store/test_zip.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import shutil import tempfile import zipfile from typing import TYPE_CHECKING @@ -14,6 +15,7 @@ from zarr.testing.store import StoreTests if TYPE_CHECKING: + from pathlib import Path from typing import Any @@ -111,3 +113,15 @@ async def test_zip_open_mode_translation( kws = {**store_kwargs, "mode": zip_mode} store = await self.store_cls.open(**kws) assert store.read_only == read_only + + def test_externally_zipped_store(self, tmp_path: Path) -> None: + # See: https://github.com/zarr-developers/zarr-python/issues/2757 + zarr_path = tmp_path / "foo.zarr" + root = zarr.open_group(store=zarr_path, mode="w") + root.require_group("foo") + root["foo"]["bar"] = np.array([1]) + shutil.make_archive(zarr_path, "zip", zarr_path) + zip_path = tmp_path / "foo.zarr.zip" + zipped = zarr.open_group(ZipStore(zip_path, mode="r"), mode="r") + assert list(zipped.keys()) == list(root.keys()) + assert list(zipped["foo"].keys()) == list(root["foo"].keys())