Skip to content

Commit ec2d43d

Browse files
committed
progress on store interface (memory and local stores only)
1 parent f4822c7 commit ec2d43d

File tree

13 files changed

+2775
-2804
lines changed

13 files changed

+2775
-2804
lines changed

zarr/tests/test_storage.py

Lines changed: 2485 additions & 2479 deletions
Large diffs are not rendered by default.

zarr/v3/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88
from zarr.v3.group import Group # noqa: F401
99
from zarr.v3.metadata import RuntimeConfiguration, runtime_configuration # noqa: F401
1010
from zarr.v3.store import ( # noqa: F401
11-
LocalStore,
12-
RemoteStore,
13-
Store,
1411
StoreLike,
15-
StorePath,
1612
make_store_path,
1713
)
1814
from zarr.v3.sync import sync as _sync
@@ -27,7 +23,6 @@ async def open_auto_async(
2723
return await Array.open(store_path, runtime_configuration=runtime_configuration_)
2824
except KeyError:
2925
return await Group.open(store_path, runtime_configuration=runtime_configuration_)
30-
3126

3227

3328
def open_auto(

zarr/v3/abc/codec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import numpy as np
1717

1818
from zarr.v3.common import BytesLike, SliceSelection
19-
from zarr.v3.stores import StorePath
19+
from zarr.v3.store import StorePath
2020

2121

2222
if TYPE_CHECKING:

zarr/v3/abc/store.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
from abc import abstractmethod, ABC
22

3-
from typing import List, Tuple
3+
from typing import List, Tuple, Optional
44

55

66
class Store(ABC):
7-
pass
8-
9-
10-
class ReadStore(Store):
117
@abstractmethod
12-
async def get(self, key: str) -> bytes:
8+
async def get(
9+
self, key: str, byte_range: Optional[Tuple[int, Optional[int]]] = None
10+
) -> Optional[bytes]:
1311
"""Retrieve the value associated with a given key.
1412
1513
Parameters
1614
----------
1715
key : str
16+
byte_range : tuple[int, Optional[int]], optional
1817
1918
Returns
2019
-------
@@ -23,7 +22,9 @@ async def get(self, key: str) -> bytes:
2322
...
2423

2524
@abstractmethod
26-
async def get_partial_values(self, key_ranges: List[Tuple[str, Tuple[int, int]]]) -> List[bytes]:
25+
async def get_partial_values(
26+
self, key_ranges: List[Tuple[str, Tuple[int, int]]]
27+
) -> List[bytes]:
2728
"""Retrieve possibly partial values from given key_ranges.
2829
2930
Parameters
@@ -38,6 +39,7 @@ async def get_partial_values(self, key_ranges: List[Tuple[str, Tuple[int, int]]]
3839
"""
3940
...
4041

42+
@abstractmethod
4143
async def exists(self, key: str) -> bool:
4244
"""Check if a key exists in the store.
4345
@@ -51,8 +53,12 @@ async def exists(self, key: str) -> bool:
5153
"""
5254
...
5355

56+
@property
57+
@abstractmethod
58+
def supports_writes(self) -> bool:
59+
"""Does the store support writes?"""
60+
...
5461

55-
class WriteStore(ReadStore):
5662
@abstractmethod
5763
async def set(self, key: str, value: bytes) -> None:
5864
"""Store a (key, value) pair.
@@ -64,7 +70,8 @@ async def set(self, key: str, value: bytes) -> None:
6470
"""
6571
...
6672

67-
async def delete(self, key: str) -> None
73+
@abstractmethod
74+
async def delete(self, key: str) -> None:
6875
"""Remove a key from the store
6976
7077
Parameters
@@ -73,10 +80,11 @@ async def delete(self, key: str) -> None
7380
"""
7481
...
7582

76-
77-
class PartialWriteStore(WriteStore):
78-
# TODO, instead of using this, should we just check if the store is a PartialWriteStore?
79-
supports_partial_writes = True
83+
@property
84+
@abstractmethod
85+
def supports_partial_writes(self) -> bool:
86+
"""Does the store support partial writes?"""
87+
...
8088

8189
@abstractmethod
8290
async def set_partial_values(self, key_start_values: List[Tuple[str, int, bytes]]) -> None:
@@ -91,8 +99,12 @@ async def set_partial_values(self, key_start_values: List[Tuple[str, int, bytes]
9199
"""
92100
...
93101

102+
@property
103+
@abstractmethod
104+
def supports_listing(self) -> bool:
105+
"""Does the store support listing?"""
106+
...
94107

95-
class ListMixin:
96108
@abstractmethod
97109
async def list(self) -> List[str]:
98110
"""Retrieve all keys in the store.
@@ -132,11 +144,3 @@ async def list_dir(self, prefix: str) -> List[str]:
132144
list[str]
133145
"""
134146
...
135-
136-
137-
class ReadListStore(ReadStore, ListMixin):
138-
pass
139-
140-
141-
class WriteListStore(WriteStore, ListMixin):
142-
pass

zarr/v3/codecs/sharding.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
CodecMetadata,
4444
ShardingCodecIndexLocation,
4545
)
46-
from zarr.v3.stores import StorePath
46+
from zarr.v3.store import StorePath
4747

4848
MAX_UINT_64 = 2**64 - 1
4949

zarr/v3/store/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from zarr.v3.store.core import StorePath, StoreLike, make_store_path
2+
from zarr.v3.store.remote import RemoteStore
3+
from zarr.v3.store.local import LocalStore
4+
from zarr.v3.store.memory import MemoryStore

zarr/v3/store/core.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from typing import Any, Optional, Tuple, Union
5+
6+
from zarr.v3.common import BytesLike
7+
from zarr.v3.abc.store import Store
8+
9+
10+
def _dereference_path(root: str, path: str) -> str:
11+
assert isinstance(root, str)
12+
assert isinstance(path, str)
13+
root = root.rstrip("/")
14+
path = f"{root}/{path}" if root != "" else path
15+
path = path.rstrip("/")
16+
return path
17+
18+
19+
class StorePath:
20+
store: Store
21+
path: str
22+
23+
def __init__(self, store: Store, path: Optional[str] = None):
24+
self.store = store
25+
self.path = path or ""
26+
27+
@classmethod
28+
def from_path(cls, pth: Path) -> StorePath:
29+
return cls(Store.from_path(pth))
30+
31+
async def get(
32+
self, byte_range: Optional[Tuple[int, Optional[int]]] = None
33+
) -> Optional[BytesLike]:
34+
return await self.store.get(self.path, byte_range)
35+
36+
async def set(self, value: BytesLike, byte_range: Optional[Tuple[int, int]] = None) -> None:
37+
if byte_range is not None:
38+
raise NotImplementedError("Store.set does not have partial writes yet")
39+
await self.store.set(self.path, value)
40+
41+
async def delete(self) -> None:
42+
await self.store.delete(self.path)
43+
44+
async def exists(self) -> bool:
45+
return await self.store.exists(self.path)
46+
47+
def __truediv__(self, other: str) -> StorePath:
48+
return self.__class__(self.store, _dereference_path(self.path, other))
49+
50+
def __str__(self) -> str:
51+
return _dereference_path(str(self.store), self.path)
52+
53+
def __repr__(self) -> str:
54+
return f"StorePath({self.store.__class__.__name__}, {repr(str(self))})"
55+
56+
def __eq__(self, other: Any) -> bool:
57+
try:
58+
if self.store == other.store and self.path == other.path:
59+
return True
60+
except Exception:
61+
pass
62+
return False
63+
64+
65+
StoreLike = Union[Store, StorePath, Path, str]
66+
67+
68+
def make_store_path(store_like: StoreLike) -> StorePath:
69+
if isinstance(store_like, StorePath):
70+
return store_like
71+
elif isinstance(store_like, Store):
72+
return StorePath(store_like)
73+
# elif isinstance(store_like, Path):
74+
# return StorePath(Store.from_path(store_like))
75+
elif isinstance(store_like, str):
76+
try:
77+
from upath import UPath
78+
79+
return StorePath(Store.from_path(UPath(store_like)))
80+
except ImportError as e:
81+
raise e
82+
# return StorePath(LocalStore(Path(store_like)))
83+
raise TypeError

0 commit comments

Comments
 (0)