Mercurial > repos > shellac > guppy_basecaller
diff env/lib/python3.7/site-packages/boto/s3/bucket.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
line wrap: on
line diff
--- a/env/lib/python3.7/site-packages/boto/s3/bucket.py Thu May 14 16:47:39 2020 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1879 +0,0 @@ -# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ -# Copyright (c) 2010, Eucalyptus Systems, Inc. -# All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, dis- -# tribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the fol- -# lowing conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- -# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. - -import boto -from boto import handler -from boto.resultset import ResultSet -from boto.exception import BotoClientError -from boto.s3.acl import Policy, CannedACLStrings, Grant -from boto.s3.key import Key -from boto.s3.prefix import Prefix -from boto.s3.deletemarker import DeleteMarker -from boto.s3.multipart import MultiPartUpload -from boto.s3.multipart import CompleteMultiPartUpload -from boto.s3.multidelete import MultiDeleteResult -from boto.s3.multidelete import Error -from boto.s3.bucketlistresultset import BucketListResultSet -from boto.s3.bucketlistresultset import VersionedBucketListResultSet -from boto.s3.bucketlistresultset import MultiPartUploadListResultSet -from boto.s3.lifecycle import Lifecycle -from boto.s3.tagging import Tags -from boto.s3.cors import CORSConfiguration -from boto.s3.bucketlogging import BucketLogging -from boto.s3 import website -import boto.jsonresponse -import boto.utils -import xml.sax -import xml.sax.saxutils -import re -import base64 -from collections import defaultdict -from boto.compat import BytesIO, six, StringIO, urllib - -# as per http://goo.gl/BDuud (02/19/2011) - - -class S3WebsiteEndpointTranslate(object): - - trans_region = defaultdict(lambda: 's3-website-us-east-1') - trans_region['eu-west-1'] = 's3-website-eu-west-1' - trans_region['eu-central-1'] = 's3-website.eu-central-1' - trans_region['us-west-1'] = 's3-website-us-west-1' - trans_region['us-west-2'] = 's3-website-us-west-2' - trans_region['sa-east-1'] = 's3-website-sa-east-1' - trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1' - trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1' - trans_region['ap-southeast-2'] = 's3-website-ap-southeast-2' - trans_region['cn-north-1'] = 's3-website.cn-north-1' - - @classmethod - def translate_region(self, reg): - return self.trans_region[reg] - -S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'] - - -class Bucket(object): - - LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery' - - BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?> - <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> - <Payer>%s</Payer> - </RequestPaymentConfiguration>""" - - VersioningBody = """<?xml version="1.0" encoding="UTF-8"?> - <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> - <Status>%s</Status> - <MfaDelete>%s</MfaDelete> - </VersioningConfiguration>""" - - VersionRE = '<Status>([A-Za-z]+)</Status>' - MFADeleteRE = '<MfaDelete>([A-Za-z]+)</MfaDelete>' - - def __init__(self, connection=None, name=None, key_class=Key): - self.name = name - self.connection = connection - self.key_class = key_class - - def __repr__(self): - return '<Bucket: %s>' % self.name - - def __iter__(self): - return iter(BucketListResultSet(self)) - - def __contains__(self, key_name): - return not (self.get_key(key_name) is None) - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'Name': - self.name = value - elif name == 'CreationDate': - self.creation_date = value - else: - setattr(self, name, value) - - def set_key_class(self, key_class): - """ - Set the Key class associated with this bucket. By default, this - would be the boto.s3.key.Key class but if you want to subclass that - for some reason this allows you to associate your new class with a - bucket so that when you call bucket.new_key() or when you get a listing - of keys in the bucket you will get an instances of your key class - rather than the default. - - :type key_class: class - :param key_class: A subclass of Key that can be more specific - """ - self.key_class = key_class - - def lookup(self, key_name, headers=None): - """ - Deprecated: Please use get_key method. - - :type key_name: string - :param key_name: The name of the key to retrieve - - :rtype: :class:`boto.s3.key.Key` - :returns: A Key object from this bucket. - """ - return self.get_key(key_name, headers=headers) - - def get_key(self, key_name, headers=None, version_id=None, - response_headers=None, validate=True): - """ - Check to see if a particular key exists within the bucket. This - method uses a HEAD request to check for the existence of the key. - Returns: An instance of a Key object or None - - :param key_name: The name of the key to retrieve - :type key_name: string - - :param headers: The headers to send when retrieving the key - :type headers: dict - - :param version_id: - :type version_id: string - - :param response_headers: A dictionary containing HTTP - headers/values that will override any headers associated - with the stored object in the response. See - http://goo.gl/EWOPb for details. - :type response_headers: dict - - :param validate: Verifies whether the key exists. If ``False``, this - will not hit the service, constructing an in-memory object. - Default is ``True``. - :type validate: bool - - :rtype: :class:`boto.s3.key.Key` - :returns: A Key object from this bucket. - """ - if validate is False: - if headers or version_id or response_headers: - raise BotoClientError( - "When providing 'validate=False', no other params " + \ - "are allowed." - ) - - # This leans on the default behavior of ``new_key`` (not hitting - # the service). If that changes, that behavior should migrate here. - return self.new_key(key_name) - - query_args_l = [] - if version_id: - query_args_l.append('versionId=%s' % version_id) - if response_headers: - for rk, rv in six.iteritems(response_headers): - query_args_l.append('%s=%s' % (rk, urllib.parse.quote(rv))) - - key, resp = self._get_key_internal(key_name, headers, query_args_l) - return key - - def _get_key_internal(self, key_name, headers, query_args_l): - query_args = '&'.join(query_args_l) or None - response = self.connection.make_request('HEAD', self.name, key_name, - headers=headers, - query_args=query_args) - response.read() - # Allow any success status (2xx) - for example this lets us - # support Range gets, which return status 206: - if response.status / 100 == 2: - k = self.key_class(self) - provider = self.connection.provider - k.metadata = boto.utils.get_aws_metadata(response.msg, provider) - for field in Key.base_fields: - k.__dict__[field.lower().replace('-', '_')] = \ - response.getheader(field) - # the following machinations are a workaround to the fact that - # apache/fastcgi omits the content-length header on HEAD - # requests when the content-length is zero. - # See http://goo.gl/0Tdax for more details. - clen = response.getheader('content-length') - if clen: - k.size = int(response.getheader('content-length')) - else: - k.size = 0 - k.name = key_name - k.handle_version_headers(response) - k.handle_encryption_headers(response) - k.handle_restore_headers(response) - k.handle_storage_class_header(response) - k.handle_addl_headers(response.getheaders()) - return k, response - else: - if response.status == 404: - return None, response - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, '') - - def list(self, prefix='', delimiter='', marker='', headers=None, - encoding_type=None): - """ - List key objects within a bucket. This returns an instance of an - BucketListResultSet that automatically handles all of the result - paging, etc. from S3. You just need to keep iterating until - there are no more results. - - Called with no arguments, this will return an iterator object across - all keys within the bucket. - - The Key objects returned by the iterator are obtained by parsing - the results of a GET on the bucket, also known as the List Objects - request. The XML returned by this request contains only a subset - of the information about each key. Certain metadata fields such - as Content-Type and user metadata are not available in the XML. - Therefore, if you want these additional metadata fields you will - have to do a HEAD request on the Key in the bucket. - - :type prefix: string - :param prefix: allows you to limit the listing to a particular - prefix. For example, if you call the method with - prefix='/foo/' then the iterator will only cycle through - the keys that begin with the string '/foo/'. - - :type delimiter: string - :param delimiter: can be used in conjunction with the prefix - to allow you to organize and browse your keys - hierarchically. See http://goo.gl/Xx63h for more details. - - :type marker: string - :param marker: The "marker" of where you are in the result set - - :param encoding_type: Requests Amazon S3 to encode the response and - specifies the encoding method to use. - - An object key can contain any Unicode character; however, XML 1.0 - parser cannot parse some characters, such as characters with an - ASCII value from 0 to 10. For characters that are not supported in - XML 1.0, you can add this parameter to request that Amazon S3 - encode the keys in the response. - - Valid options: ``url`` - :type encoding_type: string - - :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` - :return: an instance of a BucketListResultSet that handles paging, etc - """ - return BucketListResultSet(self, prefix, delimiter, marker, headers, - encoding_type=encoding_type) - - def list_versions(self, prefix='', delimiter='', key_marker='', - version_id_marker='', headers=None, encoding_type=None): - """ - List version objects within a bucket. This returns an - instance of an VersionedBucketListResultSet that automatically - handles all of the result paging, etc. from S3. You just need - to keep iterating until there are no more results. Called - with no arguments, this will return an iterator object across - all keys within the bucket. - - :type prefix: string - :param prefix: allows you to limit the listing to a particular - prefix. For example, if you call the method with - prefix='/foo/' then the iterator will only cycle through - the keys that begin with the string '/foo/'. - - :type delimiter: string - :param delimiter: can be used in conjunction with the prefix - to allow you to organize and browse your keys - hierarchically. See: - - http://aws.amazon.com/releasenotes/Amazon-S3/213 - - for more details. - - :type key_marker: string - :param key_marker: The "marker" of where you are in the result set - - :param encoding_type: Requests Amazon S3 to encode the response and - specifies the encoding method to use. - - An object key can contain any Unicode character; however, XML 1.0 - parser cannot parse some characters, such as characters with an - ASCII value from 0 to 10. For characters that are not supported in - XML 1.0, you can add this parameter to request that Amazon S3 - encode the keys in the response. - - Valid options: ``url`` - :type encoding_type: string - - :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet` - :return: an instance of a BucketListResultSet that handles paging, etc - """ - return VersionedBucketListResultSet(self, prefix, delimiter, - key_marker, version_id_marker, - headers, - encoding_type=encoding_type) - - def list_multipart_uploads(self, key_marker='', - upload_id_marker='', - headers=None, encoding_type=None): - """ - List multipart upload objects within a bucket. This returns an - instance of an MultiPartUploadListResultSet that automatically - handles all of the result paging, etc. from S3. You just need - to keep iterating until there are no more results. - - :type key_marker: string - :param key_marker: The "marker" of where you are in the result set - - :type upload_id_marker: string - :param upload_id_marker: The upload identifier - - :param encoding_type: Requests Amazon S3 to encode the response and - specifies the encoding method to use. - - An object key can contain any Unicode character; however, XML 1.0 - parser cannot parse some characters, such as characters with an - ASCII value from 0 to 10. For characters that are not supported in - XML 1.0, you can add this parameter to request that Amazon S3 - encode the keys in the response. - - Valid options: ``url`` - :type encoding_type: string - - :rtype: :class:`boto.s3.bucketlistresultset.MultiPartUploadListResultSet` - :return: an instance of a BucketListResultSet that handles paging, etc - """ - return MultiPartUploadListResultSet(self, key_marker, - upload_id_marker, - headers, - encoding_type=encoding_type) - - def _get_all_query_args(self, params, initial_query_string=''): - pairs = [] - - if initial_query_string: - pairs.append(initial_query_string) - - for key, value in sorted(params.items(), key=lambda x: x[0]): - if value is None: - continue - key = key.replace('_', '-') - if key == 'maxkeys': - key = 'max-keys' - if not isinstance(value, six.string_types + (six.binary_type,)): - value = six.text_type(value) - if not isinstance(value, six.binary_type): - value = value.encode('utf-8') - if value: - pairs.append(u'%s=%s' % ( - urllib.parse.quote(key), - urllib.parse.quote(value) - )) - - return '&'.join(pairs) - - def _get_all(self, element_map, initial_query_string='', - headers=None, **params): - query_args = self._get_all_query_args( - params, - initial_query_string=initial_query_string - ) - response = self.connection.make_request('GET', self.name, - headers=headers, - query_args=query_args) - body = response.read() - boto.log.debug(body) - if response.status == 200: - rs = ResultSet(element_map) - h = handler.XmlHandler(rs, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - return rs - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def validate_kwarg_names(self, kwargs, names): - """ - Checks that all named arguments are in the specified list of names. - - :type kwargs: dict - :param kwargs: Dictionary of kwargs to validate. - - :type names: list - :param names: List of possible named arguments. - """ - for kwarg in kwargs: - if kwarg not in names: - raise TypeError('Invalid argument "%s"!' % kwarg) - - def get_all_keys(self, headers=None, **params): - """ - A lower-level method for listing contents of a bucket. This - closely models the actual S3 API and requires you to manually - handle the paging of results. For a higher-level method that - handles the details of paging for you, you can use the list - method. - - :type max_keys: int - :param max_keys: The maximum number of keys to retrieve - - :type prefix: string - :param prefix: The prefix of the keys you want to retrieve - - :type marker: string - :param marker: The "marker" of where you are in the result set - - :type delimiter: string - :param delimiter: If this optional, Unicode string parameter - is included with your request, then keys that contain the - same string between the prefix and the first occurrence of - the delimiter will be rolled up into a single result - element in the CommonPrefixes collection. These rolled-up - keys are not returned elsewhere in the response. - - :param encoding_type: Requests Amazon S3 to encode the response and - specifies the encoding method to use. - - An object key can contain any Unicode character; however, XML 1.0 - parser cannot parse some characters, such as characters with an - ASCII value from 0 to 10. For characters that are not supported in - XML 1.0, you can add this parameter to request that Amazon S3 - encode the keys in the response. - - Valid options: ``url`` - :type encoding_type: string - - :rtype: ResultSet - :return: The result from S3 listing the keys requested - - """ - self.validate_kwarg_names(params, ['maxkeys', 'max_keys', 'prefix', - 'marker', 'delimiter', - 'encoding_type']) - return self._get_all([('Contents', self.key_class), - ('CommonPrefixes', Prefix)], - '', headers, **params) - - def get_all_versions(self, headers=None, **params): - """ - A lower-level, version-aware method for listing contents of a - bucket. This closely models the actual S3 API and requires - you to manually handle the paging of results. For a - higher-level method that handles the details of paging for - you, you can use the list method. - - :type max_keys: int - :param max_keys: The maximum number of keys to retrieve - - :type prefix: string - :param prefix: The prefix of the keys you want to retrieve - - :type key_marker: string - :param key_marker: The "marker" of where you are in the result set - with respect to keys. - - :type version_id_marker: string - :param version_id_marker: The "marker" of where you are in the result - set with respect to version-id's. - - :type delimiter: string - :param delimiter: If this optional, Unicode string parameter - is included with your request, then keys that contain the - same string between the prefix and the first occurrence of - the delimiter will be rolled up into a single result - element in the CommonPrefixes collection. These rolled-up - keys are not returned elsewhere in the response. - - :param encoding_type: Requests Amazon S3 to encode the response and - specifies the encoding method to use. - - An object key can contain any Unicode character; however, XML 1.0 - parser cannot parse some characters, such as characters with an - ASCII value from 0 to 10. For characters that are not supported in - XML 1.0, you can add this parameter to request that Amazon S3 - encode the keys in the response. - - Valid options: ``url`` - :type encoding_type: string - - :rtype: ResultSet - :return: The result from S3 listing the keys requested - """ - self.validate_get_all_versions_params(params) - return self._get_all([('Version', self.key_class), - ('CommonPrefixes', Prefix), - ('DeleteMarker', DeleteMarker)], - 'versions', headers, **params) - - def validate_get_all_versions_params(self, params): - """ - Validate that the parameters passed to get_all_versions are valid. - Overridden by subclasses that allow a different set of parameters. - - :type params: dict - :param params: Parameters to validate. - """ - self.validate_kwarg_names( - params, ['maxkeys', 'max_keys', 'prefix', 'key_marker', - 'version_id_marker', 'delimiter', 'encoding_type']) - - def get_all_multipart_uploads(self, headers=None, **params): - """ - A lower-level, version-aware method for listing active - MultiPart uploads for a bucket. This closely models the - actual S3 API and requires you to manually handle the paging - of results. For a higher-level method that handles the - details of paging for you, you can use the list method. - - :type max_uploads: int - :param max_uploads: The maximum number of uploads to retrieve. - Default value is 1000. - - :type key_marker: string - :param key_marker: Together with upload_id_marker, this - parameter specifies the multipart upload after which - listing should begin. If upload_id_marker is not - specified, only the keys lexicographically greater than - the specified key_marker will be included in the list. - - If upload_id_marker is specified, any multipart uploads - for a key equal to the key_marker might also be included, - provided those multipart uploads have upload IDs - lexicographically greater than the specified - upload_id_marker. - - :type upload_id_marker: string - :param upload_id_marker: Together with key-marker, specifies - the multipart upload after which listing should begin. If - key_marker is not specified, the upload_id_marker - parameter is ignored. Otherwise, any multipart uploads - for a key equal to the key_marker might be included in the - list only if they have an upload ID lexicographically - greater than the specified upload_id_marker. - - :type encoding_type: string - :param encoding_type: Requests Amazon S3 to encode the response and - specifies the encoding method to use. - - An object key can contain any Unicode character; however, XML 1.0 - parser cannot parse some characters, such as characters with an - ASCII value from 0 to 10. For characters that are not supported in - XML 1.0, you can add this parameter to request that Amazon S3 - encode the keys in the response. - - Valid options: ``url`` - - :type delimiter: string - :param delimiter: Character you use to group keys. - All keys that contain the same string between the prefix, if - specified, and the first occurrence of the delimiter after the - prefix are grouped under a single result element, CommonPrefixes. - If you don't specify the prefix parameter, then the substring - starts at the beginning of the key. The keys that are grouped - under CommonPrefixes result element are not returned elsewhere - in the response. - - :type prefix: string - :param prefix: Lists in-progress uploads only for those keys that - begin with the specified prefix. You can use prefixes to separate - a bucket into different grouping of keys. (You can think of using - prefix to make groups in the same way you'd use a folder in a - file system.) - - :rtype: ResultSet - :return: The result from S3 listing the uploads requested - - """ - self.validate_kwarg_names(params, ['max_uploads', 'key_marker', - 'upload_id_marker', 'encoding_type', - 'delimiter', 'prefix']) - return self._get_all([('Upload', MultiPartUpload), - ('CommonPrefixes', Prefix)], - 'uploads', headers, **params) - - def new_key(self, key_name=None): - """ - Creates a new key - - :type key_name: string - :param key_name: The name of the key to create - - :rtype: :class:`boto.s3.key.Key` or subclass - :returns: An instance of the newly created key object - """ - if not key_name: - raise ValueError('Empty key names are not allowed') - return self.key_class(self, key_name) - - def generate_url(self, expires_in, method='GET', headers=None, - force_http=False, response_headers=None, - expires_in_absolute=False): - return self.connection.generate_url(expires_in, method, self.name, - headers=headers, - force_http=force_http, - response_headers=response_headers, - expires_in_absolute=expires_in_absolute) - - def delete_keys(self, keys, quiet=False, mfa_token=None, headers=None): - """ - Deletes a set of keys using S3's Multi-object delete API. If a - VersionID is specified for that key then that version is removed. - Returns a MultiDeleteResult Object, which contains Deleted - and Error elements for each key you ask to delete. - - :type keys: list - :param keys: A list of either key_names or (key_name, versionid) pairs - or a list of Key instances. - - :type quiet: boolean - :param quiet: In quiet mode the response includes only keys - where the delete operation encountered an error. For a - successful deletion, the operation does not return any - information about the delete in the response body. - - :type mfa_token: tuple or list of strings - :param mfa_token: A tuple or list consisting of the serial - number from the MFA device and the current value of the - six-digit token associated with the device. This value is - required anytime you are deleting versioned objects from a - bucket that has the MFADelete option on the bucket. - - :returns: An instance of MultiDeleteResult - """ - ikeys = iter(keys) - result = MultiDeleteResult(self) - provider = self.connection.provider - query_args = 'delete' - - def delete_keys2(hdrs): - hdrs = hdrs or {} - data = u"""<?xml version="1.0" encoding="UTF-8"?>""" - data += u"<Delete>" - if quiet: - data += u"<Quiet>true</Quiet>" - count = 0 - while count < 1000: - try: - key = next(ikeys) - except StopIteration: - break - if isinstance(key, six.string_types): - key_name = key - version_id = None - elif isinstance(key, tuple) and len(key) == 2: - key_name, version_id = key - elif (isinstance(key, Key) or isinstance(key, DeleteMarker)) and key.name: - key_name = key.name - version_id = key.version_id - else: - if isinstance(key, Prefix): - key_name = key.name - code = 'PrefixSkipped' # Don't delete Prefix - else: - key_name = repr(key) # try get a string - code = 'InvalidArgument' # other unknown type - message = 'Invalid. No delete action taken for this object.' - error = Error(key_name, code=code, message=message) - result.errors.append(error) - continue - count += 1 - data += u"<Object><Key>%s</Key>" % xml.sax.saxutils.escape(key_name) - if version_id: - data += u"<VersionId>%s</VersionId>" % version_id - data += u"</Object>" - data += u"</Delete>" - if count <= 0: - return False # no more - data = data.encode('utf-8') - fp = BytesIO(data) - md5 = boto.utils.compute_md5(fp) - hdrs['Content-MD5'] = md5[1] - hdrs['Content-Type'] = 'text/xml' - if mfa_token: - hdrs[provider.mfa_header] = ' '.join(mfa_token) - response = self.connection.make_request('POST', self.name, - headers=hdrs, - query_args=query_args, - data=data) - body = response.read() - if response.status == 200: - h = handler.XmlHandler(result, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - return count >= 1000 # more? - else: - raise provider.storage_response_error(response.status, - response.reason, - body) - while delete_keys2(headers): - pass - return result - - def delete_key(self, key_name, headers=None, version_id=None, - mfa_token=None): - """ - Deletes a key from the bucket. If a version_id is provided, - only that version of the key will be deleted. - - :type key_name: string - :param key_name: The key name to delete - - :type version_id: string - :param version_id: The version ID (optional) - - :type mfa_token: tuple or list of strings - :param mfa_token: A tuple or list consisting of the serial - number from the MFA device and the current value of the - six-digit token associated with the device. This value is - required anytime you are deleting versioned objects from a - bucket that has the MFADelete option on the bucket. - - :rtype: :class:`boto.s3.key.Key` or subclass - :returns: A key object holding information on what was - deleted. The Caller can see if a delete_marker was - created or removed and what version_id the delete created - or removed. - """ - if not key_name: - raise ValueError('Empty key names are not allowed') - return self._delete_key_internal(key_name, headers=headers, - version_id=version_id, - mfa_token=mfa_token, - query_args_l=None) - - def _delete_key_internal(self, key_name, headers=None, version_id=None, - mfa_token=None, query_args_l=None): - query_args_l = query_args_l or [] - provider = self.connection.provider - if version_id: - query_args_l.append('versionId=%s' % version_id) - query_args = '&'.join(query_args_l) or None - if mfa_token: - if not headers: - headers = {} - headers[provider.mfa_header] = ' '.join(mfa_token) - response = self.connection.make_request('DELETE', self.name, key_name, - headers=headers, - query_args=query_args) - body = response.read() - if response.status != 204: - raise provider.storage_response_error(response.status, - response.reason, body) - else: - # return a key object with information on what was deleted. - k = self.key_class(self) - k.name = key_name - k.handle_version_headers(response) - k.handle_addl_headers(response.getheaders()) - return k - - def copy_key(self, new_key_name, src_bucket_name, - src_key_name, metadata=None, src_version_id=None, - storage_class='STANDARD', preserve_acl=False, - encrypt_key=False, headers=None, query_args=None): - """ - Create a new key in the bucket by copying another existing key. - - :type new_key_name: string - :param new_key_name: The name of the new key - - :type src_bucket_name: string - :param src_bucket_name: The name of the source bucket - - :type src_key_name: string - :param src_key_name: The name of the source key - - :type src_version_id: string - :param src_version_id: The version id for the key. This param - is optional. If not specified, the newest version of the - key will be copied. - - :type metadata: dict - :param metadata: Metadata to be associated with new key. If - metadata is supplied, it will replace the metadata of the - source key being copied. If no metadata is supplied, the - source key's metadata will be copied to the new key. - - :type storage_class: string - :param storage_class: The storage class of the new key. By - default, the new key will use the standard storage class. - Possible values are: STANDARD | REDUCED_REDUNDANCY - - :type preserve_acl: bool - :param preserve_acl: If True, the ACL from the source key will - be copied to the destination key. If False, the - destination key will have the default ACL. Note that - preserving the ACL in the new key object will require two - additional API calls to S3, one to retrieve the current - ACL and one to set that ACL on the new object. If you - don't care about the ACL, a value of False will be - significantly more efficient. - - :type encrypt_key: bool - :param encrypt_key: If True, the new copy of the object will - be encrypted on the server-side by S3 and will be stored - in an encrypted form while at rest in S3. - - :type headers: dict - :param headers: A dictionary of header name/value pairs. - - :type query_args: string - :param query_args: A string of additional querystring arguments - to append to the request - - :rtype: :class:`boto.s3.key.Key` or subclass - :returns: An instance of the newly created key object - """ - headers = headers or {} - provider = self.connection.provider - src_key_name = boto.utils.get_utf8_value(src_key_name) - if preserve_acl: - if self.name == src_bucket_name: - src_bucket = self - else: - src_bucket = self.connection.get_bucket( - src_bucket_name, validate=False, headers=headers) - acl = src_bucket.get_xml_acl(src_key_name, headers=headers) - if encrypt_key: - headers[provider.server_side_encryption_header] = 'AES256' - src = '%s/%s' % (src_bucket_name, urllib.parse.quote(src_key_name)) - if src_version_id: - src += '?versionId=%s' % src_version_id - headers[provider.copy_source_header] = str(src) - # make sure storage_class_header key exists before accessing it - if provider.storage_class_header and storage_class: - headers[provider.storage_class_header] = storage_class - if metadata is not None: - headers[provider.metadata_directive_header] = 'REPLACE' - headers = boto.utils.merge_meta(headers, metadata, provider) - elif not query_args: # Can't use this header with multi-part copy. - headers[provider.metadata_directive_header] = 'COPY' - response = self.connection.make_request('PUT', self.name, new_key_name, - headers=headers, - query_args=query_args) - body = response.read() - if response.status == 200: - key = self.new_key(new_key_name) - h = handler.XmlHandler(key, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - if hasattr(key, 'Error'): - raise provider.storage_copy_error(key.Code, key.Message, body) - key.handle_version_headers(response) - key.handle_addl_headers(response.getheaders()) - if preserve_acl: - self.set_xml_acl(acl, new_key_name) - return key - else: - raise provider.storage_response_error(response.status, - response.reason, body) - - def set_canned_acl(self, acl_str, key_name='', headers=None, - version_id=None): - assert acl_str in CannedACLStrings - - if headers: - headers[self.connection.provider.acl_header] = acl_str - else: - headers = {self.connection.provider.acl_header: acl_str} - - query_args = 'acl' - if version_id: - query_args += '&versionId=%s' % version_id - response = self.connection.make_request('PUT', self.name, key_name, - headers=headers, query_args=query_args) - body = response.read() - if response.status != 200: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def get_xml_acl(self, key_name='', headers=None, version_id=None): - query_args = 'acl' - if version_id: - query_args += '&versionId=%s' % version_id - response = self.connection.make_request('GET', self.name, key_name, - query_args=query_args, - headers=headers) - body = response.read() - if response.status != 200: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - return body - - def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None, - query_args='acl'): - if version_id: - query_args += '&versionId=%s' % version_id - if not isinstance(acl_str, bytes): - acl_str = acl_str.encode('utf-8') - response = self.connection.make_request('PUT', self.name, key_name, - data=acl_str, - query_args=query_args, - headers=headers) - body = response.read() - if response.status != 200: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None): - if isinstance(acl_or_str, Policy): - self.set_xml_acl(acl_or_str.to_xml(), key_name, - headers, version_id) - else: - self.set_canned_acl(acl_or_str, key_name, - headers, version_id) - - def get_acl(self, key_name='', headers=None, version_id=None): - query_args = 'acl' - if version_id: - query_args += '&versionId=%s' % version_id - response = self.connection.make_request('GET', self.name, key_name, - query_args=query_args, - headers=headers) - body = response.read() - if response.status == 200: - policy = Policy(self) - h = handler.XmlHandler(policy, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - return policy - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_subresource(self, subresource, value, key_name='', headers=None, - version_id=None): - """ - Set a subresource for a bucket or key. - - :type subresource: string - :param subresource: The subresource to set. - - :type value: string - :param value: The value of the subresource. - - :type key_name: string - :param key_name: The key to operate on, or None to operate on the - bucket. - - :type headers: dict - :param headers: Additional HTTP headers to include in the request. - - :type src_version_id: string - :param src_version_id: Optional. The version id of the key to - operate on. If not specified, operate on the newest - version. - """ - if not subresource: - raise TypeError('set_subresource called with subresource=None') - query_args = subresource - if version_id: - query_args += '&versionId=%s' % version_id - if not isinstance(value, bytes): - value = value.encode('utf-8') - response = self.connection.make_request('PUT', self.name, key_name, - data=value, - query_args=query_args, - headers=headers) - body = response.read() - if response.status != 200: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def get_subresource(self, subresource, key_name='', headers=None, - version_id=None): - """ - Get a subresource for a bucket or key. - - :type subresource: string - :param subresource: The subresource to get. - - :type key_name: string - :param key_name: The key to operate on, or None to operate on the - bucket. - - :type headers: dict - :param headers: Additional HTTP headers to include in the request. - - :type src_version_id: string - :param src_version_id: Optional. The version id of the key to - operate on. If not specified, operate on the newest - version. - - :rtype: string - :returns: The value of the subresource. - """ - if not subresource: - raise TypeError('get_subresource called with subresource=None') - query_args = subresource - if version_id: - query_args += '&versionId=%s' % version_id - response = self.connection.make_request('GET', self.name, key_name, - query_args=query_args, - headers=headers) - body = response.read() - if response.status != 200: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - return body - - def make_public(self, recursive=False, headers=None): - self.set_canned_acl('public-read', headers=headers) - if recursive: - for key in self: - self.set_canned_acl('public-read', key.name, headers=headers) - - def add_email_grant(self, permission, email_address, - recursive=False, headers=None): - """ - Convenience method that provides a quick way to add an email grant - to a bucket. This method retrieves the current ACL, creates a new - grant based on the parameters passed in, adds that grant to the ACL - and then PUT's the new ACL back to S3. - - :type permission: string - :param permission: The permission being granted. Should be one of: - (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). - - :type email_address: string - :param email_address: The email address associated with the AWS - account your are granting the permission to. - - :type recursive: boolean - :param recursive: A boolean value to controls whether the - command will apply the grant to all keys within the bucket - or not. The default value is False. By passing a True - value, the call will iterate through all keys in the - bucket and apply the same grant to each key. CAUTION: If - you have a lot of keys, this could take a long time! - """ - if permission not in S3Permissions: - raise self.connection.provider.storage_permissions_error( - 'Unknown Permission: %s' % permission) - policy = self.get_acl(headers=headers) - policy.acl.add_email_grant(permission, email_address) - self.set_acl(policy, headers=headers) - if recursive: - for key in self: - key.add_email_grant(permission, email_address, headers=headers) - - def add_user_grant(self, permission, user_id, recursive=False, - headers=None, display_name=None): - """ - Convenience method that provides a quick way to add a canonical - user grant to a bucket. This method retrieves the current ACL, - creates a new grant based on the parameters passed in, adds that - grant to the ACL and then PUT's the new ACL back to S3. - - :type permission: string - :param permission: The permission being granted. Should be one of: - (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). - - :type user_id: string - :param user_id: The canonical user id associated with the AWS - account your are granting the permission to. - - :type recursive: boolean - :param recursive: A boolean value to controls whether the - command will apply the grant to all keys within the bucket - or not. The default value is False. By passing a True - value, the call will iterate through all keys in the - bucket and apply the same grant to each key. CAUTION: If - you have a lot of keys, this could take a long time! - - :type display_name: string - :param display_name: An option string containing the user's - Display Name. Only required on Walrus. - """ - if permission not in S3Permissions: - raise self.connection.provider.storage_permissions_error( - 'Unknown Permission: %s' % permission) - policy = self.get_acl(headers=headers) - policy.acl.add_user_grant(permission, user_id, - display_name=display_name) - self.set_acl(policy, headers=headers) - if recursive: - for key in self: - key.add_user_grant(permission, user_id, headers=headers, - display_name=display_name) - - def list_grants(self, headers=None): - policy = self.get_acl(headers=headers) - return policy.acl.grants - - def get_location(self, headers=None): - """ - Returns the LocationConstraint for the bucket. - - :rtype: str - :return: The LocationConstraint for the bucket or the empty - string if no constraint was specified when bucket was created. - """ - response = self.connection.make_request('GET', self.name, - headers=headers, - query_args='location') - body = response.read() - if response.status == 200: - rs = ResultSet(self) - h = handler.XmlHandler(rs, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - return rs.LocationConstraint - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_xml_logging(self, logging_str, headers=None): - """ - Set logging on a bucket directly to the given xml string. - - :type logging_str: unicode string - :param logging_str: The XML for the bucketloggingstatus which - will be set. The string will be converted to utf-8 before - it is sent. Usually, you will obtain this XML from the - BucketLogging object. - - :rtype: bool - :return: True if ok or raises an exception. - """ - body = logging_str - if not isinstance(body, bytes): - body = body.encode('utf-8') - response = self.connection.make_request('PUT', self.name, data=body, - query_args='logging', headers=headers) - body = response.read() - if response.status == 200: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def enable_logging(self, target_bucket, target_prefix='', - grants=None, headers=None): - """ - Enable logging on a bucket. - - :type target_bucket: bucket or string - :param target_bucket: The bucket to log to. - - :type target_prefix: string - :param target_prefix: The prefix which should be prepended to the - generated log files written to the target_bucket. - - :type grants: list of Grant objects - :param grants: A list of extra permissions which will be granted on - the log files which are created. - - :rtype: bool - :return: True if ok or raises an exception. - """ - if isinstance(target_bucket, Bucket): - target_bucket = target_bucket.name - blogging = BucketLogging(target=target_bucket, prefix=target_prefix, - grants=grants) - return self.set_xml_logging(blogging.to_xml(), headers=headers) - - def disable_logging(self, headers=None): - """ - Disable logging on a bucket. - - :rtype: bool - :return: True if ok or raises an exception. - """ - blogging = BucketLogging() - return self.set_xml_logging(blogging.to_xml(), headers=headers) - - def get_logging_status(self, headers=None): - """ - Get the logging status for this bucket. - - :rtype: :class:`boto.s3.bucketlogging.BucketLogging` - :return: A BucketLogging object for this bucket. - """ - response = self.connection.make_request('GET', self.name, - query_args='logging', headers=headers) - body = response.read() - if response.status == 200: - blogging = BucketLogging() - h = handler.XmlHandler(blogging, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - return blogging - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_as_logging_target(self, headers=None): - """ - Setup the current bucket as a logging target by granting the necessary - permissions to the LogDelivery group to write log files to this bucket. - """ - policy = self.get_acl(headers=headers) - g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup) - g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup) - policy.acl.add_grant(g1) - policy.acl.add_grant(g2) - self.set_acl(policy, headers=headers) - - def get_request_payment(self, headers=None): - response = self.connection.make_request('GET', self.name, - query_args='requestPayment', headers=headers) - body = response.read() - if response.status == 200: - return body - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_request_payment(self, payer='BucketOwner', headers=None): - body = self.BucketPaymentBody % payer - response = self.connection.make_request('PUT', self.name, data=body, - query_args='requestPayment', headers=headers) - body = response.read() - if response.status == 200: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def configure_versioning(self, versioning, mfa_delete=False, - mfa_token=None, headers=None): - """ - Configure versioning for this bucket. - - ..note:: This feature is currently in beta. - - :type versioning: bool - :param versioning: A boolean indicating whether version is - enabled (True) or disabled (False). - - :type mfa_delete: bool - :param mfa_delete: A boolean indicating whether the - Multi-Factor Authentication Delete feature is enabled - (True) or disabled (False). If mfa_delete is enabled then - all Delete operations will require the token from your MFA - device to be passed in the request. - - :type mfa_token: tuple or list of strings - :param mfa_token: A tuple or list consisting of the serial - number from the MFA device and the current value of the - six-digit token associated with the device. This value is - required when you are changing the status of the MfaDelete - property of the bucket. - """ - if versioning: - ver = 'Enabled' - else: - ver = 'Suspended' - if mfa_delete: - mfa = 'Enabled' - else: - mfa = 'Disabled' - body = self.VersioningBody % (ver, mfa) - if mfa_token: - if not headers: - headers = {} - provider = self.connection.provider - headers[provider.mfa_header] = ' '.join(mfa_token) - response = self.connection.make_request('PUT', self.name, data=body, - query_args='versioning', headers=headers) - body = response.read() - if response.status == 200: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def get_versioning_status(self, headers=None): - """ - Returns the current status of versioning on the bucket. - - :rtype: dict - :returns: A dictionary containing a key named 'Versioning' - that can have a value of either Enabled, Disabled, or - Suspended. Also, if MFADelete has ever been enabled on the - bucket, the dictionary will contain a key named - 'MFADelete' which will have a value of either Enabled or - Suspended. - """ - response = self.connection.make_request('GET', self.name, - query_args='versioning', headers=headers) - body = response.read() - if not isinstance(body, six.string_types): - body = body.decode('utf-8') - boto.log.debug(body) - if response.status == 200: - d = {} - ver = re.search(self.VersionRE, body) - if ver: - d['Versioning'] = ver.group(1) - mfa = re.search(self.MFADeleteRE, body) - if mfa: - d['MfaDelete'] = mfa.group(1) - return d - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def configure_lifecycle(self, lifecycle_config, headers=None): - """ - Configure lifecycle for this bucket. - - :type lifecycle_config: :class:`boto.s3.lifecycle.Lifecycle` - :param lifecycle_config: The lifecycle configuration you want - to configure for this bucket. - """ - xml = lifecycle_config.to_xml() - #xml = xml.encode('utf-8') - fp = StringIO(xml) - md5 = boto.utils.compute_md5(fp) - if headers is None: - headers = {} - headers['Content-MD5'] = md5[1] - headers['Content-Type'] = 'text/xml' - response = self.connection.make_request('PUT', self.name, - data=fp.getvalue(), - query_args='lifecycle', - headers=headers) - body = response.read() - if response.status == 200: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def get_lifecycle_config(self, headers=None): - """ - Returns the current lifecycle configuration on the bucket. - - :rtype: :class:`boto.s3.lifecycle.Lifecycle` - :returns: A LifecycleConfig object that describes all current - lifecycle rules in effect for the bucket. - """ - response = self.connection.make_request('GET', self.name, - query_args='lifecycle', headers=headers) - body = response.read() - boto.log.debug(body) - if response.status == 200: - lifecycle = Lifecycle() - h = handler.XmlHandler(lifecycle, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - return lifecycle - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def delete_lifecycle_configuration(self, headers=None): - """ - Removes all lifecycle configuration from the bucket. - """ - response = self.connection.make_request('DELETE', self.name, - query_args='lifecycle', - headers=headers) - body = response.read() - boto.log.debug(body) - if response.status == 204: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def configure_website(self, suffix=None, error_key=None, - redirect_all_requests_to=None, - routing_rules=None, - headers=None): - """ - Configure this bucket to act as a website - - :type suffix: str - :param suffix: Suffix that is appended to a request that is for a - "directory" on the website endpoint (e.g. if the suffix is - index.html and you make a request to samplebucket/images/ - the data that is returned will be for the object with the - key name images/index.html). The suffix must not be empty - and must not include a slash character. - - :type error_key: str - :param error_key: The object key name to use when a 4XX class - error occurs. This is optional. - - :type redirect_all_requests_to: :class:`boto.s3.website.RedirectLocation` - :param redirect_all_requests_to: Describes the redirect behavior for - every request to this bucket's website endpoint. If this value is - non None, no other values are considered when configuring the - website configuration for the bucket. This is an instance of - ``RedirectLocation``. - - :type routing_rules: :class:`boto.s3.website.RoutingRules` - :param routing_rules: Object which specifies conditions - and redirects that apply when the conditions are met. - - """ - config = website.WebsiteConfiguration( - suffix, error_key, redirect_all_requests_to, - routing_rules) - return self.set_website_configuration(config, headers=headers) - - def set_website_configuration(self, config, headers=None): - """ - :type config: boto.s3.website.WebsiteConfiguration - :param config: Configuration data - """ - return self.set_website_configuration_xml(config.to_xml(), - headers=headers) - - - def set_website_configuration_xml(self, xml, headers=None): - """Upload xml website configuration""" - response = self.connection.make_request('PUT', self.name, data=xml, - query_args='website', - headers=headers) - body = response.read() - if response.status == 200: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def get_website_configuration(self, headers=None): - """ - Returns the current status of website configuration on the bucket. - - :rtype: dict - :returns: A dictionary containing a Python representation - of the XML response from S3. The overall structure is: - - * WebsiteConfiguration - - * IndexDocument - - * Suffix : suffix that is appended to request that - is for a "directory" on the website endpoint - * ErrorDocument - - * Key : name of object to serve when an error occurs - - """ - return self.get_website_configuration_with_xml(headers)[0] - - def get_website_configuration_obj(self, headers=None): - """Get the website configuration as a - :class:`boto.s3.website.WebsiteConfiguration` object. - """ - config_xml = self.get_website_configuration_xml(headers=headers) - config = website.WebsiteConfiguration() - h = handler.XmlHandler(config, self) - xml.sax.parseString(config_xml, h) - return config - - def get_website_configuration_with_xml(self, headers=None): - """ - Returns the current status of website configuration on the bucket as - unparsed XML. - - :rtype: 2-Tuple - :returns: 2-tuple containing: - - 1) A dictionary containing a Python representation \ - of the XML response. The overall structure is: - - * WebsiteConfiguration - - * IndexDocument - - * Suffix : suffix that is appended to request that \ - is for a "directory" on the website endpoint - - * ErrorDocument - - * Key : name of object to serve when an error occurs - - - 2) unparsed XML describing the bucket's website configuration - - """ - - body = self.get_website_configuration_xml(headers=headers) - e = boto.jsonresponse.Element() - h = boto.jsonresponse.XmlHandler(e, None) - h.parse(body) - return e, body - - def get_website_configuration_xml(self, headers=None): - """Get raw website configuration xml""" - response = self.connection.make_request('GET', self.name, - query_args='website', headers=headers) - body = response.read().decode('utf-8') - boto.log.debug(body) - - if response.status != 200: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - return body - - def delete_website_configuration(self, headers=None): - """ - Removes all website configuration from the bucket. - """ - response = self.connection.make_request('DELETE', self.name, - query_args='website', headers=headers) - body = response.read() - boto.log.debug(body) - if response.status == 204: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def get_website_endpoint(self): - """ - Returns the fully qualified hostname to use is you want to access this - bucket as a website. This doesn't validate whether the bucket has - been correctly configured as a website or not. - """ - l = [self.name] - l.append(S3WebsiteEndpointTranslate.translate_region(self.get_location())) - l.append('.'.join(self.connection.host.split('.')[-2:])) - return '.'.join(l) - - def get_policy(self, headers=None): - """ - Returns the JSON policy associated with the bucket. The policy - is returned as an uninterpreted JSON string. - """ - response = self.connection.make_request('GET', self.name, - query_args='policy', headers=headers) - body = response.read() - if response.status == 200: - return body - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_policy(self, policy, headers=None): - """ - Add or replace the JSON policy associated with the bucket. - - :type policy: str - :param policy: The JSON policy as a string. - """ - response = self.connection.make_request('PUT', self.name, - data=policy, - query_args='policy', - headers=headers) - body = response.read() - if response.status >= 200 and response.status <= 204: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def delete_policy(self, headers=None): - response = self.connection.make_request('DELETE', self.name, - data='/?policy', - query_args='policy', - headers=headers) - body = response.read() - if response.status >= 200 and response.status <= 204: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_cors_xml(self, cors_xml, headers=None): - """ - Set the CORS (Cross-Origin Resource Sharing) for a bucket. - - :type cors_xml: str - :param cors_xml: The XML document describing your desired - CORS configuration. See the S3 documentation for details - of the exact syntax required. - """ - fp = StringIO(cors_xml) - md5 = boto.utils.compute_md5(fp) - if headers is None: - headers = {} - headers['Content-MD5'] = md5[1] - headers['Content-Type'] = 'text/xml' - response = self.connection.make_request('PUT', self.name, - data=fp.getvalue(), - query_args='cors', - headers=headers) - body = response.read() - if response.status == 200: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_cors(self, cors_config, headers=None): - """ - Set the CORS for this bucket given a boto CORSConfiguration - object. - - :type cors_config: :class:`boto.s3.cors.CORSConfiguration` - :param cors_config: The CORS configuration you want - to configure for this bucket. - """ - return self.set_cors_xml(cors_config.to_xml()) - - def get_cors_xml(self, headers=None): - """ - Returns the current CORS configuration on the bucket as an - XML document. - """ - response = self.connection.make_request('GET', self.name, - query_args='cors', headers=headers) - body = response.read() - boto.log.debug(body) - if response.status == 200: - return body - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def get_cors(self, headers=None): - """ - Returns the current CORS configuration on the bucket. - - :rtype: :class:`boto.s3.cors.CORSConfiguration` - :returns: A CORSConfiguration object that describes all current - CORS rules in effect for the bucket. - """ - body = self.get_cors_xml(headers) - cors = CORSConfiguration() - h = handler.XmlHandler(cors, self) - xml.sax.parseString(body, h) - return cors - - def delete_cors(self, headers=None): - """ - Removes all CORS configuration from the bucket. - """ - response = self.connection.make_request('DELETE', self.name, - query_args='cors', - headers=headers) - body = response.read() - boto.log.debug(body) - if response.status == 204: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def initiate_multipart_upload(self, key_name, headers=None, - reduced_redundancy=False, - metadata=None, encrypt_key=False, - policy=None): - """ - Start a multipart upload operation. - - .. note:: - - Note: After you initiate multipart upload and upload one or more - parts, you must either complete or abort multipart upload in order - to stop getting charged for storage of the uploaded parts. Only - after you either complete or abort multipart upload, Amazon S3 - frees up the parts storage and stops charging you for the parts - storage. - - :type key_name: string - :param key_name: The name of the key that will ultimately - result from this multipart upload operation. This will be - exactly as the key appears in the bucket after the upload - process has been completed. - - :type headers: dict - :param headers: Additional HTTP headers to send and store with the - resulting key in S3. - - :type reduced_redundancy: boolean - :param reduced_redundancy: In multipart uploads, the storage - class is specified when initiating the upload, not when - uploading individual parts. So if you want the resulting - key to use the reduced redundancy storage class set this - flag when you initiate the upload. - - :type metadata: dict - :param metadata: Any metadata that you would like to set on the key - that results from the multipart upload. - - :type encrypt_key: bool - :param encrypt_key: If True, the new copy of the object will - be encrypted on the server-side by S3 and will be stored - in an encrypted form while at rest in S3. - - :type policy: :class:`boto.s3.acl.CannedACLStrings` - :param policy: A canned ACL policy that will be applied to the - new key (once completed) in S3. - """ - query_args = 'uploads' - provider = self.connection.provider - headers = headers or {} - if policy: - headers[provider.acl_header] = policy - if reduced_redundancy: - storage_class_header = provider.storage_class_header - if storage_class_header: - headers[storage_class_header] = 'REDUCED_REDUNDANCY' - # TODO: what if the provider doesn't support reduced redundancy? - # (see boto.s3.key.Key.set_contents_from_file) - if encrypt_key: - headers[provider.server_side_encryption_header] = 'AES256' - if metadata is None: - metadata = {} - - headers = boto.utils.merge_meta(headers, metadata, - self.connection.provider) - response = self.connection.make_request('POST', self.name, key_name, - query_args=query_args, - headers=headers) - body = response.read() - boto.log.debug(body) - if response.status == 200: - resp = MultiPartUpload(self) - h = handler.XmlHandler(resp, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - return resp - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def complete_multipart_upload(self, key_name, upload_id, - xml_body, headers=None): - """ - Complete a multipart upload operation. - """ - query_args = 'uploadId=%s' % upload_id - if headers is None: - headers = {} - headers['Content-Type'] = 'text/xml' - response = self.connection.make_request('POST', self.name, key_name, - query_args=query_args, - headers=headers, data=xml_body) - contains_error = False - body = response.read().decode('utf-8') - # Some errors will be reported in the body of the response - # even though the HTTP response code is 200. This check - # does a quick and dirty peek in the body for an error element. - if body.find('<Error>') > 0: - contains_error = True - boto.log.debug(body) - if response.status == 200 and not contains_error: - resp = CompleteMultiPartUpload(self) - h = handler.XmlHandler(resp, self) - if not isinstance(body, bytes): - body = body.encode('utf-8') - xml.sax.parseString(body, h) - # Use a dummy key to parse various response headers - # for versioning, encryption info and then explicitly - # set the completed MPU object values from key. - k = self.key_class(self) - k.handle_version_headers(response) - k.handle_encryption_headers(response) - resp.version_id = k.version_id - resp.encrypted = k.encrypted - return resp - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def cancel_multipart_upload(self, key_name, upload_id, headers=None): - """ - To verify that all parts have been removed, so you don't get charged - for the part storage, you should call the List Parts operation and - ensure the parts list is empty. - """ - query_args = 'uploadId=%s' % upload_id - response = self.connection.make_request('DELETE', self.name, key_name, - query_args=query_args, - headers=headers) - body = response.read() - boto.log.debug(body) - if response.status != 204: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def delete(self, headers=None): - return self.connection.delete_bucket(self.name, headers=headers) - - def get_tags(self, headers=None): - response = self.get_xml_tags(headers) - tags = Tags() - h = handler.XmlHandler(tags, self) - if not isinstance(response, bytes): - response = response.encode('utf-8') - xml.sax.parseString(response, h) - return tags - - def get_xml_tags(self, headers=None): - response = self.connection.make_request('GET', self.name, - query_args='tagging', - headers=headers) - body = response.read() - if response.status == 200: - return body - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - - def set_xml_tags(self, tag_str, headers=None, query_args='tagging'): - if headers is None: - headers = {} - md5 = boto.utils.compute_md5(StringIO(tag_str)) - headers['Content-MD5'] = md5[1] - headers['Content-Type'] = 'text/xml' - if not isinstance(tag_str, bytes): - tag_str = tag_str.encode('utf-8') - response = self.connection.make_request('PUT', self.name, - data=tag_str, - query_args=query_args, - headers=headers) - body = response.read() - if response.status != 204: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body) - return True - - def set_tags(self, tags, headers=None): - return self.set_xml_tags(tags.to_xml(), headers=headers) - - def delete_tags(self, headers=None): - response = self.connection.make_request('DELETE', self.name, - query_args='tagging', - headers=headers) - body = response.read() - boto.log.debug(body) - if response.status == 204: - return True - else: - raise self.connection.provider.storage_response_error( - response.status, response.reason, body)