Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2525)

Unified Diff: boto/s3/bucket.py

Issue 8386013: Merging in latest boto. (Closed) Base URL: svn://svn.chromium.org/boto
Patch Set: Redoing vendor drop by deleting and then merging. Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « boto/s3/acl.py ('k') | boto/s3/bucketlistresultset.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: boto/s3/bucket.py
diff --git a/boto/s3/bucket.py b/boto/s3/bucket.py
index c1b38e983cf8904e611726bb76130222355d37d1..144edbff6d9272c86b8f458a74da04d2181aee8d 100644
--- a/boto/s3/bucket.py
+++ b/boto/s3/bucket.py
@@ -23,13 +23,11 @@
import boto
from boto import handler
-from boto.provider import Provider
from boto.resultset import ResultSet
-from boto.s3.acl import ACL, Policy, CannedACLStrings, Grant
+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.user import User
from boto.s3.multipart import MultiPartUpload
from boto.s3.multipart import CompleteMultiPartUpload
from boto.s3.bucketlistresultset import BucketListResultSet
@@ -48,6 +46,7 @@ class S3WebsiteEndpointTranslate:
trans_region['EU'] = 's3-website-eu-west-1'
trans_region['us-west-1'] = 's3-website-us-west-1'
+ trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1'
trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1'
@classmethod
@@ -106,7 +105,7 @@ class Bucket(object):
return iter(BucketListResultSet(self))
def __contains__(self, key_name):
- return not (self.get_key(key_name) is None)
+ return not (self.get_key(key_name) is None)
def startElement(self, name, attrs, connection):
return None
@@ -175,10 +174,19 @@ class Bucket(object):
k.content_type = response.getheader('content-type')
k.content_encoding = response.getheader('content-encoding')
k.last_modified = response.getheader('last-modified')
- k.size = int(response.getheader('content-length'))
+ # 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.cache_control = response.getheader('cache-control')
k.name = key_name
k.handle_version_headers(response)
+ k.handle_encryption_headers(response)
return k
else:
if response.status == 404:
@@ -281,7 +289,7 @@ class Bucket(object):
def _get_all(self, element_map, initial_query_string='',
headers=None, **params):
l = []
- for k,v in params.items():
+ for k, v in params.items():
k = k.replace('_', '-')
if k == 'maxkeys':
k = 'max-keys'
@@ -294,7 +302,8 @@ class Bucket(object):
else:
s = initial_query_string
response = self.connection.make_request('GET', self.name,
- headers=headers, query_args=s)
+ headers=headers,
+ query_args=s)
body = response.read()
boto.log.debug(body)
if response.status == 200:
@@ -434,11 +443,12 @@ class Bucket(object):
"""
return self.key_class(self, key_name)
- def generate_url(self, expires_in, method='GET',
- headers=None, force_http=False):
+ def generate_url(self, expires_in, method='GET', headers=None,
+ force_http=False, response_headers=None):
return self.connection.generate_url(expires_in, method, self.name,
headers=headers,
- force_http=force_http)
+ force_http=force_http,
+ response_headers=response_headers)
def delete_key(self, key_name, headers=None,
version_id=None, mfa_token=None):
@@ -479,7 +489,8 @@ class Bucket(object):
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):
+ storage_class='STANDARD', preserve_acl=False,
+ encrypt_key=False):
"""
Create a new key in the bucket by copying another existing key.
@@ -524,21 +535,34 @@ class Bucket(object):
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.
+
:rtype: :class:`boto.s3.key.Key` or subclass
:returns: An instance of the newly created key object
"""
+ headers = {}
+ provider = self.connection.provider
+ src_key_name = boto.utils.get_utf8_value(src_key_name)
if preserve_acl:
- acl = self.get_xml_acl(src_key_name)
+ if self.name == src_bucket_name:
+ src_bucket = self
+ else:
+ src_bucket = self.connection.get_bucket(src_bucket_name)
+ acl = src_bucket.get_xml_acl(src_key_name)
+ if encrypt_key:
+ headers[provider.server_side_encryption_header] = 'AES256'
src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name))
if src_version_id:
src += '?version_id=%s' % src_version_id
- provider = self.connection.provider
- headers = {provider.copy_source_header : src}
- if storage_class != 'STANDARD':
- headers[provider.storage_class_header] = storage_class
+ headers = {provider.copy_source_header : str(src)}
+ headers[provider.storage_class_header] = storage_class
if metadata:
headers[provider.metadata_directive_header] = 'REPLACE'
- headers = boto.utils.merge_meta(headers, metadata)
+ headers = boto.utils.merge_meta(headers, metadata, provider)
else:
headers[provider.metadata_directive_header] = 'COPY'
response = self.connection.make_request('PUT', self.name, new_key_name,
@@ -555,7 +579,8 @@ class Bucket(object):
self.set_xml_acl(acl, new_key_name)
return key
else:
- raise provider.storage_response_error(response.status, response.reason, body)
+ raise provider.storage_response_error(response.status,
+ response.reason, body)
def set_canned_acl(self, acl_str, key_name='', headers=None,
version_id=None):
@@ -566,7 +591,7 @@ class Bucket(object):
else:
headers={self.connection.provider.acl_header: acl_str}
- query_args='acl'
+ query_args = 'acl'
if version_id:
query_args += '&versionId=%s' % version_id
response = self.connection.make_request('PUT', self.name, key_name,
@@ -594,7 +619,7 @@ class Bucket(object):
if version_id:
query_args += '&versionId=%s' % version_id
response = self.connection.make_request('PUT', self.name, key_name,
- data=acl_str,
+ data=acl_str.encode('ISO-8859-1'),
query_args=query_args,
headers=headers)
body = response.read()
@@ -627,6 +652,80 @@ class Bucket(object):
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
+ response = self.connection.make_request('PUT', self.name, key_name,
+ data=value.encode('UTF-8'),
+ 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:
@@ -668,8 +767,8 @@ class Bucket(object):
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):
+ 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,
@@ -692,16 +791,22 @@ class Bucket(object):
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)
+ 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)
+ 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)
@@ -795,9 +900,9 @@ class Bucket(object):
mfa_token=None, headers=None):
"""
Configure versioning for this bucket.
- Note: This feature is currently in beta release and is available
- only in the Northern California region.
-
+
+ ..note:: This feature is currently in beta.
+
:type versioning: bool
:param versioning: A boolean indicating whether version is
enabled (True) or disabled (False).
@@ -909,14 +1014,17 @@ class Bucket(object):
:rtype: dict
:returns: A dictionary containing a Python representation
- of the XML response from S3. The overall structure is:
+ 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
+ * 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
"""
response = self.connection.make_request('GET', self.name,
query_args='website', headers=headers)
@@ -978,8 +1086,70 @@ class Bucket(object):
raise self.connection.provider.storage_response_error(
response.status, response.reason, body)
- def initiate_multipart_upload(self, key_name, headers=None):
+ 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 initiate_multipart_upload(self, key_name, headers=None,
+ reduced_redundancy=False,
+ metadata=None, encrypt_key=False):
+ """
+ Start a multipart upload operation.
+
+ :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.
+ """
query_args = 'uploads'
+ provider = self.connection.provider
+ if headers is None:
+ headers = {}
+ 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)
@@ -996,6 +1166,9 @@ class Bucket(object):
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 = {}
@@ -1003,9 +1176,15 @@ class Bucket(object):
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()
+ # 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:
+ if response.status == 200 and not contains_error:
resp = CompleteMultiPartUpload(self)
h = handler.XmlHandler(resp, self)
xml.sax.parseString(body, h)
@@ -1027,4 +1206,3 @@ class Bucket(object):
def delete(self, headers=None):
return self.connection.delete_bucket(self.name, headers=headers)
-
« no previous file with comments | « boto/s3/acl.py ('k') | boto/s3/bucketlistresultset.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698