def __init__( self, name: str, checksum: str, etag: str, size: int, container: "Container", driver: "Driver", acl: Acl = None, meta_data: MetaData = None, content_disposition: str = None, content_type: str = None, cache_control: str = None, created_at: datetime = None, modified_at: datetime = None, expires_at: datetime = None, ) -> None: if meta_data is None: meta_data = CaseInsensitiveDict() else: meta_data = CaseInsensitiveDict(meta_data) 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.cache_control = cache_control self.created_at = created_at self.modified_at = modified_at self.expires_at = expires_at self._attr = CaseInsensitiveDict() # type: CaseInsensitiveDict self._meta_data = CaseInsensitiveDict() # type: CaseInsensitiveDict self._acl = None # Track attributes for blob update (PUT request) track_params = CaseInsensitiveDict({ "name": name, "meta_data": meta_data, "acl": acl, "content_disposition": content_disposition, "content_type": content_type, "cache_control": cache_control, "expires_at": expires_at, }) for key, value in track_params.items(): if key == "meta_data": self._meta_data = value elif key == "acl": self._acl = value else: self._attr[key] = value
def __init__( self, name: str, driver: "Driver", acl: str = None, meta_data: MetaData = None, created_at: datetime = None, ) -> None: if meta_data is None: meta_data = CaseInsensitiveDict() else: meta_data = CaseInsensitiveDict(meta_data) 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 = CaseInsensitiveDict() self._acl = acl # type: Optional[str] self._meta_data = CaseInsensitiveDict() # Track attributes for container update (PUT request) track_params = CaseInsensitiveDict({ "name": name, "meta_data": meta_data, "acl": acl }) for key, value in track_params.items(): if key == "meta_data": self._meta_data = value elif key == "acl": self._acl = value else: self._attr[key] = value
TEXT_FORM_FILENAME = "flask-form.txt" TEXT_MD5_CHECKSUM = "5a9b3669e3a17311e9135fe65e0877a8" BINARY_FILENAME = "avatar.png" BINARY_FORM_FILENAME = "avatar-form.png" BINARY_STREAM_FILENAME = "avatar-stream.png" BINARY_MD5_CHECKSUM = "2f907a59924ad96b7478074ed96b05f0" # Azure: Does not support dashes. # Rackspace: Converts underscores to dashes. # Minio: Capitalizes the key. BINARY_OPTIONS = CaseInsensitiveDict({ "meta_data": { "ownerid": "da17c32d-21c2-4bfe-b083-e2e78187d868", "owneremail": "*****@*****.**", }, "content_type": "image/png", "content_disposition": "attachment; filename=avatar-attachment.png", "cache_control": "max-age=84600", }) AMAZON_KEY = config("AMAZON_KEY", default=None) AMAZON_SECRET = config("AMAZON_SECRET", default=None) AMAZON_REGION = config("AMAZON_REGION", default="us-east-1") DIGITALOCEAN_KEY = config("DIGITALOCEAN_KEY", default=None) DIGITALOCEAN_SECRET = config("DIGITALOCEAN_SECRET", default=None) DIGITALOCEAN_REGION = config("DIGITALOCEAN_REGION", default="sfo2") AZURE_ACCOUNT_NAME = config("AZURE_ACCOUNT_NAME", default=None) AZURE_ACCOUNT_KEY = config("AZURE_ACCOUNT_KEY", default=None)
TEXT_FORM_FILENAME = 'flask-form.txt' TEXT_MD5_CHECKSUM = '2a5a634f5c8d931350e83e41c9b3b0bb' BINARY_FILENAME = 'avatar.png' BINARY_FORM_FILENAME = 'avatar-form.png' BINARY_STREAM_FILENAME = 'avatar-stream.png' BINARY_MD5_CHECKSUM = '2f907a59924ad96b7478074ed96b05f0' # Azure: Does not support dashes. # Rackspace: Converts underscores to dashes. # Minio: Capitalizes the key. BINARY_OPTIONS = CaseInsensitiveDict({ 'meta_data': { 'ownerid': 'da17c32d-21c2-4bfe-b083-e2e78187d868', 'owneremail': '*****@*****.**' }, 'content_type': 'image/png', 'content_disposition': 'attachment; filename=avatar-attachment.png', 'cache_control': 'max-age=84600', }) AMAZON_KEY = config('AMAZON_KEY', default=None) AMAZON_SECRET = config('AMAZON_SECRET', default=None) AMAZON_REGION = config('AMAZON_REGION', default='us-east-1') AZURE_ACCOUNT_NAME = config('AZURE_ACCOUNT_NAME', default=None) AZURE_ACCOUNT_KEY = config('AZURE_ACCOUNT_KEY', default=None) GOOGLE_CREDENTIALS = config('GOOGLE_CREDENTIALS', default=None, cast=lambda path: os.path.abspath(path))
class Driver(metaclass=abc.ABCMeta): """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: (optional) API key, username, credentials file, or local directory. :type key: str or None :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 # type: str #: :mod:`hashlib` function `str` name used by driver. hash_type = "md5" # type: str #: Unique `str` driver URL. url = None # type: Optional[str] def __init__(self, key: str = None, secret: str = None, region: str = None, **kwargs: Dict) -> None: self.key = key self.secret = secret self.region = region 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: cloudstorage.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 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 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 @abstractmethod def validate_credentials(self) -> None: """Validate driver credentials (key and secret). :return: None :rtype: None :raises CredentialsError: If driver authentication fails. """ 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 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) * container-public-access (Microsoft Azure Storage) * blob-public-access (Microsoft Azure 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 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 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 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 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 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(messages.FEATURE_NOT_SUPPORTED, "enable_container_cdn") return False @abstractmethod 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(messages.FEATURE_NOT_SUPPORTED, "disable_container_cdn") return False @abstractmethod def upload_blob( self, container: "Container", filename: FileLike, blob_name: str = None, acl: str = None, meta_data: MetaData = None, content_type: str = None, content_disposition: str = None, cache_control: str = None, chunk_size=1024, 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 cache_control: (optional) Specify directives for caching mechanisms for the blob. :type cache_control: str or None :param chunk_size: (optional) Optional chunk size for streaming a transfer. :type chunk_size: int :param extra: (optional) Extra parameters for the request. :type extra: Dict[str, str] or None :return: The uploaded blob. :rtype: Blob """ pass @abstractmethod 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 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 def download_blob(self, blob: "Blob", destination: 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 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 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 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 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, cache_control: 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[Any, Any] 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 cache_control: (optional) Specify directives for caching mechanisms for the blob. :type cache_control: str or None :param extra: (optional) Extra parameters for the request. :type extra: Dict[Any, Any] or None :return: Dictionary with URL and form fields (includes signature or policy) or header fields. :rtype: Dict[Any, Any] """ pass @abstractmethod 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[Any, Any] 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) return "<Driver: %s>" % self.name _POST_OBJECT_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict _GET_OBJECT_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict _PUT_OBJECT_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict _DELETE_OBJECT_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict _POST_CONTAINER_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict _GET_CONTAINER_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict _PUT_CONTAINER_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict _DELETE_CONTAINER_KEYS = CaseInsensitiveDict() # type: CaseInsensitiveDict