Skip to content

Commit 04b12d3

Browse files
committed
Merge branch 'v3' of https://github.com/zarr-developers/zarr-python into fix/logging_store_improvements
2 parents 76f835c + d2dc162 commit 04b12d3

File tree

11 files changed

+478
-112
lines changed

11 files changed

+478
-112
lines changed

docs/guide/consolidated_metadata.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ Usage
1212

1313
If consolidated metadata is present in a Zarr Group's metadata then it is used
1414
by default. The initial read to open the group will need to communicate with
15-
the store (reading from a file for a :class:`zarr.store.LocalStore`, making a
16-
network request for a :class:`zarr.store.RemoteStore`). After that, any subsequent
15+
the store (reading from a file for a :class:`zarr.storage.LocalStore`, making a
16+
network request for a :class:`zarr.storage.RemoteStore`). After that, any subsequent
1717
metadata reads get child Group or Array nodes will *not* require reads from the store.
1818

1919
In Python, the consolidated metadata is available on the ``.consolidated_metadata``
@@ -22,7 +22,7 @@ attribute of the ``GroupMetadata`` object.
2222
.. code-block:: python
2323
2424
>>> import zarr
25-
>>> store = zarr.store.MemoryStore({}, mode="w")
25+
>>> store = zarr.storage.MemoryStore({}, mode="w")
2626
>>> group = zarr.open_group(store=store)
2727
>>> group.create_array(shape=(1,), name="a")
2828
>>> group.create_array(shape=(2, 2), name="b")

docs/guide/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ Guide
44
.. toctree::
55
:maxdepth: 1
66

7+
storage
78
consolidated_metadata

docs/guide/storage.rst

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
Storage
2+
=======
3+
4+
Zarr-Python supports multiple storage backends, including: local file systems,
5+
Zip files, remote stores via ``fspec`` (S3, HTTP, etc.), and in-memory stores. In
6+
Zarr-Python 3, stores must implement the abstract store API from
7+
:class:`zarr.abc.store.Store`.
8+
9+
.. note::
10+
Unlike Zarr-Python 2 where the store interface was built around a generic ``MutableMapping``
11+
API, Zarr-Python 3 utilizes a custom store API that utilizes Python's AsyncIO library.
12+
13+
Implicit Store Creation
14+
-----------------------
15+
16+
In most cases, it is not required to create a ``Store`` object explicitly. Passing a string
17+
to Zarr's top level API will result in the store being created automatically.
18+
19+
.. code-block:: python
20+
21+
>>> import zarr
22+
>>> zarr.open("data/foo/bar", mode="r") # implicitly creates a LocalStore
23+
<Group file://data/foo/bar>
24+
>>> zarr.open("s3://foo/bar", mode="r") # implicitly creates a RemoteStore
25+
<Group s3://foo/bar>
26+
>>> data = {}
27+
>>> zarr.open(data, mode="w") # implicitly creates a MemoryStore
28+
<Group memory://4791444288>
29+
30+
Explicit Store Creation
31+
-----------------------
32+
33+
In some cases, it may be helpful to create a store instance directly. Zarr-Python offers four
34+
built-in store: :class:`zarr.storage.LocalStore`, :class:`zarr.storage.RemoteStore`,
35+
:class:`zarr.storage.ZipStore`, and :class:`zarr.storage.MemoryStore`.
36+
37+
Local Store
38+
~~~~~~~~~~~
39+
40+
The :class:`zarr.storage.LocalStore` stores data in a nested set of directories on a local
41+
filesystem.
42+
43+
.. code-block:: python
44+
45+
>>> import zarr
46+
>>> store = zarr.storage.LocalStore("data/foo/bar", mode="r")
47+
>>> zarr.open(store=store)
48+
<Group file://data/foo/bar>
49+
50+
Zip Store
51+
~~~~~~~~~
52+
53+
The :class:`zarr.storage.ZipStore` stores the contents of a Zarr hierarchy in a single
54+
Zip file. The `Zip Store specification_` is currently in draft form.
55+
56+
.. code-block:: python
57+
58+
>>> import zarr
59+
>>> store = zarr.storage.ZipStore("data.zip", mode="w")
60+
>>> zarr.open(store=store, shape=(2,))
61+
<Array zip://data.zip shape=(2,) dtype=float64
62+
63+
Remote Store
64+
~~~~~~~~~~~~
65+
66+
The :class:`zarr.storage.RemoteStore` stores the contents of a Zarr hierarchy in following the same
67+
logical layout as the ``LocalStore``, except the store is assumed to be on a remote storage system
68+
such as cloud object storage (e.g. AWS S3, Google Cloud Storage, Azure Blob Store). The
69+
:class:`zarr.storage.RemoteStore` is backed by `Fsspec_` and can support any Fsspec backend
70+
that implements the `AbstractFileSystem` API,
71+
72+
.. code-block:: python
73+
74+
>>> import zarr
75+
>>> store = zarr.storage.RemoteStore("gs://foo/bar", mode="r")
76+
>>> zarr.open(store=store)
77+
<Array <RemoteStore(GCSFileSystem, foo/bar)> shape=(10, 20) dtype=float32>
78+
79+
Memory Store
80+
~~~~~~~~~~~~
81+
82+
The :class:`zarr.storage.RemoteStore` a in-memory store that allows for serialization of
83+
Zarr data (metadata and chunks) to a dictionary.
84+
85+
.. code-block:: python
86+
87+
>>> import zarr
88+
>>> data = {}
89+
>>> store = zarr.storage.MemoryStore(data, mode="w")
90+
>>> zarr.open(store=store, shape=(2, ))
91+
<Array memory://4943638848 shape=(2,) dtype=float64>
92+
93+
Developing custom stores
94+
------------------------
95+
96+
Zarr-Python :class:`zarr.abc.store.Store` API is meant to be extended. The Store Abstract Base
97+
Class includes all of the methods needed to be a fully operational store in Zarr Python.
98+
Zarr also provides a test harness for custom stores: :class:`zarr.testing.store.StoreTests`.
99+
100+
.. _Zip Store Specification: https://github.com/zarr-developers/zarr-specs/pull/311
101+
.. _Fsspec: https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html#consolidated-metadata

src/zarr/abc/store.py

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121

2222
class AccessMode(NamedTuple):
23+
"""Access mode flags."""
24+
2325
str: AccessModeLiteral
2426
readonly: bool
2527
overwrite: bool
@@ -28,6 +30,24 @@ class AccessMode(NamedTuple):
2830

2931
@classmethod
3032
def from_literal(cls, mode: AccessModeLiteral) -> Self:
33+
"""
34+
Create an AccessMode instance from a literal.
35+
36+
Parameters
37+
----------
38+
mode : AccessModeLiteral
39+
One of 'r', 'r+', 'w', 'w-', 'a'.
40+
41+
Returns
42+
-------
43+
AccessMode
44+
The created instance.
45+
46+
Raises
47+
------
48+
ValueError
49+
If mode is not one of 'r', 'r+', 'w', 'w-', 'a'.
50+
"""
3151
if mode in ("r", "r+", "a", "w", "w-"):
3252
return cls(
3353
str=mode,
@@ -40,6 +60,10 @@ def from_literal(cls, mode: AccessModeLiteral) -> Self:
4060

4161

4262
class Store(ABC):
63+
"""
64+
Abstract base class for Zarr stores.
65+
"""
66+
4367
_mode: AccessMode
4468
_is_open: bool
4569

@@ -49,6 +73,21 @@ def __init__(self, *args: Any, mode: AccessModeLiteral = "r", **kwargs: Any) ->
4973

5074
@classmethod
5175
async def open(cls, *args: Any, **kwargs: Any) -> Self:
76+
"""
77+
Create and open the store.
78+
79+
Parameters
80+
----------
81+
*args : Any
82+
Positional arguments to pass to the store constructor.
83+
**kwargs : Any
84+
Keyword arguments to pass to the store constructor.
85+
86+
Returns
87+
-------
88+
Store
89+
The opened store instance.
90+
"""
5291
store = cls(*args, **kwargs)
5392
await store._open()
5493
return store
@@ -67,6 +106,20 @@ def __exit__(
67106
self.close()
68107

69108
async def _open(self) -> None:
109+
"""
110+
Open the store.
111+
112+
Raises
113+
------
114+
ValueError
115+
If the store is already open.
116+
FileExistsError
117+
If ``mode='w-'`` and the store already exists.
118+
119+
Notes
120+
-----
121+
* When ``mode='w'`` and the store already exists, it will be cleared.
122+
"""
70123
if self._is_open:
71124
raise ValueError("store is already open")
72125
if self.mode.str == "w":
@@ -76,14 +129,30 @@ async def _open(self) -> None:
76129
self._is_open = True
77130

78131
async def _ensure_open(self) -> None:
132+
"""Open the store if it is not already open."""
79133
if not self._is_open:
80134
await self._open()
81135

82136
@abstractmethod
83-
async def empty(self) -> bool: ...
137+
async def empty(self) -> bool:
138+
"""
139+
Check if the store is empty.
140+
141+
Returns
142+
-------
143+
bool
144+
True if the store is empty, False otherwise.
145+
"""
146+
...
84147

85148
@abstractmethod
86-
async def clear(self) -> None: ...
149+
async def clear(self) -> None:
150+
"""
151+
Clear the store.
152+
153+
Remove all keys and values from the store.
154+
"""
155+
...
87156

88157
@abstractmethod
89158
def with_mode(self, mode: AccessModeLiteral) -> Self:
@@ -116,6 +185,7 @@ def mode(self) -> AccessMode:
116185
return self._mode
117186

118187
def _check_writable(self) -> None:
188+
"""Raise an exception if the store is not writable."""
119189
if self.mode.readonly:
120190
raise ValueError("store mode does not support writing")
121191

@@ -199,7 +269,7 @@ async def set_if_not_exists(self, key: str, value: Buffer) -> None:
199269
Store a key to ``value`` if the key is not already present.
200270
201271
Parameters
202-
-----------
272+
----------
203273
key : str
204274
value : Buffer
205275
"""
@@ -339,6 +409,17 @@ async def set_if_not_exists(self, default: Buffer) -> None: ...
339409

340410

341411
async def set_or_delete(byte_setter: ByteSetter, value: Buffer | None) -> None:
412+
"""Set or delete a value in a byte setter
413+
414+
Parameters
415+
----------
416+
byte_setter : ByteSetter
417+
value : Buffer | None
418+
419+
Notes
420+
-----
421+
If value is None, the key will be deleted.
422+
"""
342423
if value is None:
343424
await byte_setter.delete()
344425
else:

src/zarr/storage/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from zarr.storage.common import StoreLike, StorePath, make_store_path
22
from zarr.storage.local import LocalStore
3+
from zarr.storage.logging import LoggingStore
34
from zarr.storage.memory import MemoryStore
45
from zarr.storage.remote import RemoteStore
56
from zarr.storage.zip import ZipStore
67

78
__all__ = [
89
"LocalStore",
10+
"LoggingStore",
911
"MemoryStore",
1012
"RemoteStore",
1113
"StoreLike",

0 commit comments

Comments
 (0)