Skip to content

Commit ee47265

Browse files
committed
fix(store): refactor store initialization to include only r or w permissions
1 parent a1e71f1 commit ee47265

File tree

22 files changed

+276
-220
lines changed

22 files changed

+276
-220
lines changed

src/zarr/abc/store.py

Lines changed: 33 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,34 @@
33
from abc import ABC, abstractmethod
44
from asyncio import gather
55
from itertools import starmap
6-
from typing import TYPE_CHECKING, NamedTuple, Protocol, runtime_checkable
6+
from typing import TYPE_CHECKING, Literal, Protocol, runtime_checkable
77

88
if TYPE_CHECKING:
99
from collections.abc import AsyncGenerator, Iterable
1010
from types import TracebackType
1111
from typing import Any, Self, TypeAlias
1212

1313
from zarr.core.buffer import Buffer, BufferPrototype
14-
from zarr.core.common import AccessModeLiteral, BytesLike
14+
from zarr.core.common import BytesLike
1515

16-
__all__ = ["AccessMode", "ByteGetter", "ByteSetter", "Store", "set_or_delete"]
16+
__all__ = ["StoreAccessMode", "ByteGetter", "ByteSetter", "Store", "set_or_delete"]
1717

1818
ByteRangeRequest: TypeAlias = tuple[int | None, int | None]
19-
20-
21-
class AccessMode(NamedTuple):
22-
"""Access mode flags."""
23-
24-
str: AccessModeLiteral
25-
readonly: bool
26-
overwrite: bool
27-
create: bool
28-
update: bool
29-
30-
@classmethod
31-
def from_literal(cls, mode: AccessModeLiteral) -> Self:
32-
"""
33-
Create an AccessMode instance from a literal.
34-
35-
Parameters
36-
----------
37-
mode : AccessModeLiteral
38-
One of 'r', 'r+', 'w', 'w-', 'a'.
39-
40-
Returns
41-
-------
42-
AccessMode
43-
The created instance.
44-
45-
Raises
46-
------
47-
ValueError
48-
If mode is not one of 'r', 'r+', 'w', 'w-', 'a'.
49-
"""
50-
if mode in ("r", "r+", "a", "w", "w-"):
51-
return cls(
52-
str=mode,
53-
readonly=mode == "r",
54-
overwrite=mode == "w",
55-
create=mode in ("a", "w", "w-"),
56-
update=mode in ("r+", "a"),
57-
)
58-
raise ValueError("mode must be one of 'r', 'r+', 'w', 'w-', 'a'")
19+
StoreAccessMode = Literal["r", "w"]
5920

6021

6122
class Store(ABC):
6223
"""
6324
Abstract base class for Zarr stores.
6425
"""
6526

66-
_mode: AccessMode
27+
_mode: StoreAccessMode
6728
_is_open: bool
6829

69-
def __init__(self, *args: Any, mode: AccessModeLiteral = "r", **kwargs: Any) -> None:
30+
def __init__(self, *args: Any, mode: StoreAccessMode = "r", **kwargs: Any) -> None:
7031
self._is_open = False
71-
self._mode = AccessMode.from_literal(mode)
32+
assert mode in ("r", "w")
33+
self._mode = mode
7234

7335
@classmethod
7436
async def open(cls, *args: Any, **kwargs: Any) -> Self:
@@ -112,49 +74,48 @@ async def _open(self) -> None:
11274
------
11375
ValueError
11476
If the store is already open.
115-
FileExistsError
116-
If ``mode='w-'`` and the store already exists.
117-
118-
Notes
119-
-----
120-
* When ``mode='w'`` and the store already exists, it will be cleared.
12177
"""
12278
if self._is_open:
12379
raise ValueError("store is already open")
124-
if self.mode.str == "w":
125-
await self.clear()
126-
elif self.mode.str == "w-" and not await self.empty():
127-
raise FileExistsError("Store already exists")
12880
self._is_open = True
12981

13082
async def _ensure_open(self) -> None:
13183
"""Open the store if it is not already open."""
13284
if not self._is_open:
13385
await self._open()
13486

135-
@abstractmethod
136-
async def empty(self) -> bool:
87+
async def empty(self, prefix: str = "") -> bool:
13788
"""
138-
Check if the store is empty.
89+
Check if the directory is empty.
90+
91+
Parameters
92+
----------
93+
prefix : str
94+
Prefix of keys to check.
13995
14096
Returns
14197
-------
14298
bool
14399
True if the store is empty, False otherwise.
144100
"""
145-
...
146101

147-
@abstractmethod
102+
if not prefix.endswith("/"):
103+
prefix += "/"
104+
async for _ in self.list_prefix(prefix):
105+
return False
106+
return True
107+
148108
async def clear(self) -> None:
149109
"""
150110
Clear the store.
151111
152112
Remove all keys and values from the store.
153113
"""
154-
...
114+
async for key in self.list():
115+
await self.delete(key)
155116

156117
@abstractmethod
157-
def with_mode(self, mode: AccessModeLiteral) -> Self:
118+
def with_mode(self, mode: StoreAccessMode) -> Self:
158119
"""
159120
Return a new store of the same type pointing to the same location with a new mode.
160121
@@ -163,7 +124,7 @@ def with_mode(self, mode: AccessModeLiteral) -> Self:
163124
164125
Parameters
165126
----------
166-
mode : AccessModeLiteral
127+
mode : StoreAccessMode
167128
The new mode to use.
168129
169130
Returns
@@ -179,14 +140,19 @@ def with_mode(self, mode: AccessModeLiteral) -> Self:
179140
...
180141

181142
@property
182-
def mode(self) -> AccessMode:
143+
def mode(self) -> StoreAccessMode:
183144
"""Access mode of the store."""
184145
return self._mode
185146

147+
@property
148+
def readonly(self) -> bool:
149+
"""Is the store read-only?"""
150+
return self.mode == "r"
151+
186152
def _check_writable(self) -> None:
187153
"""Raise an exception if the store is not writable."""
188-
if self.mode.readonly:
189-
raise ValueError("store mode does not support writing")
154+
if self.mode != "w":
155+
raise ValueError(f"store mode ({self.mode}) does not support writing")
190156

191157
@abstractmethod
192158
def __eq__(self, value: object) -> bool:

0 commit comments

Comments
 (0)