예제 #1
0
파일: base.py 프로젝트: tyerq/cloudstorage
    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
예제 #2
0
파일: base.py 프로젝트: tyerq/cloudstorage
    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
예제 #3
0
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)
예제 #4
0
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))
예제 #5
0
파일: base.py 프로젝트: tyerq/cloudstorage
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