diff --git a/gcloud/storage/api.py b/gcloud/storage/api.py index 1d5a047e073d..0e73ab9a75d3 100644 --- a/gcloud/storage/api.py +++ b/gcloud/storage/api.py @@ -196,7 +196,7 @@ def create_bucket(bucket_name, project=None, connection=None): """ connection = _require_connection(connection) bucket = Bucket(bucket_name, connection=connection) - bucket.create(project) + bucket.create(project, connection=connection) return bucket diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index d45383b19ffc..efdb7b567c7f 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -43,6 +43,7 @@ from gcloud._helpers import get_default_project from gcloud.exceptions import NotFound from gcloud.storage._helpers import _PropertyMixin +from gcloud.storage._helpers import _require_connection from gcloud.storage._helpers import _scalar_property from gcloud.storage.acl import BucketACL from gcloud.storage.acl import DefaultObjectACL @@ -114,23 +115,29 @@ def __contains__(self, blob_name): blob = Blob(blob_name, bucket=self) return blob.exists() - def exists(self): + def exists(self, connection=None): """Determines whether or not this bucket exists. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :rtype: boolean :returns: True if the bucket exists in Cloud Storage. """ + connection = _require_connection(connection) try: # We only need the status code (200 or not) so we seek to # minimize the returned payload. query_params = {'fields': 'name'} - self.connection.api_request(method='GET', path=self.path, - query_params=query_params) + connection.api_request(method='GET', path=self.path, + query_params=query_params) return True except NotFound: return False - def create(self, project=None): + def create(self, project=None, connection=None): """Creates current bucket. If the bucket already exists, will raise @@ -142,11 +149,17 @@ def create(self, project=None): :param project: Optional. The project to use when creating bucket. If not provided, falls back to default. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :rtype: :class:`gcloud.storage.bucket.Bucket` :returns: The newly created bucket. :raises: :class:`EnvironmentError` if the project is not given and can't be inferred. """ + connection = _require_connection(connection) if project is None: project = get_default_project() if project is None: @@ -154,7 +167,7 @@ def create(self, project=None): 'from environment.') query_params = {'project': project} - api_response = self.connection.api_request( + api_response = connection.api_request( method='POST', path='/b', query_params=query_params, data={'name': self.name}) self._set_properties(api_response) @@ -198,7 +211,7 @@ def path(self): return self.path_helper(self.name) - def get_blob(self, blob_name): + def get_blob(self, blob_name, connection=None): """Get a blob object by name. This will return None if the blob doesn't exist:: @@ -214,13 +227,19 @@ def get_blob(self, blob_name): :type blob_name: string :param blob_name: The name of the blob to retrieve. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :rtype: :class:`gcloud.storage.blob.Blob` or None :returns: The blob object if it exists, otherwise None. """ + connection = _require_connection(connection) blob = Blob(bucket=self, name=blob_name) try: - response = self.connection.api_request(method='GET', - path=blob.path) + response = connection.api_request(method='GET', + path=blob.path) name = response.get('name') # Expect this to be blob_name blob = Blob(name, bucket=self) blob._set_properties(response) @@ -291,7 +310,7 @@ def list_blobs(self, max_results=None, page_token=None, prefix=None, result.next_page_token = page_token return result - def delete(self, force=False): + def delete(self, force=False, connection=None): """Delete this bucket. The bucket **must** be empty in order to submit a delete request. If @@ -310,9 +329,15 @@ def delete(self, force=False): :type force: boolean :param force: If True, empties the bucket's objects then deletes it. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :raises: :class:`ValueError` if ``force`` is ``True`` and the bucket contains more than 256 objects / blobs. """ + connection = _require_connection(connection) if force: blobs = list(self.list_blobs( max_results=self._MAX_OBJECTS_FOR_BUCKET_DELETE + 1)) @@ -326,11 +351,12 @@ def delete(self, force=False): raise ValueError(message) # Ignore 404 errors on delete. - self.delete_blobs(blobs, on_error=lambda blob: None) + self.delete_blobs(blobs, on_error=lambda blob: None, + connection=connection) - self.connection.api_request(method='DELETE', path=self.path) + connection.api_request(method='DELETE', path=self.path) - def delete_blob(self, blob_name): + def delete_blob(self, blob_name, connection=None): """Deletes a blob from the current bucket. If the blob isn't found (backend 404), raises a @@ -353,16 +379,22 @@ def delete_blob(self, blob_name): :type blob_name: string :param blob_name: A blob name to delete. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :raises: :class:`gcloud.exceptions.NotFound` (to suppress the exception, call ``delete_blobs``, passing a no-op ``on_error`` callback, e.g.:: >>> bucket.delete_blobs([blob], on_error=lambda blob: None) """ + connection = _require_connection(connection) blob_path = Blob.path_helper(self.path, blob_name) - self.connection.api_request(method='DELETE', path=blob_path) + connection.api_request(method='DELETE', path=blob_path) - def delete_blobs(self, blobs, on_error=None): + def delete_blobs(self, blobs, on_error=None, connection=None): """Deletes a list of blobs from the current bucket. Uses :func:`Bucket.delete_blob` to delete each individual blob. @@ -375,22 +407,30 @@ def delete_blobs(self, blobs, on_error=None): :class:`gcloud.exceptions.NotFound`; otherwise, the exception is propagated. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :raises: :class:`gcloud.exceptions.NotFound` (if `on_error` is not passed). """ + connection = _require_connection(connection) for blob in blobs: try: blob_name = blob if not isinstance(blob_name, six.string_types): blob_name = blob.name - self.delete_blob(blob_name) + self.delete_blob(blob_name, connection=connection) except NotFound: if on_error is not None: on_error(blob) else: raise - def copy_blob(self, blob, destination_bucket, new_name=None): + @staticmethod + def copy_blob(blob, destination_bucket, new_name=None, + connection=None): """Copy the given blob to the given bucket, optionally with a new name. :type blob: string or :class:`gcloud.storage.blob.Blob` @@ -403,18 +443,24 @@ def copy_blob(self, blob, destination_bucket, new_name=None): :type new_name: string :param new_name: (optional) the new name for the copied file. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :rtype: :class:`gcloud.storage.blob.Blob` :returns: The new Blob. """ + connection = _require_connection(connection) if new_name is None: new_name = blob.name new_blob = Blob(bucket=destination_bucket, name=new_name) api_path = blob.path + '/copyTo' + new_blob.path - copy_result = self.connection.api_request(method='POST', path=api_path) + copy_result = connection.api_request(method='POST', path=api_path) new_blob._set_properties(copy_result) return new_blob - def upload_file(self, filename, blob_name=None): + def upload_file(self, filename, blob_name=None, connection=None): """Shortcut method to upload a file into this bucket. Use this method to quickly put a local file in Cloud Storage. @@ -447,16 +493,21 @@ def upload_file(self, filename, blob_name=None): of the bucket with the same name as on your local file system. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :rtype: :class:`Blob` :returns: The updated Blob object. """ if blob_name is None: blob_name = os.path.basename(filename) blob = Blob(bucket=self, name=blob_name) - blob.upload_from_filename(filename) + blob.upload_from_filename(filename, connection=connection) return blob - def upload_file_object(self, file_obj, blob_name=None): + def upload_file_object(self, file_obj, blob_name=None, connection=None): """Shortcut method to upload a file object into this bucket. Use this method to quickly put a local file in Cloud Storage. @@ -489,13 +540,18 @@ def upload_file_object(self, file_obj, blob_name=None): of the bucket with the same name as on your local file system. + :type connection: :class:`gcloud.storage.connection.Connection` or + ``NoneType`` + :param connection: Optional. The connection to use when sending + requests. If not provided, falls back to default. + :rtype: :class:`Blob` :returns: The updated Blob object. """ if blob_name is None: blob_name = os.path.basename(file_obj.name) blob = Blob(bucket=self, name=blob_name) - blob.upload_from_file(file_obj) + blob.upload_from_file(file_obj, connection=connection) return blob @property diff --git a/gcloud/storage/test_bucket.py b/gcloud/storage/test_bucket.py index c9ae0e56b320..4f31859c7970 100644 --- a/gcloud/storage/test_bucket.py +++ b/gcloud/storage/test_bucket.py @@ -154,8 +154,8 @@ def api_request(cls, *args, **kwargs): raise NotFound(args) BUCKET_NAME = 'bucket-name' - bucket = self._makeOne(BUCKET_NAME, connection=_FakeConnection) - self.assertFalse(bucket.exists()) + bucket = self._makeOne(BUCKET_NAME) + self.assertFalse(bucket.exists(connection=_FakeConnection)) expected_called_kwargs = { 'method': 'GET', 'path': bucket.path, @@ -178,8 +178,8 @@ def api_request(cls, *args, **kwargs): return object() BUCKET_NAME = 'bucket-name' - bucket = self._makeOne(BUCKET_NAME, connection=_FakeConnection) - self.assertTrue(bucket.exists()) + bucket = self._makeOne(BUCKET_NAME) + self.assertTrue(bucket.exists(connection=_FakeConnection)) expected_called_kwargs = { 'method': 'GET', 'path': bucket.path, @@ -194,16 +194,18 @@ def test_create_no_project(self): from gcloud._testing import _monkey_defaults BUCKET_NAME = 'bucket-name' bucket = self._makeOne(BUCKET_NAME) + CONNECTION = object() with _monkey_defaults(project=None): - self.assertRaises(EnvironmentError, bucket.create) + self.assertRaises(EnvironmentError, bucket.create, + connection=CONNECTION) def test_create_hit_explicit_project(self): BUCKET_NAME = 'bucket-name' DATA = {'name': BUCKET_NAME} connection = _Connection(DATA) PROJECT = 'PROJECT' - bucket = self._makeOne(BUCKET_NAME, connection=connection) - bucket.create(PROJECT) + bucket = self._makeOne(BUCKET_NAME) + bucket.create(PROJECT, connection=connection) kw, = connection._requested self.assertEqual(kw['method'], 'POST') @@ -217,9 +219,9 @@ def test_create_hit_implicit_project(self): DATA = {'name': BUCKET_NAME} connection = _Connection(DATA) PROJECT = 'PROJECT' - bucket = self._makeOne(BUCKET_NAME, connection=connection) + bucket = self._makeOne(BUCKET_NAME) with _monkey_defaults(project=PROJECT): - bucket.create() + bucket.create(connection=connection) kw, = connection._requested self.assertEqual(kw['method'], 'POST') @@ -255,8 +257,9 @@ def test_get_blob_miss(self): NAME = 'name' NONESUCH = 'nonesuch' connection = _Connection() - bucket = self._makeOne(NAME, connection) - self.assertTrue(bucket.get_blob(NONESUCH) is None) + bucket = self._makeOne(NAME) + result = bucket.get_blob(NONESUCH, connection=connection) + self.assertTrue(result is None) kw, = connection._requested self.assertEqual(kw['method'], 'GET') self.assertEqual(kw['path'], '/b/%s/o/%s' % (NAME, NONESUCH)) @@ -265,8 +268,8 @@ def test_get_blob_hit(self): NAME = 'name' BLOB_NAME = 'blob-name' connection = _Connection({'name': BLOB_NAME}) - bucket = self._makeOne(NAME, connection) - blob = bucket.get_blob(BLOB_NAME) + bucket = self._makeOne(NAME) + blob = bucket.get_blob(BLOB_NAME, connection=connection) self.assertTrue(blob.bucket is bucket) self.assertEqual(blob.name, BLOB_NAME) kw, = connection._requested @@ -325,8 +328,8 @@ def test_delete_default_miss(self): from gcloud.exceptions import NotFound NAME = 'name' connection = _Connection() - bucket = self._makeOne(NAME, connection) - self.assertRaises(NotFound, bucket.delete) + bucket = self._makeOne(NAME) + self.assertRaises(NotFound, bucket.delete, connection=connection) expected_cw = [{'method': 'DELETE', 'path': bucket.path}] self.assertEqual(connection._deleted_buckets, expected_cw) @@ -336,7 +339,8 @@ def test_delete_explicit_hit(self): connection = _Connection(GET_BLOBS_RESP) connection._delete_bucket = True bucket = self._makeOne(NAME, connection) - self.assertEqual(bucket.delete(force=True), None) + result = bucket.delete(force=True, connection=connection) + self.assertTrue(result is None) expected_cw = [{'method': 'DELETE', 'path': bucket.path}] self.assertEqual(connection._deleted_buckets, expected_cw) @@ -355,7 +359,8 @@ def test_delete_explicit_force_delete_blobs(self): DELETE_BLOB2_RESP) connection._delete_bucket = True bucket = self._makeOne(NAME, connection) - self.assertEqual(bucket.delete(force=True), None) + result = bucket.delete(force=True, connection=connection) + self.assertTrue(result is None) expected_cw = [{'method': 'DELETE', 'path': bucket.path}] self.assertEqual(connection._deleted_buckets, expected_cw) @@ -367,7 +372,8 @@ def test_delete_explicit_force_miss_blobs(self): connection = _Connection(GET_BLOBS_RESP) connection._delete_bucket = True bucket = self._makeOne(NAME, connection) - self.assertEqual(bucket.delete(force=True), None) + result = bucket.delete(force=True, connection=connection) + self.assertTrue(result is None) expected_cw = [{'method': 'DELETE', 'path': bucket.path}] self.assertEqual(connection._deleted_buckets, expected_cw) @@ -387,7 +393,8 @@ def test_delete_explicit_too_many(self): # Make the Bucket refuse to delete with 2 objects. bucket._MAX_OBJECTS_FOR_BUCKET_DELETE = 1 - self.assertRaises(ValueError, bucket.delete, force=True) + self.assertRaises(ValueError, bucket.delete, force=True, + connection=connection) self.assertEqual(connection._deleted_buckets, []) def test_delete_blob_miss(self): @@ -395,8 +402,9 @@ def test_delete_blob_miss(self): NAME = 'name' NONESUCH = 'nonesuch' connection = _Connection() - bucket = self._makeOne(NAME, connection) - self.assertRaises(NotFound, bucket.delete_blob, NONESUCH) + bucket = self._makeOne(NAME) + self.assertRaises(NotFound, bucket.delete_blob, NONESUCH, + connection=connection) kw, = connection._requested self.assertEqual(kw['method'], 'DELETE') self.assertEqual(kw['path'], '/b/%s/o/%s' % (NAME, NONESUCH)) @@ -405,8 +413,8 @@ def test_delete_blob_hit(self): NAME = 'name' BLOB_NAME = 'blob-name' connection = _Connection({}) - bucket = self._makeOne(NAME, connection) - result = bucket.delete_blob(BLOB_NAME) + bucket = self._makeOne(NAME) + result = bucket.delete_blob(BLOB_NAME, connection=connection) self.assertTrue(result is None) kw, = connection._requested self.assertEqual(kw['method'], 'DELETE') @@ -415,16 +423,16 @@ def test_delete_blob_hit(self): def test_delete_blobs_empty(self): NAME = 'name' connection = _Connection() - bucket = self._makeOne(NAME, connection) - bucket.delete_blobs([]) + bucket = self._makeOne(NAME) + bucket.delete_blobs([], connection=connection) self.assertEqual(connection._requested, []) def test_delete_blobs_hit(self): NAME = 'name' BLOB_NAME = 'blob-name' connection = _Connection({}) - bucket = self._makeOne(NAME, connection) - bucket.delete_blobs([BLOB_NAME]) + bucket = self._makeOne(NAME) + bucket.delete_blobs([BLOB_NAME], connection=connection) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'DELETE') @@ -436,8 +444,9 @@ def test_delete_blobs_miss_no_on_error(self): BLOB_NAME = 'blob-name' NONESUCH = 'nonesuch' connection = _Connection({}) - bucket = self._makeOne(NAME, connection) - self.assertRaises(NotFound, bucket.delete_blobs, [BLOB_NAME, NONESUCH]) + bucket = self._makeOne(NAME) + self.assertRaises(NotFound, bucket.delete_blobs, [BLOB_NAME, NONESUCH], + connection=connection) kw = connection._requested self.assertEqual(len(kw), 2) self.assertEqual(kw[0]['method'], 'DELETE') @@ -450,9 +459,10 @@ def test_delete_blobs_miss_w_on_error(self): BLOB_NAME = 'blob-name' NONESUCH = 'nonesuch' connection = _Connection({}) - bucket = self._makeOne(NAME, connection) + bucket = self._makeOne(NAME) errors = [] - bucket.delete_blobs([BLOB_NAME, NONESUCH], errors.append) + bucket.delete_blobs([BLOB_NAME, NONESUCH], errors.append, + connection=connection) self.assertEqual(errors, [NONESUCH]) kw = connection._requested self.assertEqual(len(kw), 2) @@ -471,10 +481,10 @@ class _Blob(object): path = '/b/%s/o/%s' % (SOURCE, BLOB_NAME) connection = _Connection({}) - source = self._makeOne(SOURCE, connection) - dest = self._makeOne(DEST, connection) + source = self._makeOne(SOURCE) + dest = self._makeOne(DEST) blob = _Blob() - new_blob = source.copy_blob(blob, dest) + new_blob = source.copy_blob(blob, dest, connection=connection) self.assertTrue(new_blob.bucket is dest) self.assertEqual(new_blob.name, BLOB_NAME) kw, = connection._requested @@ -494,10 +504,11 @@ class _Blob(object): path = '/b/%s/o/%s' % (SOURCE, BLOB_NAME) connection = _Connection({}) - source = self._makeOne(SOURCE, connection) - dest = self._makeOne(DEST, connection) + source = self._makeOne(SOURCE) + dest = self._makeOne(DEST) blob = _Blob() - new_blob = source.copy_blob(blob, dest, NEW_NAME) + new_blob = source.copy_blob(blob, dest, NEW_NAME, + connection=connection) self.assertTrue(new_blob.bucket is dest) self.assertEqual(new_blob.name, NEW_NAME) kw, = connection._requested @@ -519,13 +530,14 @@ def __init__(self, bucket, name): self._bucket = bucket self._name = name - def upload_from_filename(self, filename): - _uploaded.append((self._bucket, self._name, filename)) + def upload_from_filename(self, filename, connection=None): + _uploaded.append((self._bucket, self._name, filename, + connection)) bucket = self._makeOne() with _Monkey(MUT, Blob=_Blob): bucket.upload_file(FILENAME) - self.assertEqual(_uploaded, [(bucket, BASENAME, FILENAME)]) + self.assertEqual(_uploaded, [(bucket, BASENAME, FILENAME, None)]) def test_upload_file_explicit_blob(self): from gcloud._testing import _Monkey @@ -540,13 +552,14 @@ def __init__(self, bucket, name): self._bucket = bucket self._name = name - def upload_from_filename(self, filename): - _uploaded.append((self._bucket, self._name, filename)) + def upload_from_filename(self, filename, connection=None): + _uploaded.append((self._bucket, self._name, filename, + connection)) bucket = self._makeOne() with _Monkey(MUT, Blob=_Blob): bucket.upload_file(FILENAME, BLOB_NAME) - self.assertEqual(_uploaded, [(bucket, BLOB_NAME, FILENAME)]) + self.assertEqual(_uploaded, [(bucket, BLOB_NAME, FILENAME, None)]) def test_upload_file_object_no_blob(self): from gcloud._testing import _Monkey @@ -561,13 +574,13 @@ def __init__(self, bucket, name): self._bucket = bucket self._name = name - def upload_from_file(self, fh): - _uploaded.append((self._bucket, self._name, fh)) + def upload_from_file(self, fh, connection=None): + _uploaded.append((self._bucket, self._name, fh, connection)) bucket = self._makeOne() with _Monkey(MUT, Blob=_Blob): found = bucket.upload_file_object(FILEOBJECT) - self.assertEqual(_uploaded, [(bucket, FILENAME, FILEOBJECT)]) + self.assertEqual(_uploaded, [(bucket, FILENAME, FILEOBJECT, None)]) self.assertTrue(isinstance(found, _Blob)) self.assertEqual(found._name, FILENAME) self.assertTrue(found._bucket is bucket) @@ -586,13 +599,13 @@ def __init__(self, bucket, name): self._bucket = bucket self._name = name - def upload_from_file(self, fh): - _uploaded.append((self._bucket, self._name, fh)) + def upload_from_file(self, fh, connection=None): + _uploaded.append((self._bucket, self._name, fh, connection)) bucket = self._makeOne() with _Monkey(MUT, Blob=_Blob): found = bucket.upload_file_object(FILEOBJECT, BLOB_NAME) - self.assertEqual(_uploaded, [(bucket, BLOB_NAME, FILEOBJECT)]) + self.assertEqual(_uploaded, [(bucket, BLOB_NAME, FILEOBJECT, None)]) self.assertTrue(isinstance(found, _Blob)) self.assertEqual(found._name, BLOB_NAME) self.assertTrue(found._bucket is bucket)