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
1 change: 1 addition & 0 deletions changes/2856.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Opening a string or Path path is redirected to ZipStore if the path has a .zip suffix.
7 changes: 5 additions & 2 deletions docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,14 @@ z[:, :] = np.random.random((100, 100))
store.close()
```

To open an existing array from a ZIP file:
To open an existing array from a ZIP file you can open it with the usual function or by explicitly opening the `ZipStore` first :

```python exec="true" session="quickstart" source="above" result="code"

# Open the ZipStore in read-only mode
# Using the convenience functions, opening in read-only mode
z = zarr.open_array("data/example-5.zip", mode='r')

# Using a ZipStore in read-only mode
store = zarr.storage.ZipStore("data/example-5.zip", read_only=True)

z = zarr.open_array(store, mode='r')
Expand Down
6 changes: 5 additions & 1 deletion docs/user-guide/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ print(group)
`StoreLike` values can be:

- a `Path` or string indicating a location on the local file system.
This will create a [local store](#local-store):
This will create a [local store](#local-store), unless the file name endswith the ".zip" suffix, in which case it creates a [zip store](#zip-store):
```python exec="true" session="storage" source="above" result="ansi"
group = zarr.open_group(store='data/foo/bar')
print(group)
Expand All @@ -57,6 +57,10 @@ print(group)
group = zarr.open_group(store=Path('data/foo/bar'))
print(group)
```
```python exec="true" session="storage" source="above" result="ansi"
group = zarr.open_group(Path('data/foo.zip'), mode='w')
print(group)
```

- an FSSpec URI string, indicating a [remote store](#remote-store) location:
```python exec="true" session="storage" source="above" result="ansi"
Expand Down
8 changes: 7 additions & 1 deletion src/zarr/storage/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from zarr.storage._local import LocalStore
from zarr.storage._memory import ManagedMemoryStore, MemoryStore
from zarr.storage._utils import _join_paths, normalize_path, parse_store_url
from zarr.storage._zip import ZipStore

_has_fsspec = importlib.util.find_spec("fsspec")
if _has_fsspec:
Expand Down Expand Up @@ -318,7 +319,8 @@ async def make_store(
`StoreLike` objects are converted to `Store` as follows:

- `Store` or `StorePath` = `Store` object.
- `Path` or `str` = `LocalStore` object.
- `Path` or `str` with a .zip suffix = `ZipStore` object.
- other `Path` or `str` = `LocalStore` object.
- `str` that starts with a protocol = `FsspecStore` object.
- `dict[str, Buffer]` = `MemoryStore` object.
- `None` = `MemoryStore` object.
Expand Down Expand Up @@ -383,6 +385,10 @@ async def make_store(
# Create a new in-memory store
return await make_store({}, mode=mode, storage_options=storage_options)

elif isinstance(store_like, Path) and store_like.suffix == ".zip":
# Create a new LocalStore
return await ZipStore.open(path=store_like, mode=mode, read_only=_read_only)

elif isinstance(store_like, Path):
# Create a new LocalStore
return await LocalStore.open(root=store_like, mode=mode, read_only=_read_only)
Expand Down
33 changes: 32 additions & 1 deletion tests/test_store/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from pathlib import Path
from typing import Any, Literal

import numpy as np
import pytest
from _pytest.compat import LEGACY_PATH

import zarr
from zarr import Group
from zarr import Group, open_group
from zarr.core.buffer import cpu
from zarr.core.common import ZARR_JSON, AccessModeLiteral, ZarrFormat
from zarr.storage import FsspecStore, LocalStore, MemoryStore, StoreLike, StorePath, ZipStore
Expand Down Expand Up @@ -179,6 +180,36 @@ async def test_make_store_path_local(
assert store_path.read_only == (mode == "r")


@pytest.mark.parametrize("store_type", [str, Path])
@pytest.mark.parametrize("mode", ["r", "w"])
async def test_make_store_path_zip_path(
tmpdir: LEGACY_PATH,
store_type: type[str] | type[Path] | type[LocalStore],
mode: AccessModeLiteral,
) -> None:
"""
Test that make_store_path creates a ZipStore given a path ending in .zip
"""
zippath = Path(tmpdir) / "zarr.zip"
store_like = store_type(str(zippath))

if mode == "r":
store = ZipStore(zippath, mode="w")
root = open_group(store=store, mode="w")
data = np.arange(10000, dtype=np.uint16).reshape(100, 100)
z = root.create_array(
shape=data.shape, chunks=(10, 10), name="foo", dtype=np.uint16, fill_value=99
)
z[:] = data
store.close()

store_path = await make_store_path(store_like, mode=mode)
assert isinstance(store_path.store, ZipStore)
assert Path(store_path.store.path) == zippath
assert store_path.path == normalize_path("")
assert store_path.read_only == (mode == "r")


@pytest.mark.parametrize("path", [None, "", "bar"])
@pytest.mark.parametrize("mode", ["r", "w"])
async def test_make_store_path_store_path(
Expand Down
Loading