Skip to content

Commit 29a8697

Browse files
authored
Merge pull request #288 from friedrichknuth/abs_store
Add ABSStore
2 parents dc90a20 + 7b52e39 commit 29a8697

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

zarr/storage.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,3 +1883,156 @@ def __delitem__(self, key):
18831883
with self._mutex:
18841884
self._invalidate_keys()
18851885
self._invalidate_value(key)
1886+
1887+
1888+
class ABSStore(MutableMapping):
1889+
"""Storage class using Azure Blob Storage (ABS).
1890+
1891+
Parameters
1892+
----------
1893+
container_name : string
1894+
The name of the ABS container to use. Currently this must exist in the
1895+
storage account.
1896+
prefix : string
1897+
Location of the "directory" to use as the root of the storage hierarchy
1898+
within the container.
1899+
account_name : string
1900+
The Azure blob storage account name.
1901+
account_key : string
1902+
The Azure blob storage account acess key.
1903+
1904+
Notes
1905+
-----
1906+
In order to use this store, you must install the Azure Blob Storage
1907+
`Python Client Library <https://github.com/Azure/azure-storage-python/tree/master/azure-storage-blob>`_ version >= 1.3.0.
1908+
"""
1909+
1910+
def __init__(self, container_name, prefix, account_name, account_key):
1911+
self.account_name = account_name
1912+
self.account_key = account_key
1913+
self.container_name = container_name
1914+
self.prefix = normalize_storage_path(prefix)
1915+
self.initialize_container()
1916+
1917+
def initialize_container(self):
1918+
from azure.storage.blob import BlockBlobService
1919+
self.client = BlockBlobService(self.account_name, self.account_key)
1920+
1921+
# needed for pickling
1922+
def __getstate__(self):
1923+
state = self.__dict__.copy()
1924+
return state
1925+
1926+
def __setstate__(self, state):
1927+
self.__dict__.update(state)
1928+
self.initialize_container()
1929+
1930+
def __enter__(self):
1931+
return self
1932+
1933+
def __exit__(self, *args):
1934+
pass
1935+
1936+
def _append_path_to_prefix(path, prefix):
1937+
return '/'.join([normalize_storage_path(prefix),
1938+
normalize_storage_path(path)])
1939+
1940+
def full_path(self, path=None):
1941+
return _append_path_to_prefix(path, self.prefix)
1942+
1943+
def __getitem__(self, key):
1944+
blob_name = '/'.join([self.prefix, key])
1945+
blob = self.client.get_blob_to_bytes(self.container_name, blob_name)
1946+
if blob:
1947+
return blob.content
1948+
else:
1949+
raise KeyError('Blob %s not found' % blob_name)
1950+
1951+
def __setitem__(self, key, value):
1952+
blob_name = '/'.join([self.prefix, key])
1953+
self.client.create_blob_from_text(self.container_name, blob_name, value)
1954+
1955+
def __delitem__(self, key):
1956+
raise NotImplementedError
1957+
1958+
def __eq__(self, other):
1959+
return (
1960+
isinstance(other, ABSStore) and
1961+
self.container_name == other.container_name and
1962+
self.prefix == other.prefix
1963+
)
1964+
1965+
def keys(self):
1966+
raise NotImplementedError
1967+
1968+
def __iter__(self):
1969+
raise NotImplementedError
1970+
1971+
def __len__(self):
1972+
raise NotImplementedError
1973+
1974+
def __contains__(self, key):
1975+
blob_name = '/'.join([self.prefix, key])
1976+
if self.client.exists(self.container_name, blob_name):
1977+
return True
1978+
else:
1979+
return False
1980+
1981+
def list_abs_directory_blobs(self, prefix):
1982+
"""Return list of all blobs from an abs prefix."""
1983+
return [blob.name for blob in self.client.list_blobs(self.container_name)]
1984+
1985+
def list_abs_subdirectories(self, prefix):
1986+
"""Return list of all "subdirectories" from an abs prefix."""
1987+
return list(set([blob.name.rsplit('/', 1)[0] for blob in self.client.list_blobs(self.container_name) if '/' in blob.name]))
1988+
1989+
def _strip_prefix_from_path(path, prefix):
1990+
# normalized things will not have any leading or trailing slashes
1991+
path_norm = normalize_storage_path(path)
1992+
prefix_norm = normalize_storage_path(prefix)
1993+
if path_norm.startswith(prefix_norm):
1994+
return path_norm[(len(prefix_norm)+1):]
1995+
else:
1996+
return path
1997+
1998+
def list_abs_directory(self, prefix, strip_prefix=True):
1999+
"""Return a list of all blobs and subdirectories from an abs prefix."""
2000+
items = set()
2001+
items.update(self.list_abs_directory_blobs(prefix))
2002+
items.update(self.list_abs_subdirectories(prefix))
2003+
items = list(items)
2004+
if strip_prefix:
2005+
items = [_strip_prefix_from_path(path, prefix) for path in items]
2006+
return items
2007+
2008+
def dir_path(self, path=None):
2009+
store_path = normalize_storage_path(path)
2010+
# prefix is normalized to not have a trailing slash
2011+
dir_path = self.prefix
2012+
if store_path:
2013+
dir_path = os.path.join(dir_path, store_path)
2014+
else:
2015+
dir_path += '/'
2016+
return dir_path
2017+
2018+
def listdir(self, path=None):
2019+
dir_path = self.dir_path(path)
2020+
return sorted(self.list_abs_directory(dir_path, strip_prefix=True))
2021+
2022+
def rename(self, src_path, dst_path):
2023+
raise NotImplementedErrror
2024+
2025+
def rmdir(self, path=None):
2026+
dir_path = normalize_storage_path(self.full_path(path)) + '/'
2027+
for blob in self.client.list_blobs(self.container_name, prefix=dir_path):
2028+
self.client.delete_blob(self.container_name, blob.name)
2029+
2030+
def getsize(self, path=None):
2031+
dir_path = self.dir_path(path)
2032+
size = 0
2033+
for blob in self.client.list_blobs(self.container_name, prefix=dir_path):
2034+
size += blob.properties.content_length
2035+
return size
2036+
2037+
def clear(self):
2038+
raise NotImplementedError

0 commit comments

Comments
 (0)