Skip to content

Commit a3debbb

Browse files
committed
Extract ABSStore to zarr._storage.absstore
First step towards being able to assign CODEOWNERS to simplify maintenance. This does not yet attempt the rename to zarr.storage.absstore in order lower conflicts with open PRs. Imports should remain un- changed for the moment. see: zarr-developers#764
1 parent efd94fa commit a3debbb

File tree

3 files changed

+201
-191
lines changed

3 files changed

+201
-191
lines changed

zarr/_storage/__init__.py

Whitespace-only changes.

zarr/_storage/absstore.py

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
"""This module contains storage classes related to Azure Blob Storage (ABS)"""
2+
3+
import warnings
4+
from collections.abc import MutableMapping
5+
from zarr.util import normalize_storage_path
6+
7+
__doctest_requires__ = {
8+
('ABSStore', 'ABSStore.*'): ['azure.storage.blob'],
9+
}
10+
11+
12+
class ABSStore(MutableMapping):
13+
"""Storage class using Azure Blob Storage (ABS).
14+
15+
Parameters
16+
----------
17+
container : string
18+
The name of the ABS container to use.
19+
.. deprecated::
20+
Use ``client`` instead.
21+
prefix : string
22+
Location of the "directory" to use as the root of the storage hierarchy
23+
within the container.
24+
account_name : string
25+
The Azure blob storage account name.
26+
.. deprecated:: 2.8.3
27+
Use ``client`` instead.
28+
account_key : string
29+
The Azure blob storage account access key.
30+
.. deprecated:: 2.8.3
31+
Use ``client`` instead.
32+
blob_service_kwargs : dictionary
33+
Extra arguments to be passed into the azure blob client, for e.g. when
34+
using the emulator, pass in blob_service_kwargs={'is_emulated': True}.
35+
.. deprecated:: 2.8.3
36+
Use ``client`` instead.
37+
dimension_separator : {'.', '/'}, optional
38+
Separator placed between the dimensions of a chunk.
39+
client : azure.storage.blob.ContainerClient, optional
40+
And ``azure.storage.blob.ContainerClient`` to connect with. See
41+
`here <https://docs.microsoft.com/en-us/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python>`_ # noqa
42+
for more.
43+
44+
.. versionadded:: 2.8.3
45+
46+
Notes
47+
-----
48+
In order to use this store, you must install the Microsoft Azure Storage SDK for Python,
49+
``azure-storage-blob>=12.5.0``.
50+
"""
51+
52+
def __init__(self, container=None, prefix='', account_name=None, account_key=None,
53+
blob_service_kwargs=None, dimension_separator=None,
54+
client=None,
55+
):
56+
self._dimension_separator = dimension_separator
57+
self.prefix = normalize_storage_path(prefix)
58+
if client is None:
59+
# deprecated option, try to construct the client for them
60+
msg = (
61+
"Providing 'container', 'account_name', 'account_key', and 'blob_service_kwargs'"
62+
"is deprecated. Provide and instance of 'azure.storage.blob.ContainerClient' "
63+
"'client' instead."
64+
)
65+
warnings.warn(msg, FutureWarning, stacklevel=2)
66+
from azure.storage.blob import ContainerClient
67+
blob_service_kwargs = blob_service_kwargs or {}
68+
client = ContainerClient(
69+
"https://{}.blob.core.windows.net/".format(account_name), container,
70+
credential=account_key, **blob_service_kwargs
71+
)
72+
73+
self.client = client
74+
self._container = container
75+
self._account_name = account_name
76+
self._account_key = account_key
77+
78+
def _warn_deprecated(self, property_):
79+
msg = ("The {} property is deprecated and will be removed in a future "
80+
"version. Get the property from 'ABSStore.client' instead.")
81+
warnings.warn(msg.format(property_), FutureWarning, stacklevel=3)
82+
83+
@property
84+
def container(self):
85+
self._warn_deprecated("container")
86+
return self._container
87+
88+
@property
89+
def account_name(self):
90+
self._warn_deprecated("account_name")
91+
return self._account_name
92+
93+
@property
94+
def account_key(self):
95+
self._warn_deprecated("account_key")
96+
return self._account_key
97+
98+
def _append_path_to_prefix(self, path):
99+
if self.prefix == '':
100+
return normalize_storage_path(path)
101+
else:
102+
return '/'.join([self.prefix, normalize_storage_path(path)])
103+
104+
@staticmethod
105+
def _strip_prefix_from_path(path, prefix):
106+
# normalized things will not have any leading or trailing slashes
107+
path_norm = normalize_storage_path(path)
108+
prefix_norm = normalize_storage_path(prefix)
109+
if prefix:
110+
return path_norm[(len(prefix_norm)+1):]
111+
else:
112+
return path_norm
113+
114+
def __getitem__(self, key):
115+
from azure.core.exceptions import ResourceNotFoundError
116+
blob_name = self._append_path_to_prefix(key)
117+
try:
118+
return self.client.download_blob(blob_name).readall()
119+
except ResourceNotFoundError:
120+
raise KeyError('Blob %s not found' % blob_name)
121+
122+
def __setitem__(self, key, value):
123+
value = ensure_bytes(value)
124+
blob_name = self._append_path_to_prefix(key)
125+
self.client.upload_blob(blob_name, value, overwrite=True)
126+
127+
def __delitem__(self, key):
128+
from azure.core.exceptions import ResourceNotFoundError
129+
try:
130+
self.client.delete_blob(self._append_path_to_prefix(key))
131+
except ResourceNotFoundError:
132+
raise KeyError('Blob %s not found' % key)
133+
134+
def __eq__(self, other):
135+
return (
136+
isinstance(other, ABSStore) and
137+
self.client == other.client and
138+
self.prefix == other.prefix
139+
)
140+
141+
def keys(self):
142+
return list(self.__iter__())
143+
144+
def __iter__(self):
145+
if self.prefix:
146+
list_blobs_prefix = self.prefix + '/'
147+
else:
148+
list_blobs_prefix = None
149+
for blob in self.client.list_blobs(list_blobs_prefix):
150+
yield self._strip_prefix_from_path(blob.name, self.prefix)
151+
152+
def __len__(self):
153+
return len(self.keys())
154+
155+
def __contains__(self, key):
156+
blob_name = self._append_path_to_prefix(key)
157+
return self.client.get_blob_client(blob_name).exists()
158+
159+
def listdir(self, path=None):
160+
dir_path = normalize_storage_path(self._append_path_to_prefix(path))
161+
if dir_path:
162+
dir_path += '/'
163+
items = [
164+
self._strip_prefix_from_path(blob.name, dir_path)
165+
for blob in self.client.walk_blobs(name_starts_with=dir_path, delimiter='/')
166+
]
167+
return items
168+
169+
def rmdir(self, path=None):
170+
dir_path = normalize_storage_path(self._append_path_to_prefix(path))
171+
if dir_path:
172+
dir_path += '/'
173+
for blob in self.client.list_blobs(name_starts_with=dir_path):
174+
self.client.delete_blob(blob)
175+
176+
def getsize(self, path=None):
177+
store_path = normalize_storage_path(path)
178+
fs_path = self._append_path_to_prefix(store_path)
179+
if fs_path:
180+
blob_client = self.client.get_blob_client(fs_path)
181+
else:
182+
blob_client = None
183+
184+
if blob_client and blob_client.exists():
185+
return blob_client.get_blob_properties().size
186+
else:
187+
size = 0
188+
if fs_path == '':
189+
fs_path = None
190+
elif not fs_path.endswith('/'):
191+
fs_path += '/'
192+
for blob in self.client.walk_blobs(name_starts_with=fs_path, delimiter='/'):
193+
blob_client = self.client.get_blob_client(blob)
194+
if blob_client.exists():
195+
size += blob_client.get_blob_properties().size
196+
return size
197+
198+
def clear(self):
199+
self.rmdir()

0 commit comments

Comments
 (0)