"""Provides base classes for working with storage drivers."""
import logging
from abc import abstractmethod
from datetime import datetime
from io import BytesIO, FileIO, TextIOWrapper
from typing import Dict, IO, Iterable, List, Union
from cloudstorage.exceptions import NotFoundError
from cloudstorage.messages import *
__all__ = [
'Blob',
'Container',
'Driver',
]
logger = logging.getLogger(__name__)
# TODO: QUESTIONS: Move to typing_.py module?
FileLike = Union[IO[BytesIO], BytesIO, FileIO, TextIOWrapper]
MetaData = Dict[str, str]
ContentLength = Dict[int, int]
ExtraOptions = Dict[str, str]
FormPost = Dict[str, Dict[str, str]]
class DocstringMeta(type):
"""Metaclass that allows docstring 'inheritance'.
Dirty hack for sphinx...
Source: `Use method docstring from abstract base class if derived class
has none #3140 <https://github.com/sphinx-doc/sphinx/issues/3140>`_
"""
# noinspection PyInitNewSignature
# noinspection PyMethodParameters
def __new__(mcls, name, bases, namespace):
cls = super().__new__(mcls, name, bases, namespace)
mro = cls.__mro__[1:]
for name, member in namespace.items():
if not getattr(member, '__doc__'):
for base in mro:
try:
member.__doc__ = getattr(base, name).__doc__
break
except AttributeError:
pass
return cls
[docs]class Blob:
"""Represents an object blob.
.. code-block:: python
picture_blob = container.get_blob('picture.png')
picture_blob.size
# 50301
picture_blob.checksum
# '2f907a59924ad96b7478074ed96b05f0'
picture_blob.content_type
# 'image/png'
picture_blob.content_disposition
# 'attachment; filename=picture-attachment.png'
:param name: Blob name (must be unique in container).
:type name: str
:param checksum: Checksum of this blob.
:type checksum: str
:param etag: Blob etag which can also be the checksum. The etag for
`LocalDriver` is a SHA1 hexdigest of the blob's full path.
:type etag: str
:param size: Blob size in bytes.
:type size: int
:param container: Reference to the blob's container.
:type container: Container
:param driver: Reference to the blob's container's driver.
:type driver: Driver
:param meta_data: (optional) Metadata stored with the blob.
:type meta_data: Dict[str, str] or None
:param acl: (optional) Access control list (ACL) for this blob.
:type acl: dict or None
:param content_disposition: (optional) Specifies presentational
information for this blob.
:type content_disposition: str or None
:param content_type: (optional) A standard MIME type describing the format
of the object data.
:type content_type: str or None
:param created_at: (optional) Creation time of this blob.
:type created_at: datetime.datetime or None
:param modified_at: (optional) Last modified time of this blob.
:type modified_at: datetime.datetime or None
:param expires_at: (optional) Deletion or expiration time for this blob.
:type expires_at: datetime.datetime or None
"""
def __init__(self, name: str, checksum: str, etag: str, size: int,
container: 'Container', driver: 'Driver', acl: str = None,
meta_data: MetaData = None, content_disposition: str = None,
content_type: str = None, created_at: datetime = None,
modified_at: datetime = None,
expires_at: datetime = None) -> None:
# acl = acl if acl is not None else {}
meta_data = meta_data if meta_data is not None else {}
self.name = name
self.size = size
self.checksum = checksum
self.etag = etag
self.container = container
self.driver = driver
# self.acl = acl
self.meta_data = meta_data
self.content_disposition = content_disposition
self.content_type = content_type
self.created_at = created_at
self.modified_at = modified_at
self.expires_at = expires_at
self._attr = {}
self._acl = {}
self._meta_data = {}
# Track attributes for object PUT
for key, value in locals().items():
if key == 'meta_data':
self._meta_data = value
elif key == 'acl':
self._acl = value
else:
self._attr[key] = value
def __eq__(self, other: 'Blob') -> bool:
"""Override the default equals behavior.
:param other: The other Blob.
:type other: Blob
:return: True if the Blobs are the same.
:rtype: bool
"""
return (isinstance(other, self.__class__) and
self.checksum == other.checksum)
def __hash__(self) -> hash:
"""Override the default hash behavior.
:return: Hash.
:rtype: hash
"""
# TODO: QUESTION: Include extra attributes like self.name?
return hash(self.checksum)
[docs] def __len__(self) -> int:
"""The blob size in bytes.
:return: bytes
:rtype: int
"""
return self.size
def __ne__(self, other: 'Blob') -> bool:
"""Override the default not equals behavior.
:param other: The other blob.
:type other: Blob
:return: True if the containers are not the same.
:rtype: bool
"""
return self.checksum != other.checksum
@property
def cdn_url(self) -> str:
"""The Content Delivery Network URL for this blob.
`https://container-name.storage.com/picture.png`
:return: The CDN URL for this blob.
:rtype: str
"""
return self.driver.blob_cdn_url(blob=self)
@property
def path(self) -> str:
"""Relative URL path for this blob.
`container-name/picture.png`
:return: The relative URL path to this blob.
:rtype: str
"""
return '%s/%s' % (self.container.name, self.name)
[docs] def delete(self) -> None:
"""Delete this blob from the container.
.. code-block:: python
picture_blob = container.get_blob('picture.png')
picture_blob.delete()
picture_blob in container
# False
:return: NoneType
:rtype: None
:raises NotFoundError: If the blob object doesn't exist.
"""
self.driver.delete_blob(blob=self)
[docs] def download(self, destination: Union[str, FileLike]) -> None:
"""Download the contents of this blob into a file-like object or into
a named file.
Filename:
.. code-block:: python
picture_blob = container.get_blob('picture.png')
picture_blob.download('/path/picture-copy.png')
File object:
.. IMPORTANT:: Always use write binary mode `wb` when downloading a
blob to a file object.
.. code-block:: python
picture_blob = container.get_blob('picture.png')
with open('/path/picture-copy.png', 'wb') as picture_file:
picture_blob.download(picture_file)
:param destination: A file handle to which to write the blob’s data or
a filename to be passed to `open`.
:type destination: file or str
:return: NoneType
:rtype: None
:raises NotFoundError: If the blob object doesn't exist.
"""
self.driver.download_blob(self, destination)
[docs] def generate_download_url(self, expires: int = 3600, method: str = 'GET',
content_disposition: str = None,
extra: ExtraOptions = None) -> str:
"""Generates a signed URL for this blob.
If you have a blob that you want to allow access to for a set amount of
time, you can use this method to generate a URL that is only valid
within a certain time period. This is particularly useful if you don’t
want publicly accessible blobs, but don’t want to require users to
explicitly log in. [#f1]_
.. [#f1] `Blobs / Objects — google-cloud 0.24.0 documentation
<https://googlecloudplatform.github.io/google-cloud-python/
stable/storage-blobs.html>`_
Basic example:
.. code-block:: python
import requests
picture_blob = container.get_blob('picture.png')
download_url = picture_blob.download_url(expires=3600)
response = requests.get(download_url)
# <Response [200]>
with open('/path/picture-download.png', 'wb') as picture_file:
for chunk in response.iter_content(chunk_size=128):
picture_file.write(chunk)
Response Content-Disposition example:
.. code-block:: python
picture_blob = container.get_blob('picture.png')
params = {
'expires': 3600,
'content_disposition': 'attachment; filename=attachment.png'
}
download_url = picture_blob.download_url(**params)
response = requests.get(download_url)
# <Response [200]>
response.headers['content-disposition']
# attachment; filename=attachment.png
References:
* `Boto 3: S3.Client.generate_presigned_url
<http://boto3.readthedocs.io/en/latest/reference/services/s3.html#
S3.Client.generate_presigned_url>`_
* `Google Cloud Storage: generate_signed_url
<https://googlecloudplatform.github.io/google-cloud-python/stable/
storage-blobs.html>`_
* `Rackspace: TempURL
<https://developer.rackspace.com/docs/cloud-files/v1/use-cases/
public-access-to-your-cloud-files-account/#tempurl>`_
:param expires: (optional) Expiration in seconds.
:type expires: int
:param method: (optional) HTTP request method. Defaults to `GET`.
:type method: str
:param content_disposition: (optional) Sets the Content-Disposition
header of the response.
:type content_disposition: str or None
:param extra: (optional) Extra parameters for the request.
* All
* **content_type** *(str) --* Sets the Content-Type
header of the response.
* Google Cloud Storage
* **version** *(str) --* A value that indicates which
generation of the resource to fetch.
* Amazon S3
* **version_id** *(str) --* Version of the object.
:type extra: Dict[str, str] or None
:return: Pre-signed URL for downloading a blob. :class:`.LocalDriver`
returns urlsafe signature.
:rtype: str
"""
return self.driver.generate_blob_download_url(
blob=self,
expires=expires,
method=method,
content_disposition=content_disposition,
extra=extra)
[docs] def patch(self) -> None:
"""Saves all changed attributes for this blob.
.. warning:: Not supported by all drivers yet.
:return: NoneType
:rtype: None
:raises NotFoundError: If the blob object doesn't exist.
"""
self.driver.patch_blob(blob=self)
def __repr__(self):
return '<Blob %s %s %s>' % (
self.name, self.container.name, self.driver.name)
[docs]class Container:
"""Represents a container (bucket or folder) which contains blobs.
.. code-block:: python
container = storage.get_container('container-name')
container.name
# container-name
container.created_at
# 2017-04-11 08:58:12-04:00
len(container)
# 20
.. todo:: Add option to delete blobs before deleting the container.
.. todo:: Support extra headers like Content-Encoding.
:param name: Container name (must be unique).
:type name: str
:param driver: Reference to this container's driver.
:type driver: Driver
:param acl: (optional) Container's canned Access Control List (ACL).
If `None`, defaults to storage backend default.
* private
* public-read
* public-read-write
* authenticated-read
* bucket-owner-read
* bucket-owner-full-control
* aws-exec-read (Amazon S3)
* project-private (Google Cloud Storage)
:type acl: str or None
:param meta_data: (optional) Metadata stored with this container.
:type meta_data: Dict[str, str] or None
:param created_at: Creation time of this container.
:type created_at: datetime.datetime or None
"""
def __init__(self, name: str, driver: 'Driver', acl: str = None,
meta_data: MetaData = None,
created_at: datetime = None) -> None:
meta_data = meta_data if meta_data is not None else {}
self.name = name
self.driver = driver
# TODO: FEATURE: Support normalized ACL view.
self.acl = acl
self.meta_data = meta_data
self.created_at = created_at
self._attr = {}
self._acl = {}
self._meta_data = {}
# Track attributes for object PUT
for key, value in locals().items():
if key == 'meta_data':
self._meta_data = value
elif key == 'acl':
self._acl = value
else:
self._attr[key] = value
[docs] def __contains__(self, blob: Blob) -> bool:
"""Determines whether or not the blob exists in this container.
.. code-block:: python
container = storage.get_container('container-name')
picture_blob = container.get_blob('picture.png')
picture_blob in container
# True
'picture.png' in container
# True
:param blob: Blob or Blob name.
:type blob: str or Blob
:return: True if the blob exists.
:rtype: bool
"""
blob_name = blob.name if hasattr(blob, 'name') else blob
try:
self.driver.get_blob(container=self, blob_name=blob_name)
return True
except NotFoundError:
return False
def __eq__(self, other: Blob) -> bool:
"""Override the default equals behavior.
:param other: The other container.
:type other: Container
:return: True if the containers are the same.
:rtype: bool
"""
return (isinstance(other, self.__class__)
and self.name == other.name
and self.driver.name == other.driver.name)
def __hash__(self) -> hash:
"""Override the default hash behavior.
:return: Hash.
:rtype: hash
"""
return hash(self.name) ^ hash(self.driver.name)
[docs] def __iter__(self) -> Iterable[Blob]:
"""Get all blobs associated to the container.
.. code-block:: python
container = storage.get_container('container-name')
for blob in container:
blob.name
# blob-1.ext, blob-2.ext
:return: Iterable of all blobs belonging to this container.
:rtype: Iterable{Blob]
"""
return self.driver.get_blobs(container=self)
[docs] def __len__(self) -> int:
"""Total number of blobs in this container.
:return: Blob count in this container.
:rtype: int
"""
blobs = self.driver.get_blobs(container=self)
return len(list(blobs))
def __ne__(self, other: Blob) -> bool:
"""Override the default not equals behavior.
:param other: The other container.
:type other: Container
:return: True if the containers are not the same.
:rtype: bool
"""
return not self.__eq__(other)
@property
def cdn_url(self) -> str:
"""The Content Delivery Network URL for this container.
`https://container-name.storage.com/`
:return: The CDN URL for this container.
:rtype: str
"""
return self.driver.container_cdn_url(container=self)
[docs] def patch(self) -> None:
"""Saves all changed attributes for this container.
.. warning:: Not supported by all drivers yet.
:return: NoneType
:rtype: None
:raises NotFoundError: If the container doesn't exist.
"""
self.driver.patch_container(container=self)
[docs] def delete(self) -> None:
"""Delete this container.
.. important:: All blob objects in the container must be deleted before
the container itself can be deleted.
.. code-block:: python
container = storage.get_container('container-name')
container.delete()
container in storage
# False
:return: NoneType
:rtype: None
:raises IsNotEmptyError: If the container is not empty.
:raises NotFoundError: If the container doesn't exist.
"""
self.driver.delete_container(container=self)
[docs] def upload_blob(self, filename: Union[str, FileLike], blob_name: str = None,
acl: str = None, meta_data: MetaData = None,
content_type: str = None, content_disposition: str = None,
extra: ExtraOptions = None) -> Blob:
"""Upload a filename or file like object to a container.
If `content_type` is `None`, Cloud Storage will attempt to guess the
standard MIME type using the packages: `python-magic` or `mimetypes`. If
that fails, Cloud Storage will leave it up to the storage backend to
guess it.
.. warning:: The effect of uploading to an existing blob depends on the
“versioning” and “lifecycle” policies defined on the blob’s
container. In the absence of those policies, upload will
overwrite any existing contents.
Basic example:
.. code-block:: python
container = storage.get_container('container-name')
picture_blob = container.upload_blob('/path/picture.png')
# <Blob picture.png container-name S3>
Set Content-Type example:
.. code-block:: python
container = storage.get_container('container-name')
with open('/path/resume.doc', 'rb') as resume_file:
resume_blob = container.upload_blob(resume_file,
content_type='application/msword')
resume_blob.content_type
# 'application/msword'
Set Metadata and ACL:
.. code-block:: python
picture_file = open('/path/picture.png', 'rb)
'acl': 'public-read',
meta_data = {
'owner-email': 'user.one@startup.com',
'owner-id': '1'
}
container = storage.get_container('container-name')
picture_blob = container.upload_blob(picture_file,
acl='public-read', meta_data=meta_data)
picture_blob.meta_data
# {owner-id': '1', 'owner-email': 'user.one@startup.com'}
References:
* `Boto 3: PUT Object
<http://boto3.readthedocs.io/en/latest/reference/services/s3.html#
S3.Client.put_object>`_
* `Google Cloud Storage: upload_from_file / upload_from_filename
<https://googlecloudplatform.github.io/google-cloud-python/stable/
storage-blobs.html>`_
* `Rackspace Cloud Files: Create or update object
<https://developer.rackspace.com/docs/cloud-files/v1/
storage-api-reference/object-services-operations/
#create-or-update-object>`_
:param filename: A file handle open for reading or the path to the file.
:type filename: file or str
:param acl: (optional) Blob canned Access Control List (ACL).
If `None`, defaults to storage backend default.
* private
* public-read
* public-read-write
* authenticated-read
* bucket-owner-read
* bucket-owner-full-control
* aws-exec-read (Amazon S3)
* project-private (Google Cloud Storage)
:type acl: str or None
:param blob_name: (optional) Override the blob's name. If not set, will
default to the filename from path or filename of
iterator object.
:type blob_name: str or None
:param meta_data: (optional) A map of metadata to store with the blob.
:type meta_data: Dict[str, str] or None
:param content_type: (optional) A standard MIME type describing the
format of the object data.
:type content_type: str or None
:param content_disposition: (optional) Specifies presentational
information for the blob.
:type content_disposition: str or None
:param extra: (optional) Extra parameters for the request.
:type extra: Dict[str, str] or None
:return: The uploaded blob.
:rtype: Blob
"""
return self.driver.upload_blob(container=self, filename=filename,
blob_name=blob_name, acl=acl,
meta_data=meta_data,
content_type=content_type,
content_disposition=content_disposition,
extra=extra)
[docs] def get_blob(self, blob_name: str) -> Blob:
"""Get a blob object by name.
.. code-block:: python
container = storage.get_container('container-name')
picture_blob = container.get_blob('picture.png')
# <Blob picture.png container-name S3>
:param blob_name: The name of the blob to retrieve.
:type blob_name: str
:return: The blob object if it exists.
:rtype: Blob
:raise NotFoundError: If the blob object doesn't exist.
"""
return self.driver.get_blob(container=self, blob_name=blob_name)
[docs] def generate_upload_url(self, blob_name: str, expires: int = 3600,
acl: str = None, meta_data: MetaData = None,
content_disposition: str = None,
content_length: ContentLength = None,
content_type: str = None,
extra: ExtraOptions = None) -> FormPost:
"""Generate a signature and policy for uploading objects to this
container.
This method gives your website a way to upload objects to a container
through a web form without giving the user direct write access.
Basic example:
.. code-block:: python
import requests
picture_file = open('/path/picture.png', 'rb')
container = storage.get_container('container-name')
form_post = container.generate_upload_url('avatar-user-1.png')
url = form_post['url']
fields = form_post['fields']
multipart_form_data = {
'file': ('avatar.png', picture_file, 'image/png'),
}
resp = requests.post(url, data=fields, files=multipart_form_data)
# <Response [201]> or <Response [204]>
avatar_blob = container.get_blob('avatar-user-1.png')
# <Blob avatar-user-1.png container-name S3>
Form example:
.. code-block:: python
container = storage.get_container('container-name')
form_post = container.generate_upload_url('avatar-user-1.png')
# Generate an upload form using the form fields and url
fields = [
'<input type="hidden" name="{name}" value="{value}" />'.format(
name=name, value=value)
for name, value in form_post['fields'].items()
]
upload_form = [
'<form action="{url}" method="post" '
'enctype="multipart/form-data">'.format(
url=form_post['url']),
*fields,
'<input name="file" type="file" />',
'<input type="submit" value="Upload" />',
'</form>',
]
print('\\n'.join(upload_form))
.. code-block:: html
<!--Google Cloud Storage Generated Form-->
<form action="https://container-name.storage.googleapis.com"
method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="avatar-user-1.png" />
<input type="hidden" name="bucket" value="container-name" />
<input type="hidden" name="GoogleAccessId" value="<my-access-id>" />
<input type="hidden" name="policy" value="<generated-policy>" />
<input type="hidden" name="signature" value="<generated-sig>" />
<input name="file" type="file" />
<input type="submit" value="Upload" />
</form>
Content-Disposition and Metadata example:
.. code-block:: python
import requests
params = {
'blob_name': 'avatar-user-1.png',
'meta_data': {
'owner-id': '1',
'owner-email': 'user.one@startup.com'
},
'content_type': 'image/png',
'content_disposition': 'attachment; filename=attachment.png'
}
form_post = container.generate_upload_url(**params)
url = form_post['url']
fields = form_post['fields']
multipart_form_data = {
'file': open('/path/picture.png', 'rb'),
}
resp = requests.post(url, data=fields, files=multipart_form_data)
# <Response [201]> or <Response [204]>
avatar_blob = container.get_blob('avatar-user-1.png')
avatar_blob.content_disposition
# 'attachment; filename=attachment.png'
References:
* `Boto 3: S3.Client.generate_presigned_post
<http://boto3.readthedocs.io/en/latest/reference/services/s3.html#
S3.Client.generate_presigned_post>`_
* `Google Cloud Storage: POST Object
<https://cloud.google.com/storage/docs/xml-api/post-object>`_
* `Rackspace Cloud Files: FormPost
<https://developer.rackspace.com/docs/cloud-files/v1/use-cases/
public-access-to-your-cloud-files-account/#formpost>`_
:param blob_name: The blob's name, prefix, or `''` if a user is
providing a file name. Note, Rackspace Cloud Files
only supports prefixes.
:type blob_name: str or None
:param expires: (optional) Expiration in seconds.
:type expires: int
:param acl: (optional) Container canned Access Control List (ACL).
If `None`, defaults to storage backend default.
* private
* public-read
* public-read-write
* authenticated-read
* bucket-owner-read
* bucket-owner-full-control
* aws-exec-read (Amazon S3)
* project-private (Google Cloud Storage)
:type acl: str or None
:param meta_data: (optional) A map of metadata to store with the blob.
:type meta_data: Dict[str, str] or None
:param content_disposition: (optional) Specifies presentational
information for the blob.
:type content_disposition: str or None
:param content_type: (optional) A standard MIME type describing the
format of the object data.
:type content_type: str or None
:param content_length: Specifies that uploaded files can only be
between a certain size range in bytes:
`(<min>, <max>)`.
:type content_length: tuple[int, int] or None
:param extra: (optional) Extra parameters for the request.
* **success_action_redirect** *(str) --* A URL that users
are redirected to when an upload is successful. If you
do not provide a URL, Cloud Storage responds with the
status code that you specified in
`success_action_status`.
* **success_action_status** *(str) --* The status code
that you want Cloud Storage to respond with when an
upload is successful. The default is `204`.
:type extra: Dict[str, str] or None
:return: Dictionary with URL and form fields (includes signature or
policy).
:rtype: Dict[str, str]
"""
return self.driver.generate_container_upload_url(
container=self,
blob_name=blob_name,
expires=expires, acl=acl,
meta_data=meta_data,
content_disposition=content_disposition,
content_length=content_length,
content_type=content_type,
extra=extra)
[docs] def enable_cdn(self) -> bool:
"""Enable Content Delivery Network (CDN) for this container.
:return: True if successful or false if not supported.
:rtype: bool
"""
return self.driver.enable_container_cdn(container=self)
[docs] def disable_cdn(self) -> bool:
"""Disable Content Delivery Network (CDN) for this container.
:return: True if successful or false if not supported.
:rtype: bool
"""
return self.driver.disable_container_cdn(container=self)
def __repr__(self):
return '<Container %s %s>' % (self.name, self.driver.name)
[docs]class Driver(metaclass=DocstringMeta):
"""Abstract Base Driver Class (:class:`abc.ABCMeta`) to derive from.
.. todo::
* Create driver abstract method to get total number of containers.
* Create driver abstract method to get total number of blobs
in a container.
* Support for ACL permission grants.
* Support for CORS.
* Support for container / blob expiration (delete_at).
:param key: API key, username, credentials file, or local directory.
:type key: str
:param secret: (optional) API secret key.
:type secret: str
:param region: (optional) Region to connect to.
:type region: str
:param kwargs: (optional) Extra options for the driver.
:type kwargs: dict
"""
#: Unique `str` driver name.
name = None
#: :mod:`hashlib` function `str` name used by driver.
hash_type = 'md5'
#: Unique `str` driver URL.
url = None
def __init__(self, key: str, secret: str = None, region: str = None,
**kwargs: Dict) -> None:
self.key = key
self.secret = secret
self.region = region
[docs] def __contains__(self, container) -> bool:
"""Determines whether or not the container exists.
.. code: python
container = storage.get_container('container-name')
container in storage
# True
'container-name' in storage
# True
:param container: Container or container name.
:type container: Container or str
:return: True if the container exists.
:rtype: bool
"""
if hasattr(container, 'name'):
container_name = container.name
else:
container_name = container
try:
self.get_container(container_name=container_name)
return True
except NotFoundError:
return False
@abstractmethod
[docs] def __iter__(self) -> Iterable[Container]:
"""Get all containers associated to the driver.
.. code-block:: python
for container in storage:
print(container.name)
:yield: Iterator of all containers belonging to this driver.
:yield type: Iterable[:class:`.Container`]
"""
pass
@abstractmethod
[docs] def __len__(self) -> int:
"""The total number of containers in the driver.
:return: Number of containers belonging to this driver.
:rtype: int
"""
pass
@staticmethod
@abstractmethod
def _normalize_parameters(params: Dict[str, str],
normalizers: Dict[str, str]) -> Dict[str, str]:
"""Transform parameter key names to match syntax required by the driver.
:param params: Dictionary of parameters for method.
:type params: dict
:param normalizers: Dictionary mapping of key names.
:type normalizers: dict
:return: Dictionary of transformed key names.
::
{
'<key-name>': `<Mapped-Name>`
'meta_data': 'Metadata',
'content_disposition': 'ContentDisposition'
}
:rtype: Dict[str, str]
"""
pass
@property
@abstractmethod
def regions(self) -> List[str]:
"""List of supported regions for this driver.
:return: List of region strings.
:rtype: list[str]
"""
pass
@abstractmethod
[docs] def create_container(self, container_name: str, acl: str = None,
meta_data: MetaData = None) -> Container:
"""Create a new container.
For example:
.. code-block:: python
container = storage.create_container('container-name')
# <Container container-name driver-name>
:param container_name: The container name to create.
:type container_name: str
:param acl: (optional) Container canned Access Control List (ACL).
If `None`, defaults to storage backend default.
* private
* public-read
* public-read-write
* authenticated-read
* bucket-owner-read
* bucket-owner-full-control
* aws-exec-read (Amazon S3)
* project-private (Google Cloud Storage)
:type acl: str or None
:param meta_data: (optional) A map of metadata to store with the
container.
:type meta_data: Dict[str, str] or None
:return: The newly created or existing container.
:rtype: :class:`.Container`
:raises CloudStorageError: If the container name contains invalid
characters.
"""
pass
@abstractmethod
[docs] def get_container(self, container_name: str) -> Container:
"""Get a container by name.
For example:
.. code-block:: python
container = storage.get_container('container-name')
# <Container container-name driver-name>
:param container_name: The name of the container to retrieve.
:type container_name: str
:return: The container if it exists.
:rtype: :class:`.Container`
:raise NotFoundError: If the container doesn't exist.
"""
pass
@abstractmethod
[docs] def patch_container(self, container: Container) -> None:
"""Saves all changed attributes for the container.
.. important:: This class method is called by :meth:`.Container.save`.
:param container: A container instance.
:type container: :class:`.Container`
:return: NoneType
:rtype: None
:raises NotFoundError: If the container doesn't exist.
"""
pass
@abstractmethod
[docs] def delete_container(self, container: Container) -> None:
"""Delete this container.
.. important:: This class method is called by :meth:`.Container.delete`.
:param container: A container instance.
:type container: :class:`.Container`
:return: NoneType
:rtype: None
:raises IsNotEmptyError: If the container is not empty.
:raises NotFoundError: If the container doesn't exist.
"""
pass
@abstractmethod
[docs] def container_cdn_url(self, container: Container) -> str:
"""The Content Delivery Network URL for this container.
.. important:: This class method is called by
:attr:`.Container.cdn_url`.
:return: The CDN URL for this container.
:rtype: str
"""
pass
@abstractmethod
[docs] def enable_container_cdn(self, container: Container) -> bool:
"""(Optional) Enable Content Delivery Network (CDN) for the container.
.. important:: This class method is called by
:meth:`.Container.enable_cdn`.
:param container: A container instance.
:type container: :class:`.Container`
:return: True if successful or false if not supported.
:rtype: bool
"""
logger.warning(feature_not_supported % 'enable_container_cdn')
return False
@abstractmethod
[docs] def disable_container_cdn(self, container: Container) -> bool:
"""(Optional) Disable Content Delivery Network (CDN) on the container.
.. important:: This class method is called by
:meth:`.Container.disable_cdn`.
:param container: A container instance.
:type container: :class:`.Container`
:return: True if successful or false if not supported.
:rtype: bool
"""
logger.warning(feature_not_supported % 'disable_container_cdn')
return False
@abstractmethod
[docs] def upload_blob(self, container: Container, filename: Union[str, FileLike],
blob_name: str = None, acl: str = None,
meta_data: MetaData = None, content_type: str = None,
content_disposition: str = None,
extra: ExtraOptions = None) -> Blob:
"""Upload a filename or file like object to a container.
.. important:: This class method is called by
:meth:`.Container.upload_blob`.
:param container: The container to upload the blob to.
:type container: :class:`.Container`
:param filename: A file handle open for reading or the path to the file.
:type filename: file or str
:param acl: (optional) Blob canned Access Control List (ACL).
:type acl: str or None
:param blob_name: (optional) Override the blob's name. If not set, will
default to the filename from path or filename of
iterator object.
:type blob_name: str or None
:param meta_data: (optional) A map of metadata to store with the blob.
:type meta_data: Dict[str, str] or None
:param content_type: (optional) A standard MIME type describing the
format of the object data.
:type content_type: str or None
:param content_disposition: (optional) Specifies presentational
information for the blob.
:type content_disposition: str or None
:param extra: (optional) Extra parameters for the request.
:type extra: Dict[str, str] or None
:return: The uploaded blob.
:rtype: Blob
"""
pass
@abstractmethod
[docs] def get_blob(self, container: Container, blob_name: str) -> Blob:
"""Get a blob object by name.
.. important:: This class method is called by :meth:`.Blob.get_blob`.
:param container: The container that holds the blob.
:type container: :class:`.Container`
:param blob_name: The name of the blob to retrieve.
:type blob_name: str
:return: The blob object if it exists.
:rtype: Blob
:raise NotFoundError: If the blob object doesn't exist.
"""
pass
@abstractmethod
[docs] def get_blobs(self, container: Container) -> Iterable[Blob]:
"""Get all blobs associated to the container.
.. important:: This class method is called by :meth:`.Blob.__iter__`.
:param container: A container instance.
:type container: :class:`.Container`
:return: Iterable of all blobs belonging to this container.
:rtype: Iterable{Blob]
"""
pass
@abstractmethod
[docs] def download_blob(self, blob: Blob,
destination: Union[str, FileLike]) -> None:
"""Download the contents of this blob into a file-like object or into
a named file.
.. important:: This class method is called by :meth:`.Blob.download`.
:param blob: The blob object to download.
:type blob: Blob
:param destination: A file handle to which to write the blob’s data or
a filename to be passed to `open`.
:type destination: file or str
:return: NoneType
:rtype: None
:raises NotFoundError: If the blob object doesn't exist.
"""
pass
@abstractmethod
[docs] def patch_blob(self, blob: Blob) -> None:
"""Saves all changed attributes for this blob.
.. important:: This class method is called by :meth:`.Blob.update`.
:return: NoneType
:rtype: None
:raises NotFoundError: If the blob object doesn't exist.
"""
pass
@abstractmethod
[docs] def delete_blob(self, blob: Blob) -> None:
"""Deletes a blob from storage.
.. important:: This class method is called by :meth:`.Blob.delete`.
:param blob: The blob to delete.
:type blob: Blob
:return: NoneType
:rtype: None
:raise NotFoundError: If the blob object doesn't exist.
"""
pass
@abstractmethod
[docs] def blob_cdn_url(self, blob: Blob) -> str:
"""The Content Delivery Network URL for the blob.
.. important:: This class method is called by :attr:`.Blob.cdn_url`.
:param blob: The public blob object.
:type blob: Blob
:return: The CDN URL for the blob.
:rtype: str
"""
pass
@abstractmethod
[docs] def generate_container_upload_url(self, container: Container,
blob_name: str,
expires: int = 3600, acl: str = None,
meta_data: MetaData = None,
content_disposition: str = None,
content_length: ContentLength = None,
content_type: str = None,
extra: ExtraOptions = None) -> FormPost:
"""Generate a signature and policy for uploading objects to the
container.
.. important:: This class method is called by
:meth:`.Container.generate_upload_url`.
:param container: A container to upload the blob object to.
:type container: :class:`.Container`
:param blob_name: The blob's name, prefix, or `''` if a user is
providing a file name. Note, Rackspace Cloud Files
only supports prefixes.
:type blob_name: str or None
:param expires: (optional) Expiration in seconds.
:type expires: int
:param acl: (optional) Container canned Access Control List (ACL).
:type acl: str or None
:param meta_data: (optional) A map of metadata to store with the blob.
:type meta_data: Dict[str, str] or None
:param content_disposition: (optional) Specifies presentational
information for the blob.
:type content_disposition: str or None
:param content_type: (optional) A standard MIME type describing the
format of the object data.
:type content_type: str or None
:param content_length: Specifies that uploaded files can only be
between a certain size range in bytes.
:type content_length: tuple[int, int] or None
:param extra: (optional) Extra parameters for the request.
:type extra: Dict[str, str] or None
:return: Dictionary with URL and form fields (includes signature or
policy).
:rtype: Dict[str, str]
"""
pass
@abstractmethod
[docs] def generate_blob_download_url(self, blob: Blob, expires: int = 3600,
method: str = 'GET',
content_disposition: str = None,
extra: ExtraOptions = None) -> str:
"""Generates a signed URL for this blob.
.. important:: This class method is called by
:meth:`.Blob.generate_download_url`.
:param blob: The blob to download with a signed URL.
:type blob: Blob
:param expires: (optional) Expiration in seconds.
:type expires: int
:param method: (optional) HTTP request method. Defaults to `GET`.
:type method: str
:param content_disposition: (optional) Sets the Content-Disposition
header of the response.
:type content_disposition: str or None
:param extra: (optional) Extra parameters for the request.
:type extra: Dict[str, str] or None
:return: Pre-signed URL for downloading a blob.
:rtype: str
"""
pass
def __repr__(self):
if self.region:
return '<Driver: %s %s>' % (self.name, self.region)
else:
return '<Driver: %s>' % self.name
_POST_OBJECT_KEYS = {}
_GET_OBJECT_KEYS = {}
_PUT_OBJECT_KEYS = {}
_DELETE_OBJECT_KEYS = {}
_POST_CONTAINER_KEYS = {}
_GET_CONTAINER_KEYS = {}
_PUT_CONTAINER_KEYS = {}
_DELETE_CONTAINER_KEYS = {}