33from abc import ABC , abstractmethod
44from asyncio import gather
55from itertools import starmap
6- from typing import TYPE_CHECKING , NamedTuple , Protocol , runtime_checkable
6+ from typing import TYPE_CHECKING , Protocol , runtime_checkable
77
88if TYPE_CHECKING :
9- from collections .abc import AsyncGenerator , Iterable
9+ from collections .abc import AsyncGenerator , AsyncIterator , 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__ = ["ByteGetter" , "ByteSetter" , "Store" , "set_or_delete" ]
1717
1818ByteRangeRequest : TypeAlias = tuple [int | None , int | None ]
1919
2020
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'" )
59-
60-
6121class Store (ABC ):
6222 """
6323 Abstract base class for Zarr stores.
6424 """
6525
66- _mode : AccessMode
26+ _read_only : bool
6727 _is_open : bool
6828
69- def __init__ (self , * args : Any , mode : AccessModeLiteral = "r" , ** kwargs : Any ) -> None :
29+ def __init__ (self , * , read_only : bool = False ) -> None :
7030 self ._is_open = False
71- self ._mode = AccessMode . from_literal ( mode )
31+ self ._read_only = read_only
7232
7333 @classmethod
7434 async def open (cls , * args : Any , ** kwargs : Any ) -> Self :
@@ -112,81 +72,60 @@ async def _open(self) -> None:
11272 ------
11373 ValueError
11474 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.
12175 """
12276 if self ._is_open :
12377 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" )
12878 self ._is_open = True
12979
13080 async def _ensure_open (self ) -> None :
13181 """Open the store if it is not already open."""
13282 if not self ._is_open :
13383 await self ._open ()
13484
135- @abstractmethod
136- async def empty (self ) -> bool :
85+ async def is_empty (self , prefix : str ) -> bool :
13786 """
138- Check if the store is empty.
87+ Check if the directory is empty.
88+
89+ Parameters
90+ ----------
91+ prefix : str
92+ Prefix of keys to check.
13993
14094 Returns
14195 -------
14296 bool
14397 True if the store is empty, False otherwise.
14498 """
145- ...
99+ if not self .supports_listing :
100+ raise NotImplementedError
101+ if prefix != "" and not prefix .endswith ("/" ):
102+ prefix += "/"
103+ async for _ in self .list_prefix (prefix ):
104+ return False
105+ return True
146106
147- @abstractmethod
148107 async def clear (self ) -> None :
149108 """
150109 Clear the store.
151110
152111 Remove all keys and values from the store.
153112 """
154- ...
155-
156- @abstractmethod
157- def with_mode (self , mode : AccessModeLiteral ) -> Self :
158- """
159- Return a new store of the same type pointing to the same location with a new mode.
160-
161- The returned Store is not automatically opened. Call :meth:`Store.open` before
162- using.
163-
164- Parameters
165- ----------
166- mode : AccessModeLiteral
167- The new mode to use.
168-
169- Returns
170- -------
171- store
172- A new store of the same type with the new mode.
173-
174- Examples
175- --------
176- >>> writer = zarr.store.MemoryStore(mode="w")
177- >>> reader = writer.with_mode("r")
178- """
179- ...
113+ if not self .supports_deletes :
114+ raise NotImplementedError
115+ if not self .supports_listing :
116+ raise NotImplementedError
117+ self ._check_writable ()
118+ await self .delete_dir ("" )
180119
181120 @property
182- def mode (self ) -> AccessMode :
183- """Access mode of the store. """
184- return self ._mode
121+ def read_only (self ) -> bool :
122+ """Is the store read-only? """
123+ return self ._read_only
185124
186125 def _check_writable (self ) -> None :
187126 """Raise an exception if the store is not writable."""
188- if self .mode . readonly :
189- raise ValueError ("store mode does not support writing" )
127+ if self .read_only :
128+ raise ValueError ("store was opened in read-only mode and does not support writing" )
190129
191130 @abstractmethod
192131 def __eq__ (self , value : object ) -> bool :
@@ -329,16 +268,19 @@ def supports_listing(self) -> bool:
329268 ...
330269
331270 @abstractmethod
332- def list (self ) -> AsyncGenerator [str ]:
271+ def list (self ) -> AsyncIterator [str ]:
333272 """Retrieve all keys in the store.
334273
335274 Returns
336275 -------
337- AsyncGenerator [str, None ]
276+ AsyncIterator [str]
338277 """
278+ # This method should be async, like overridden methods in child classes.
279+ # However, that's not straightforward:
280+ # https://stackoverflow.com/questions/68905848
339281
340282 @abstractmethod
341- def list_prefix (self , prefix : str ) -> AsyncGenerator [str ]:
283+ def list_prefix (self , prefix : str ) -> AsyncIterator [str ]:
342284 """
343285 Retrieve all keys in the store that begin with a given prefix. Keys are returned relative
344286 to the root of the store.
@@ -349,11 +291,14 @@ def list_prefix(self, prefix: str) -> AsyncGenerator[str]:
349291
350292 Returns
351293 -------
352- AsyncGenerator [str, None ]
294+ AsyncIterator [str]
353295 """
296+ # This method should be async, like overridden methods in child classes.
297+ # However, that's not straightforward:
298+ # https://stackoverflow.com/questions/68905848
354299
355300 @abstractmethod
356- def list_dir (self , prefix : str ) -> AsyncGenerator [str ]:
301+ def list_dir (self , prefix : str ) -> AsyncIterator [str ]:
357302 """
358303 Retrieve all keys and prefixes with a given prefix and which do not contain the character
359304 “/” after the given prefix.
@@ -364,8 +309,11 @@ def list_dir(self, prefix: str) -> AsyncGenerator[str]:
364309
365310 Returns
366311 -------
367- AsyncGenerator [str, None ]
312+ AsyncIterator [str]
368313 """
314+ # This method should be async, like overridden methods in child classes.
315+ # However, that's not straightforward:
316+ # https://stackoverflow.com/questions/68905848
369317
370318 async def delete_dir (self , prefix : str ) -> None :
371319 """
@@ -376,7 +324,7 @@ async def delete_dir(self, prefix: str) -> None:
376324 if not self .supports_listing :
377325 raise NotImplementedError
378326 self ._check_writable ()
379- if not prefix .endswith ("/" ):
327+ if prefix != "" and not prefix .endswith ("/" ):
380328 prefix += "/"
381329 async for key in self .list_prefix (prefix ):
382330 await self .delete (key )
0 commit comments