oss2.api 源代码

# -*- coding: utf-8 -*-

"""
Data parameters for file upload methods
-----------------------------
For example, :func:`put_object <Bucket.put_object>` has the `data` parameter. It can be any one of the following types:
    - unicode type (for Python3 it is str): Internally converted to UTF-8 bytes.
    - bytes types: No conversion
    - file-like object: For seekable and tellable file object, it will read from the current position to the end. Otherwise, make sure the current position is the file start position.
    - Iterator types: The data must be of the iterator type if its length is not predictable. Chunked Encoding is used internally for transfer operations.

The input paramater in bucket config update methods
-----------------------------
For example :func:`put_bucket_cors <Bucket.put_bucket_cors>` has the `input` parameter. It can be any of the following types:
  - Bucket config related class such as `BucketCors`
  - unicode type (For Python3, it is str)
  - UTF-8 encoded bytes
  - file-like object
  - Iterator types, uses `Chunked Encoding` for transfer
Except supporting the `Bucket configuration related class`, the `input` parameter has the same types as the `data` parameters.

Return value 
------
Most methods in :class:`Service` and :class:`Bucket` return :class:`RequestResult <oss2.models.RequestResult>` or its subclasses.
`RequestResult` class defines the HTTP status code, response headers and OSS Request ID. The  subclasses of :class:`RequestResult <oss2.models.RequestResult>` define the specific data that is of interest to users.
For example,
`ListBucketsResult.buckets` returns the Bucket instance list. and `GetObjectResult` returns a file-like object which can call `read()` to get the response body.

Encrypted interface :class:`CryptoBucket` :
-----------------------------
CryptoBucket provides encrypted interfaces only for upload/download data, such as `get_object` and `put_object`. The CryptoBucket return values are the same as for the corresponding Bucket interfaces. 


Exceptions
----
In general, Python SDK can throw up three Exception types, which are inherited from :class:`OssError <oss2.exceptions.OssError>` . 
    - :class:`ClientError <oss2.exceptions.ClientError>`  : Client side exceptions due to the user's incorrect usage parameters. 
    - :class:`ServerError <oss2.exceptions.ServerError>` and its subclasses : Server side exceptions which contain error codes such as 4xx or 5xx.
    - :class:`RequestError <oss2.exceptions.RequestError>`  : The underlying requests lib's exceptions, such as DNS error or timeout. 
Besides these, `Bucket.put_object_from_file` and `Bucket.get_object_to_file` may throw file-related exceptions.


.. _byte_range:

Download range
------------
For example, :func:`get_object <Bucket.get_object>` and :func:`upload_part_copy <Bucket.upload_part_copy>` have the `byte_range` parameter, which specifies the read range.
It is a 2-tuple: (start,last). These methods internally would translate the tuple into the value of Http header Range, such as:
    - For (0, 99), the translated Range header is 'bytes=0-99', which means reading the first 100 bytes.
    - For (None, 99), the translated Range header is 'bytes=-99', which means reading the last 99 bytes
    - For (100, None), the translated Range header is 'bytes=100-', which means reading the whole data starting from the 101th character (The index is 100 and index starts with 0).


Paging
-------
Lists APIs such as :func:`list_buckets <Service.list_buckets>` and :func:`list_objects <Bucket.list_objects>` support paging.
Specify the paging markers, for example, marker or key_marker, to query a specific page after that marker. 
For the first page, the marker is empty, which is the default value.
For the following pages, use the next_marker or next_key_marker value as the marker value. Check the is_truncated value after each call to determine if it is the last page. If the value is false, it means it is the last page.

.. _progress_callback:

Upload or Download Progress
-----------
Upload or Download APIs such as `get_object`, `put_object`, `resumable_upload` support the progress callback method. Users can use it to implement progress bars or other functions when the progress of data is important.

`progress_callback` definition:
    >>> def progress_callback(bytes_consumed, total_bytes):
    >>>     '''progress callback
    >>> 
    >>>     :param int bytes_consumed: Consumed bytes. For upload it's uploaded bytes. for download it is download bytes.
    >>>     :param int total_bytes: Total bytes.
    >>>     '''

Note that `total_bytes` has different meanings for download and upload.
    - For upload: If the input is bytes data or file object supports seek/tell, it is the total size. Otherwise it is none.
    - For Download: If http headers that are returned have content-length header, then it is the value of content-length. Otherwise it is none.


.. _unix_time:

Unix Time
---------
OSS Python SDK automatically converts the server time to Unix time (or epoch time, https://en.wikipedia.org/wiki/Unix_time )

The standard time format in OSS is:
    - HTTP Date format, for example `Sat, 05 Dec 2015 11:04:39 GMT`. It is used in http headers such as If-Modified-Since or Last-Modified.
    - ISO8601 format, for example `2015-12-05T00:00:00.000Z`.
      It is used for lifecycle management configuration, for the create time of a result bucket list, last modified time of a result file list and more.

`http_date` converts the Unix Time to HTTP Date. `http_to_unixtime` does the opposite. For example:
    >>> import oss2, time
    >>> unix_time = int(time.time())             # Current time in UNIX Time, Value is 1449313829
    >>> date_str = oss2.http_date(unix_time)     # The date_str will be 'Sat, 05 Dec 2015 11:10:29 GMT'
    >>> oss2.http_to_unixtime(date_str)          # The result is 1449313829

.. Note::
    Please use `http_date` instead of `strftime` to generate the date in http protocol. Because the latter depends on the locale.
    For example, `strftime` result could contain Chinese which could not be parsed by OSS server.

`iso8601_to_unixtime` converts date in ISO8601 format to Unix Time. `date_to_iso8601` and `iso8601_to_date` do the translation between ISO8601 and datetime.date. For example:
    >>> import oss2
    >>> d = oss2.iso8601_to_date('2015-12-05T00:00:00.000Z')  # Get datetime.date(2015, 12, 5)
    >>> date_str = oss2.date_to_iso8601(d)                    # Get '2015-12-05T00:00:00.000Z'
    >>> oss2.iso8601_to_unixtime(date_str)                    # Get 1449273600
"""

from . import xml_utils
from . import http
from . import utils
from . import exceptions
from . import defaults

from .models import *
from .compat import urlquote, urlparse, to_unicode, to_string
from .crypto import BaseCryptoProvider

import time
import shutil
import oss2.utils


class _Base(object):
    def __init__(self, auth, endpoint, is_cname, session, connect_timeout,
                 app_name='', enable_crc=True):
        self.auth = auth
        self.endpoint = _normalize_endpoint(endpoint.strip())
        self.session = session or http.Session()
        self.timeout = defaults.get(connect_timeout, defaults.connect_timeout)
        self.app_name = app_name
        self.enable_crc = enable_crc

        self._make_url = _UrlMaker(self.endpoint, is_cname)

    def _do(self, method, bucket_name, key, **kwargs):
        key = to_string(key)
        req = http.Request(method, self._make_url(bucket_name, key),
                           app_name=self.app_name,
                           **kwargs)
        self.auth._sign_request(req, bucket_name, key)

        resp = self.session.do_request(req, timeout=self.timeout)
        if resp.status // 100 != 2:
            raise exceptions.make_exception(resp)

        # Note that connections are only released back to the pool for reuse once all body data has been read. 
        # be sure to either set stream to False or read the content property of the Response object.
        # For more details, please refer to http://docs.python-requests.org/en/master/user/advanced/#keep-alive.
        content_length = oss2.models._hget(resp.headers, 'content-length', int)
        if content_length is not None and content_length == 0:
            resp.read()

        return resp

    def _parse_result(self, resp, parse_func, klass):
        result = klass(resp)
        parse_func(result, resp.read())
        return result


[文档]class Service(_Base): """The class for interacting with Service related operations, such as listing all buckets. Usage: >>> import oss2 >>> auth = oss2.Auth('your-access-key-id', 'your-access-key-secret') >>> service = oss2.Service(auth, 'oss-cn-hangzhou.aliyuncs.com') >>> service.list_buckets() <oss2.models.ListBucketsResult object at 0x0299FAB0> :param auth: The auth instance that contains access-key-id and access-key-secret. :type auth: oss2.Auth :param str endpoint: The endpoint domain, such as 'oss-cn-hangzhou.aliyuncs.com'. :param session: The session instance. If specified as None, a new session is used. Otherwise, it will reuse the previous session. :type session: oss2.Session :param float connect_timeout: The connection timeout in seconds. :param str app_name: App name. If this is not empty, it will be appended in the User Agent header. Note that this value will be a part of the HTTP Header value and thus must follow http protocol’s format. """ def __init__(self, auth, endpoint, session=None, connect_timeout=None, app_name=''): super(Service, self).__init__(auth, endpoint, False, session, connect_timeout, app_name=app_name)
[文档] def list_buckets(self, prefix='', marker='', max_keys=100): """List buckets by prefix :param str prefix: List buckets with the prefix. List all buckets if it's empty. :param str marker: The paging maker. It's empty for first page and then use next_marker in the response of the previous page. :param int max_keys: Max bucket count to return. :return: The bucket list :rtype: :class:`ListBucketsResult <oss2.models.ListBucketsResult>` """ resp = self._do('GET', '', '', params={'prefix': prefix, 'marker': marker, 'max-keys': str(max_keys)}) return self._parse_result(resp, xml_utils.parse_list_buckets, ListBucketsResult)
[文档]class Bucket(_Base): """The class for Bucket or Object related operations, such as the creation or deletion of buckets, and the upload or download of objects. Usage (A Bucket in the Hangzhou data center is used as an example): >>> import oss2 >>> auth = oss2.Auth('your-access-key-id', 'your-access-key-secret') >>> bucket = oss2.Bucket(auth, 'http://oss-cn-hangzhou.aliyuncs.com', 'your-bucket') >>> bucket.put_object('readme.txt', 'content of the object') <oss2.models.PutObjectResult object at 0x029B9930> :param auth: Auth object that contains the AccessKeyId and AccessKeySecret of the user. :type auth: oss2.Auth :param str endpoint: Domain name of endpoint or the CName. :param str bucket_name: Bucket name :param bool is_cname: True if the endpoint is CNAME. Otherwise, it is False. :param session: Session instance. None if creating a new session. :type session: oss2.Session :param float connect_timeout: Connection timeout in seconds. :param str app_name: App name. If it is not empty, the name will be appended in User Agent. Note that this value will be a part of the HTTP Header value and thus must follow http protocol’s format. """ ACL = 'acl' CORS = 'cors' LIFECYCLE = 'lifecycle' LOCATION = 'location' LOGGING = 'logging' REFERER = 'referer' WEBSITE = 'website' LIVE = 'live' COMP = 'comp' STATUS = 'status' VOD = 'vod' SYMLINK = 'symlink' STAT = 'stat' BUCKET_INFO = 'bucketInfo' def __init__(self, auth, endpoint, bucket_name, is_cname=False, session=None, connect_timeout=None, app_name='', enable_crc=True): super(Bucket, self).__init__(auth, endpoint, is_cname, session, connect_timeout, app_name, enable_crc) self.bucket_name = bucket_name.strip()
[文档] def sign_url(self, method, key, expires, headers=None, params=None): """Generate the presigned URL. The signed URL can be used to access the object by any user who has the URL. For example, in the code below, it generates the signed URL with 5 minutes TTL for log.jpg file: >>> bucket.sign_url('GET', 'log.jpg', 5 * 60) 'http://your-bucket.oss-cn-hangzhou.aliyuncs.com/logo.jpg?OSSAccessKeyId=YourAccessKeyId\&Expires=1447178011&Signature=UJfeJgvcypWq6Q%2Bm3IJcSHbvSak%3D' :param method: HTTP method such as 'GET', 'PUT', 'DELETE', and so on. :type method: str :param key: The object key. :param expires: Expiration time in seconds. The URL is invalid after it expires. :param headers: The HTTP headers to sign. For example, the headers starting with x-oss-meta- (user's custom metadata), Content-Type, and so on. There is no need to download them. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param params: HTTP query parameters to sign :return: Signed URL. """ key = to_string(key) req = http.Request(method, self._make_url(self.bucket_name, key), headers=headers, params=params) return self.auth._sign_url(req, self.bucket_name, key, expires)
[文档] def sign_rtmp_url(self, channel_name, playlist_name, expires): """Sign RTMP pushed streaming URL. It is used to push the RTMP streaming to OSS for trusted user who has the URL. :param channel_name: The live channel name :param expires: Expiration time in seconds.The URL is invalid after it expires. :param playlist_name: Playlist name. It should be the one created in live channel creation time. :param params: HTTP query parameters to sign. :return: Signed URL. """ url = self._make_url(self.bucket_name, 'live').replace('http://', 'rtmp://').replace('https://', 'rtmp://') + '/' + channel_name params = {} params['playlistName'] = playlist_name return self.auth._sign_rtmp_url(url, self.bucket_name, channel_name, playlist_name, expires, params)
[文档] def list_objects(self, prefix='', delimiter='', marker='', max_keys=100): """List objects by the prefix under a bucket. :param str prefix: The prefix of the objects to list. :param str delimiter: The folder separator which can simulate directory. :param str marker: Paging marker. It's empty for first page and then use next_marker in the response of the previous page. :param int max_keys: Max entries to return. The sum of files and directories should not exceed that value. :return: :class:`ListObjectsResult <oss2.models.ListObjectsResult>` """ resp = self.__do_object('GET', '', params={'prefix': prefix, 'delimiter': delimiter, 'marker': marker, 'max-keys': str(max_keys), 'encoding-type': 'url'}) return self._parse_result(resp, xml_utils.parse_list_objects, ListObjectsResult)
[文档] def put_object(self, key, data, headers=None, progress_callback=None): """Upload a normal file (not appendable). Usage: >>> bucket.put_object('readme.txt', 'content of readme.txt') >>> with open(u'local_file.txt', 'rb') as f: >>> bucket.put_object('remote_file.txt', f) Upload a folder >>> bucket.enable_crc = False # this is needed as by default crc is enabled and it will not work when creating folder. >>> bucket.put_object('testfolder/', None) :param key: The object name in OSS. :param data: The file content for upload. :type data: bytes, str or file-like object. :param headers: HTTP headers. It can be Content-type, Content-MD5 or x-oss-meta- prefixed headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: The user's callback. A typical usage is the progress bar. See :ref:`progress_callback` for more information. :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ headers = utils.set_content_type(http.CaseInsensitiveDict(headers), key) if progress_callback: data = utils.make_progress_adapter(data, progress_callback) if self.enable_crc: data = utils.make_crc_adapter(data) resp = self.__do_object('PUT', key, data=data, headers=headers) result = PutObjectResult(resp) if self.enable_crc and result.crc is not None: utils.check_crc('put', data.crc, result.crc, result.request_id) return result
[文档] def put_object_from_file(self, key, filename, headers=None, progress_callback=None): """Upload a normal local file to OSS. :param str key: The object name in oss. :param str filename: The local file path with the read permission. :param headers: The HTTP headers. It could be content-type, Content-MD5 or x-oss-meta- prefixed headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: The user's callback. Typical usage is progress bar. See :ref:`progress_callback` for more information. :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ headers = utils.set_content_type(http.CaseInsensitiveDict(headers), filename) with open(to_unicode(filename), 'rb') as f: return self.put_object(key, f, headers=headers, progress_callback=progress_callback)
[文档] def append_object(self, key, position, data, headers=None, progress_callback=None, init_crc=None): """Append the data to an existing object or create a new appendable file if there is no existing object. :param str key: The new file name or existing file name . :param int position: It's 0 for creating a new appendable file or the current length for appending an existing file. The `position` value can be got from `AppendObjectResult.next_position` of the previous append_object results. :param data: User data :type data: str,bytes,file-like object or iterator object. :param headers: HTTP headers. It can be content-type, Content-MD5 or x-oss-meta- prefixed headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: The user's callback. A typical usage is the progress bar. Check out :ref:`progress_callback` for more information. :return: :class:`AppendObjectResult <oss2.models.AppendObjectResult>` :raises: If the `position` is not the same as the current file's length, :class:`PositionNotEqualToLength <oss2.exceptions.PositionNotEqualToLength>` is thrown. If the file is not appendable, :class:`ObjectNotAppendable <oss2.exceptions.ObjectNotAppendable>` is thrown. Other client side exceptions are also thrown. """ headers = utils.set_content_type(http.CaseInsensitiveDict(headers), key) if progress_callback: data = utils.make_progress_adapter(data, progress_callback) if self.enable_crc and init_crc is not None: data = utils.make_crc_adapter(data, init_crc) resp = self.__do_object('POST', key, data=data, headers=headers, params={'append': '', 'position': str(position)}) result = AppendObjectResult(resp) if self.enable_crc and result.crc is not None and init_crc is not None: utils.check_crc('append', data.crc, result.crc, result.request_id) return result
[文档] def get_object(self, key, byte_range=None, headers=None, progress_callback=None, process=None, params=None): """Download a file. Usage: >>> result = bucket.get_object('readme.txt') >>> print(result.read()) 'hello world' :param key: The object name in OSS :param byte_range: Download range. See :ref:`byte_range` for more information. :param headers: HTTP headers :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: The progress callback function specified by the user. Please see :ref:`progress_callback` for more information. :param process: The OSS file process, for example image processing. The process is applied to the returned object. :param params: Query string parameters for HTTP requests. :return: The file-like object :raises: If the file does not exist, :class:`NoSuchKey <oss2.exceptions.NoSuchKey>` is thrown. Other exceptions may also be thrown. """ headers = http.CaseInsensitiveDict(headers) range_string = _make_range_string(byte_range) if range_string: headers['range'] = range_string params = {} if params is None else params if process: params.update({'x-oss-process': process}) resp = self.__do_object('GET', key, headers=headers, params=params) return GetObjectResult(resp, progress_callback, self.enable_crc)
[文档] def get_object_to_file(self, key, filename, byte_range=None, headers=None, progress_callback=None, process=None, params=None): """Download an object to the local file. :param key: The object name in OSS. :param filename: The local file name. The parent directory should be existent and have write permissions. :param byte_range: The download range. See :ref:`byte_range`. :param headers: HTTP headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: The progress callback function specified by the user. See :ref:`progress_callback` for more infomation. :param process: The OSS file process, for example image processing. The process is applied to the returned object. :return: If the file does not exist, :class:`NoSuchKey <oss2.exceptions.NoSuchKey>` is thrown. Other exceptions may also be thrown. """ with open(to_unicode(filename), 'wb') as f: result = self.get_object(key, byte_range=byte_range, headers=headers, progress_callback=progress_callback, process=process, params=params) if result.content_length is None: shutil.copyfileobj(result, f) else: utils.copyfileobj_and_verify(result, f, result.content_length, request_id=result.request_id) return result
[文档] def head_object(self, key, headers=None): """Get object metadata. The metadata is in HTTP response headers, which could be accessed by `RequestResult.headers`. Usage: >>> result = bucket.head_object('readme.txt') >>> print(result.content_type) text/plain :param key: The object name in OSS. :param headers: HTTP headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :return: :class:`HeadObjectResult <oss2.models.HeadObjectResult>` :raises: If the bucket or file does not exist, :class:`NotFound <oss2.exceptions.NotFound>` is thrown. """ resp = self.__do_object('HEAD', key, headers=headers) return HeadObjectResult(resp)
[文档] def get_object_meta(self, key): """Get the object's basic metadata which includes ETag, Size, LastModified, and it does not return object content. The metadata is in HTTP response headers, which could be accessed by `GetObjectMetaResult`'s `last_modified`, `content_length`, `etag` . :param key: The object key in OSS. :return: :class:`GetObjectMetaResult <oss2.models.GetObjectMetaResult>` :raises: If file does not exist, :class:`NoSuchKey <oss2.exceptions.NoSuchKey>` is thrown. Other exceptions can also be thrown. """ resp = self.__do_object('GET', key, params={'objectMeta': ''}) return GetObjectMetaResult(resp)
[文档] def object_exists(self, key): """If the file exists, it returns True. Otherwise, it returns False. If the bucket does not exist or other errors occur, exceptions will be thrown.""" # If head_object is used as the implementation, as it only has response header, when 404 is returned, no way to tell if it's a NoSuchBucket or NoSuchKey. # # Before version 2.2.0, it calls get_object with current + 24h as the if-modified-since parameter. # If file exists, it returns 304 (NotModified). If file does not exists, returns NoSuchkey. # However get_object would retrieve object in other sites if "Retrieve from source" is set and object is not found in OSS. # That is the file could be from other sites and thus should have return 404 instead of the object in this case. # # So the current solution is to call get_object_meta which is not impacted by "Retrieve from source" feature. # Meanwhile it could differentiate bucket not found or key not found. try: self.get_object_meta(key) except exceptions.NoSuchKey: return False except: raise return True
[文档] def copy_object(self, source_bucket_name, source_key, target_key, headers=None): """Copy a file to the current bucket. :param str source_bucket_name: Source bucket name :param str source_key: Source file name :param str target_key: Target file name :param headers: HTTP headers :type headers: dict or oss2.CaseInsensitiveDict(recommended) :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ headers = http.CaseInsensitiveDict(headers) headers['x-oss-copy-source'] = '/' + source_bucket_name + '/' + urlquote(source_key, '') resp = self.__do_object('PUT', target_key, headers=headers) return PutObjectResult(resp)
[文档] def update_object_meta(self, key, headers): """Update Object's metadata information, including HTTP standard headers such as Content-Type or x-oss-meta- prefixed custom metadata. If user specifies invalid headers, for example non-standard headers or non x-oss-meta- headers, the call would still succeed but no operation is done on the server side. User could call :func:`head_object` to get the updated information. Note that get_object_meta does return all metadata, but head_object does. :param str key: object key :param headers: HTTP headers :type headers: dict or oss2.CaseInsensitiveDict(recommended) :return: :class:`RequestResult <oss2.models.RequestResults>` """ return self.copy_object(self.bucket_name, key, key, headers=headers)
[文档] def delete_object(self, key): """Delete a file. :param str key: The object key. :return: :class:`RequestResult <oss2.models.RequestResult>` """ resp = self.__do_object('DELETE', key) return RequestResult(resp)
[文档] def restore_object(self, key): """Restore an object: If the interface is invoked for the first time for the object, it returns RequestResult.status = 202 . If the restore interface has been successfully invoked and the service-side is still unfrozen, the exception RestoreAlreadyInProgress(status=409) will be thrown. If the restore interface has been successfully invoked and the server-side thawing has been completed, it returns RequestResult.status = 200 when the interface is invoked again, and the object can be downloaded If object does not exist, the exception NoSuchKey (status=404) is thrown. If you invoke the restore interface for non Archive type Object, the exception OperationNotSupported (status=400) is thrown. You can also get the meta data by calling the head_object interface to determine whether you can restore, or to check the status of a restore. Usage: >>> meta = bucket.head_object(key) >>> if meta.resp.headers['x-oss-storage-class'] == oss2.BUCKET_STORAGE_CLASS_ARCHIVE: >>> bucket.restore_object(key) >>> while True: >>> meta = bucket.head_object(key) >>> if meta.resp.headers['x-oss-restore'] == 'ongoing-request="true"': >>> time.sleep(5) >>> else: >>> break :param str key: The object name :return: :class:`RequestResult <oss2.models.RequestResult>` """ resp = self.__do_object('POST', key, params={'restore': ''}) return RequestResult(resp)
[文档] def put_object_acl(self, key, permission): """Set the object ACL. :param str key: The object name :param str permission: The valid values are oss2.OBJECT_ACL_DEFAULT,oss2.OBJECT_ACL_PRIVATE,oss2.OBJECT_ACL_PUBLIC_READ or oss2.OBJECT_ACL_PUBLIC_READ_WRITE. :return: :class:`RequestResult <oss2.models.RequestResult>` """ resp = self.__do_object('PUT', key, params={'acl': ''}, headers={'x-oss-object-acl': permission}) return RequestResult(resp)
[文档] def get_object_acl(self, key): """Get the object ACL. :return: :class:`GetObjectAclResult <oss2.models.GetObjectAclResult>` """ resp = self.__do_object('GET', key, params={'acl': ''}) return self._parse_result(resp, xml_utils.parse_get_object_acl, GetObjectAclResult)
[文档] def batch_delete_objects(self, key_list): """Delete objects specified in the batch key_list. The key_list cannot be empty. :param key_list: The object key list, non-empty. :type key_list: list of str :return: :class:`BatchDeleteObjectsResult <oss2.models.BatchDeleteObjectsResult>` """ if not key_list: raise ClientError('key_list should not be empty') data = xml_utils.to_batch_delete_objects_request(key_list, False) resp = self.__do_object('POST', '', data=data, params={'delete': '', 'encoding-type': 'url'}, headers={'Content-MD5': utils.content_md5(data)}) return self._parse_result(resp, xml_utils.parse_batch_delete_objects, BatchDeleteObjectsResult)
[文档] def init_multipart_upload(self, key, headers=None): """Initialize a multipart upload. `upload_id`, `bucket name` and `object key` in the returned value forms a 3-tuple which is a unique ID for the upload. :param str key: The object key. :param headers: HTTP headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :return: :class:`InitMultipartUploadResult <oss2.models.InitMultipartUploadResult>` """ headers = utils.set_content_type(http.CaseInsensitiveDict(headers), key) resp = self.__do_object('POST', key, params={'uploads': ''}, headers=headers) return self._parse_result(resp, xml_utils.parse_init_multipart_upload, InitMultipartUploadResult)
[文档] def upload_part(self, key, upload_id, part_number, data, progress_callback=None, headers=None): """Upload a part. :param str key: The object key to upload. It must be the same as the one in :func:`init_multipart_upload`. :param str upload_id: The multipart upload ID :param int part_number: The part number, starting with 1. :param data: Data to upload :param progress_callback: The the progress callback function specified by the user. Can be used to implement progress bars and other functions. See :ref:`progress_callback` for more information. :param headers: HTTP headers, such as Content-MD5 . :type headers: dict or oss2.CaseInsensitiveDict(recommended) :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ if progress_callback: data = utils.make_progress_adapter(data, progress_callback) if self.enable_crc: data = utils.make_crc_adapter(data) resp = self.__do_object('PUT', key, params={'uploadId': upload_id, 'partNumber': str(part_number)}, headers=headers, data=data) result = PutObjectResult(resp) if self.enable_crc and result.crc is not None: utils.check_crc('put', data.crc, result.crc, result.request_id) return result
[文档] def complete_multipart_upload(self, key, upload_id, parts, headers=None): """Complete a multipart upload. A file would be created with all the parts' data and these parts will not be available to user. :param str key: The object key which should be the same as the one in :func:`init_multipart_upload`. :param str upload_id: The multipart upload ID. :param parts: PartInfo list. The part_number and ETag are required in PartInfo. The ETag comes from the result of :func:`upload_part`. :type parts: list of :class:`PartInfo <oss2.models.PartInfo>` :param headers: HTTP headers :type headers: dict or oss2.CaseInsensitiveDict(recommended) :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ data = xml_utils.to_complete_upload_request(sorted(parts, key=lambda p: p.part_number)) resp = self.__do_object('POST', key, params={'uploadId': upload_id}, data=data, headers=headers) return PutObjectResult(resp)
[文档] def abort_multipart_upload(self, key, upload_id): """Abort a multipart upload. :param str key: The object key, must be the same as the one in :func:`init_multipart_upload`. :param str upload_id: The multipart upload ID. :return: :class:`RequestResult <oss2.models.RequestResult>` """ resp = self.__do_object('DELETE', key, params={'uploadId': upload_id}) return RequestResult(resp)
[文档] def list_multipart_uploads(self, prefix='', delimiter='', key_marker='', upload_id_marker='', max_uploads=1000): """Lists all the ongoing multipart uploads. It supports paging. :param str prefix: The prefix filter. :param str delimiter: The delimiter of folder. :param str key_marker: The key marker for paging. It is empty for first page and then the `next_key_marker` is used in the response of the previous page. :param str upload_id_marker: The upload ID marker for paging. It is empty for first page, and then the `next_upload_id_marker` in the response of the previous page. :param int max_uploads: Max entries to return. :return: :class:`ListMultipartUploadsResult <oss2.models.ListMultipartUploadsResult>` """ resp = self.__do_object('GET', '', params={'uploads': '', 'prefix': prefix, 'delimiter': delimiter, 'key-marker': key_marker, 'upload-id-marker': upload_id_marker, 'max-uploads': str(max_uploads), 'encoding-type': 'url'}) return self._parse_result(resp, xml_utils.parse_list_multipart_uploads, ListMultipartUploadsResult)
[文档] def upload_part_copy(self, source_bucket_name, source_key, byte_range, target_key, target_upload_id, target_part_number, headers=None): """Uploads a part from another object. Copy part or the whole of an existing file into a slice of the target file. :param byte_range: The range to copy in the source file :ref:`byte_range` :param headers: HTTP headers :type headers: dict or oss2.CaseInsensitiveDict(recommended) :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ headers = http.CaseInsensitiveDict(headers) headers['x-oss-copy-source'] = '/' + source_bucket_name + '/' + source_key range_string = _make_range_string(byte_range) if range_string: headers['x-oss-copy-source-range'] = range_string resp = self.__do_object('PUT', target_key, params={'uploadId': target_upload_id, 'partNumber': str(target_part_number)}, headers=headers) return PutObjectResult(resp)
[文档] def list_parts(self, key, upload_id, marker='', max_parts=1000): """List uploaded parts and it supports paging. As comparison, list_multipart_uploads lists ongoing parts. :param str key: The object key. :param str upload_id: The upload ID. :param str marker: The key marker for paging. :param int max_parts: Max entries to return. :return: :class:`ListPartsResult <oss2.models.ListPartsResult>` """ resp = self.__do_object('GET', key, params={'uploadId': upload_id, 'part-number-marker': marker, 'max-parts': str(max_parts)}) return self._parse_result(resp, xml_utils.parse_list_parts, ListPartsResult)
[文档] def create_bucket(self, permission=None, input=None): """Create a new bucket. :param str permission: Bucket ACL. It could be oss2.BUCKET_ACL_PRIVATE (recommended,default value) or oss2.BUCKET_ACL_PUBLIC_READ or oss2.BUCKET_ACL_PUBLIC_READ_WRITE. :param input: :class:`BucketCreateConfig <oss2.models.BucketCreateConfig>` object """ if permission: headers = {'x-oss-acl': permission} else: headers = None data = self.__convert_data(BucketCreateConfig, xml_utils.to_put_bucket_config, input) resp = self.__do_bucket('PUT', headers=headers, data=data) return RequestResult(resp)
[文档] def delete_bucket(self): """Delete a bucket. A bucket can be deleted only when the bucket is empty and there are no incomplete multipart uploads. :return: :class:`RequestResult <oss2.models.RequestResult>` :raises: If the bucket is not empty, :class:`BucketNotEmpty <oss2.exceptions.BucketNotEmpty>` is thrown. """ resp = self.__do_bucket('DELETE') return RequestResult(resp)
[文档] def put_bucket_acl(self, permission): """Set the bucket ACL. :param str permission: The new ACL whose value could be oss2.BUCKET_ACL_PRIVATE, oss2.BUCKET_ACL_PUBLIC_READ or oss2.BUCKET_ACL_PUBLIC_READ_WRITE """ resp = self.__do_bucket('PUT', headers={'x-oss-acl': permission}, params={Bucket.ACL: ''}) return RequestResult(resp)
[文档] def get_bucket_acl(self): """Get bucket ACL. :return: :class:`GetBucketAclResult <oss2.models.GetBucketAclResult>` """ resp = self.__do_bucket('GET', params={Bucket.ACL: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_acl, GetBucketAclResult)
[文档] def put_bucket_cors(self, input): """Set the bucket CORS. :param input: :class:`BucketCors <oss2.models.BucketCors>` instance or data can be converted to BucketCors by xml_utils.to_put_bucket_cors . """ data = self.__convert_data(BucketCors, xml_utils.to_put_bucket_cors, input) resp = self.__do_bucket('PUT', data=data, params={Bucket.CORS: ''}) return RequestResult(resp)
[文档] def get_bucket_cors(self): """Get the bucket CORS. :return: :class:`GetBucketCorsResult <oss2.models.GetBucketCorsResult>` """ resp = self.__do_bucket('GET', params={Bucket.CORS: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_cors, GetBucketCorsResult)
[文档] def delete_bucket_cors(self): """Delete the bucket CORS.""" resp = self.__do_bucket('DELETE', params={Bucket.CORS: ''}) return RequestResult(resp)
[文档] def put_bucket_lifecycle(self, input): """Set the lifecycle of the bucket. :param input: :class:`BucketLifecycle <oss2.models.BucketLifecycle>` instance or data can be converted to BucketLifecycle by xml_utils.to_put_bucket_lifecycle. """ data = self.__convert_data(BucketLifecycle, xml_utils.to_put_bucket_lifecycle, input) resp = self.__do_bucket('PUT', data=data, params={Bucket.LIFECYCLE: ''}) return RequestResult(resp)
[文档] def get_bucket_lifecycle(self): """Get the bucket lifecycle. :return: :class:`GetBucketLifecycleResult <oss2.models.GetBucketLifecycleResult>` :raises: If the lifecycle is not set in the bucket, :class:`NoSuchLifecycle <oss2.exceptions.NoSuchLifecycle>` is thrown. """ resp = self.__do_bucket('GET', params={Bucket.LIFECYCLE: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_lifecycle, GetBucketLifecycleResult)
[文档] def delete_bucket_lifecycle(self): """Delete the lifecycle of the bucket. It still return 200 OK if the lifecycle does not exist.""" resp = self.__do_bucket('DELETE', params={Bucket.LIFECYCLE: ''}) return RequestResult(resp)
[文档] def get_bucket_location(self): """Get the bucket location. :return: :class:`GetBucketLocationResult <oss2.models.GetBucketLocationResult>` """ resp = self.__do_bucket('GET', params={Bucket.LOCATION: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_location, GetBucketLocationResult)
[文档] def put_bucket_logging(self, input): """Set the bucket logging. :param input: :class:`BucketLogging <oss2.models.BucketLogging>` instance or other data that can be converted to BucketLogging by `xml_utils.to_put_bucket_logging` . """ data = self.__convert_data(BucketLogging, xml_utils.to_put_bucket_logging, input) resp = self.__do_bucket('PUT', data=data, params={Bucket.LOGGING: ''}) return RequestResult(resp)
[文档] def get_bucket_logging(self): """Get the bucket logging. :return: :class:`GetBucketLoggingResult <oss2.models.GetBucketLoggingResult>` """ resp = self.__do_bucket('GET', params={Bucket.LOGGING: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_logging, GetBucketLoggingResult)
[文档] def delete_bucket_logging(self): """Delete the bucket's logging configuration---the existing logging files are not deleted.""" resp = self.__do_bucket('DELETE', params={Bucket.LOGGING: ''}) return RequestResult(resp)
[文档] def put_bucket_referer(self, input): """Set the bucket's allowed referer. :param input: :class:`BucketReferer <oss2.models.BucketReferer>` instance or other data that can be converted to BucketReferer by xml_utils.to_put_bucket_referer. """ data = self.__convert_data(BucketReferer, xml_utils.to_put_bucket_referer, input) resp = self.__do_bucket('PUT', data=data, params={Bucket.REFERER: ''}) return RequestResult(resp)
[文档] def get_bucket_referer(self): """Get the bucket's allowed referer. :return: :class:`GetBucketRefererResult <oss2.models.GetBucketRefererResult>` """ resp = self.__do_bucket('GET', params={Bucket.REFERER: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_referer, GetBucketRefererResult)
[文档] def get_bucket_stat(self): """Get the bucket status, including bucket size, count of objects, count of ongoing multipart uploads. :return: :class:`GetBucketStatResult <oss2.models.GetBucketStatResult>` """ resp = self.__do_bucket('GET', params={Bucket.STAT: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_stat, GetBucketStatResult)
[文档] def get_bucket_info(self): """Get the bucket info, such as Created-Time, Endpoint, Owner, ACL and more. :return: :class:`GetBucketInfoResult <oss2.models.GetBucketInfoResult>` """ resp = self.__do_bucket('GET', params={Bucket.BUCKET_INFO: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_info, GetBucketInfoResult)
[文档] def put_bucket_website(self, input): """Set the static website configuration for the bucket. :param input: :class:`BucketWebsite <oss2.models.BucketWebsite>` """ data = self.__convert_data(BucketWebsite, xml_utils.to_put_bucket_website, input) resp = self.__do_bucket('PUT', data=data, params={Bucket.WEBSITE: ''}) return RequestResult(resp)
[文档] def get_bucket_website(self): """Get the static website configuration. :return: :class:`GetBucketWebsiteResult <oss2.models.GetBucketWebsiteResult>` :raises: If the static website config is not set, :class:`NoSuchWebsite <oss2.exceptions.NoSuchWebsite>` is thrown. """ resp = self.__do_bucket('GET', params={Bucket.WEBSITE: ''}) return self._parse_result(resp, xml_utils.parse_get_bucket_websiste, GetBucketWebsiteResult)
[文档] def delete_bucket_website(self): """Delete the static website configuration.""" resp = self.__do_bucket('DELETE', params={Bucket.WEBSITE: ''}) return RequestResult(resp)
[文档] def create_live_channel(self, channel_name, input): """Create a live channel. :param str channel_name: The live channel name. :param input: LiveChannelInfo instance, which includes the live channel's description information. :return: :class:`CreateLiveChannelResult <oss2.models.CreateLiveChannelResult>` """ data = self.__convert_data(LiveChannelInfo, xml_utils.to_create_live_channel, input) resp = self.__do_object('PUT', channel_name, data=data, params={Bucket.LIVE: ''}) return self._parse_result(resp, xml_utils.parse_create_live_channel, CreateLiveChannelResult)
[文档] def delete_live_channel(self, channel_name): """Delete the live channel. :param str channel_name: The live channel name. """ resp = self.__do_object('DELETE', channel_name, params={Bucket.LIVE: ''}) return RequestResult(resp)
[文档] def get_live_channel(self, channel_name): """Get the live channel configuration. :param str channel_name: The live channel name. :return: :class:`GetLiveChannelResult <oss2.models.GetLiveChannelResult>` """ resp = self.__do_object('GET', channel_name, params={Bucket.LIVE: ''}) return self._parse_result(resp, xml_utils.parse_get_live_channel, GetLiveChannelResult)
[文档] def list_live_channel(self, prefix='', marker='', max_keys=100): """List all live channels under the bucket according to the prefix and marker filters :param str prefix: The channel ID must start with this prefix. :param str marker: The channel ID marker for paging. :param int max_keys: The maximum channel count to return. :return: :class:`ListLiveChannelResult <oss2.models.ListLiveChannelResult>` """ resp = self.__do_bucket('GET', params={Bucket.LIVE: '', 'prefix': prefix, 'marker': marker, 'max-keys': str(max_keys)}) return self._parse_result(resp, xml_utils.parse_list_live_channel, ListLiveChannelResult)
[文档] def get_live_channel_stat(self, channel_name): """Get the pushed live channel stream status. :param str channel_name: The live channel name. :return: :class:`GetLiveChannelStatResult <oss2.models.GetLiveChannelStatResult>` """ resp = self.__do_object('GET', channel_name, params={Bucket.LIVE: '', Bucket.COMP: 'stat'}) return self._parse_result(resp, xml_utils.parse_live_channel_stat, GetLiveChannelStatResult)
[文档] def put_live_channel_status(self, channel_name, status): """Set the live channel status. Valid statuses are: 'enabled' and 'disabled'. :param str channel_name: The live channel name. :param str status: The live channel's desired status. """ resp = self.__do_object('PUT', channel_name, params={Bucket.LIVE: '', Bucket.STATUS: status}) return RequestResult(resp)
[文档] def get_live_channel_history(self, channel_name): """Retrieve the 10 most recently pushed live channel streams. Each record includes the start time, the end time, and the remote address (the source address of the pushed stream). :param str channel_name: The live channel name. :return: :class:`GetLiveChannelHistoryResult <oss2.models.GetLiveChannelHistoryResult>` """ resp = self.__do_object('GET', channel_name, params={Bucket.LIVE: '', Bucket.COMP: 'history'}) return self._parse_result(resp, xml_utils.parse_live_channel_history, GetLiveChannelHistoryResult)
[文档] def post_vod_playlist(self, channel_name, playlist_name, start_time = 0, end_time = 0): """Generate a VOD play list according to the play list name, start time and end time. :param str channel_name: The live channel name. :param str playlist_name: The playlist name (*.m3u8 file). :param int start_time: Start time in Unix Time, which can be obtained from int(time.time()) :param int end_time: End time in Unix Time, which can be obtained from int(time.time()) """ key = channel_name + "/" + playlist_name resp = self.__do_object('POST', key, params={Bucket.VOD: '', 'startTime': str(start_time), 'endTime': str(end_time)}) return RequestResult(resp)
def _get_bucket_config(self, config): """Get the bucket configuration. The raw xml string could be got by result.read() (not recommended though). :param str config: Supported values are `Bucket.ACL` , `Bucket.LOGGING`, etc (check out the beginning part of Bucket class for the complete list). :return: :class:`RequestResult <oss2.models.RequestResult>` """ return self.__do_bucket('GET', params={config: ''}) def __do_object(self, method, key, **kwargs): return self._do(method, self.bucket_name, key, **kwargs) def __do_bucket(self, method, **kwargs): return self._do(method, self.bucket_name, '', **kwargs) def __convert_data(self, klass, converter, data): if isinstance(data, klass): return converter(data) else: return data
[文档]class CryptoBucket(): """Class to encrypt the operation of Bucket and Object, such as upload/download object. Create/Delete bucket operations need to use the interface of Class Bucket. Usage (Suppose Bucket belongs to the Hangzhou region): >>> import oss2 >>> auth = oss2.Auth('your-access-key-id', 'your-access-key-secret') >>> bucket = oss2.CryptoBucket(auth, 'http://oss-cn-hangzhou.aliyuncs.com', 'your-bucket', oss2.LocalRsaProvider()) >>> bucket.put_object('readme.txt', 'content of the object') <oss2.models.PutObjectResult object at 0x029B9930> :param auth: It is an oss2.Auth object containing access-key-id and access-key-secret. :type auth: oss2.Auth :param str endpoint: Domain name of endpoint or the CName. :param str bucket_name: Bucket name. :param crypto_provider: The client encryption class, The parameter is empty by default. :type crypto_provider: oss2.crypto.LocalRsaProvider :param bool is_cname: True if the endpoint is CNAME. Otherwise, it is False. :param session: Session instance. None for creating a new session, if it is not None it will resuse the session. :type session: oss2.Session :param float connect_timeout: Connection timeout in seconds. :param str app_name: App name. If it is not empty, it is appended in User Agent. Note that this value will be part of the HTTP Header value and thus must follow http protocol’s format. :param bool enable_crc: True if you want to enable the CRC check. Otherwise it is False. """ def __init__(self, auth, endpoint, bucket_name, crypto_provider, is_cname=False, session=None, connect_timeout=None, app_name='', enable_crc=True): if not isinstance(crypto_provider, BaseCryptoProvider): raise ClientError('Crypto bucket must provide a valid crypto_provider') self.crypto_provider = crypto_provider self.bucket_name = bucket_name.strip() self.enable_crc = enable_crc self.bucket = Bucket(auth, endpoint, bucket_name, is_cname, session, connect_timeout, app_name, enable_crc=False)
[文档] def put_object(self, key, data, headers=None, progress_callback=None): """Upload a normal file. Usage: >>> bucket.put_object('readme.txt', 'content of readme.txt') >>> with open(u'local_file.txt', 'rb') as f: >>> bucket.put_object('remote_file.txt', f) :param key: The object name in OSS. :param data: The content for upload. :type data: bytes, str or file-like object :param headers: HTTP headers. It can be content-type, Content-MD5 or x-oss-meta- prefixed headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: The user's callback. Typical usage is the progress bar. See :ref:`progress_callback` for more information. :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ if progress_callback: data = utils.make_progress_adapter(data, progress_callback) random_key = self.crypto_provider.get_key() start = self.crypto_provider.get_start() data = self.crypto_provider.make_encrypt_adapter(data, random_key, start) headers = self.crypto_provider.build_header(headers) if self.enable_crc: data = utils.make_crc_adapter(data) return self.bucket.put_object(key, data, headers, progress_callback=None)
[文档] def put_object_from_file(self, key, filename, headers=None, progress_callback=None): """Upload a normal object from a local file to OSS. :param str key: The object name in OSS. :param str filename: Local file path, called needs the read permission. :param headers: HTTP headers. It could be content-type, Content-MD5 or x-oss-meta- prefixed headers. :type headers: dict, but recommendation is oss2.CaseInsensitiveDict :param progress_callback: The user's callback. A typical usage is the progress bar. See :ref:`progress_callback` for more information. :return: :class:`PutObjectResult <oss2.models.PutObjectResult>` """ headers = utils.set_content_type(http.CaseInsensitiveDict(headers), filename) with open(to_unicode(filename), 'rb') as f: return self.put_object(key, f, headers=headers, progress_callback=progress_callback)
[文档] def get_object(self, key, headers=None, progress_callback=None, params=None): """Download a file. Usage: >>> result = bucket.get_object('readme.txt') >>> print(result.read()) 'hello world' :param key: The object name in OSS. :param headers: HTTP headers. :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: User callback. See :ref:`progress_callback` for more information. :return: The file-like object. :raises: If the file does not exist, :class:`NoSuchKey <oss2.exceptions.NoSuchKey>` is thrown. Other exceptions may also be thrown. """ headers = http.CaseInsensitiveDict(headers) if 'range' in headers: raise ClientError('Crypto bucket do not support range get') encrypted_result = self.bucket.get_object(key, headers=headers, params=params, progress_callback=None) return GetObjectResult(encrypted_result.resp, progress_callback, self.enable_crc, crypto_provider=self.crypto_provider)
[文档] def get_object_to_file(self, key, filename, headers=None, progress_callback=None, params=None): """Download a file to the local folder. :param key: The object name in OSS. :param filename: Local file name. The folder of the file must be available for write and must be pre-existing. :param headers: HTTP headers :type headers: dict or oss2.CaseInsensitiveDict(recommended) :param progress_callback: User callback. See :ref:`progress_callback` for more information. :param process: OSS file process, for example, image processing. The process is applied to the returned object. :return: If the file does not exist, :class:`NoSuchKey <oss2.exceptions.NoSuchKey>` is thrown. Other exceptions may also be thrown. """ with open(to_unicode(filename), 'wb') as f: result = self.get_object(key, headers=headers, progress_callback=progress_callback, params=params) if result.content_length is None: shutil.copyfileobj(result, f) else: utils.copyfileobj_and_verify(result, f, result.content_length, request_id=result.request_id) return result
def _normalize_endpoint(endpoint): if not endpoint.startswith('http://') and not endpoint.startswith('https://'): return 'http://' + endpoint else: return endpoint _ENDPOINT_TYPE_ALIYUN = 0 _ENDPOINT_TYPE_CNAME = 1 _ENDPOINT_TYPE_IP = 2 def _make_range_string(range): if range is None: return '' start = range[0] last = range[1] if start is None and last is None: return '' return 'bytes=' + _range(start, last) def _range(start, last): def to_str(pos): if pos is None: return '' else: return str(pos) return to_str(start) + '-' + to_str(last) def _determine_endpoint_type(netloc, is_cname, bucket_name): if utils.is_ip_or_localhost(netloc): return _ENDPOINT_TYPE_IP if is_cname: return _ENDPOINT_TYPE_CNAME if utils.is_valid_bucket_name(bucket_name): return _ENDPOINT_TYPE_ALIYUN else: return _ENDPOINT_TYPE_IP class _UrlMaker(object): def __init__(self, endpoint, is_cname): p = urlparse(endpoint) self.scheme = p.scheme self.netloc = p.netloc self.is_cname = is_cname def __call__(self, bucket_name, key): self.type = _determine_endpoint_type(self.netloc, self.is_cname, bucket_name) key = urlquote(key, '') if self.type == _ENDPOINT_TYPE_CNAME: return '{0}://{1}/{2}'.format(self.scheme, self.netloc, key) if self.type == _ENDPOINT_TYPE_IP: if bucket_name: return '{0}://{1}/{2}/{3}'.format(self.scheme, self.netloc, bucket_name, key) else: return '{0}://{1}/{2}'.format(self.scheme, self.netloc, key) if not bucket_name: assert not key return '{0}://{1}'.format(self.scheme, self.netloc) return '{0}://{1}.{2}/{3}'.format(self.scheme, bucket_name, self.netloc, key)