diff --git a/README.rst b/README.rst index 31a6fcf77a23..766532b1adba 100644 --- a/README.rst +++ b/README.rst @@ -99,7 +99,8 @@ to Cloud Storage using this Client Library. .. code:: python from gcloud import storage - bucket = storage.get_bucket('bucket-id-here', 'project-id') + storage.set_defaults() + bucket = storage.get_bucket('bucket-id-here') # Then do other things... blob = bucket.get_blob('/remote/path/to/file.txt') print blob.download_as_string() diff --git a/docs/_components/storage-getting-started.rst b/docs/_components/storage-getting-started.rst index 4bb914e14c64..e7426b8b5d55 100644 --- a/docs/_components/storage-getting-started.rst +++ b/docs/_components/storage-getting-started.rst @@ -56,7 +56,7 @@ bucket. Let's create a bucket: - >>> bucket = connection.create_bucket('test') + >>> bucket = storage.create_bucket('test', connection=connection) Traceback (most recent call last): File "", line 1, in File "gcloud/storage/connection.py", line 340, in create_bucket @@ -151,11 +151,11 @@ in Python:: Accessing a bucket ------------------ -If you already have a bucket, use :func:`get_bucket -` to retrieve the bucket -object:: +If you already have a bucket, use +:func:`get_bucket ` to retrieve the +bucket object:: - >>> bucket = connection.get_bucket('my-bucket') + >>> bucket = storage.get_bucket('my-bucket', connection=connection) If you want to get all the blobs in the bucket, you can use :func:`get_all_blobs `:: @@ -171,17 +171,17 @@ bucket itself as an iterator:: Deleting a bucket ----------------- -You can delete a bucket using the :func:`delete_bucket -` method:: +You can delete a bucket using the +:meth:`delete ` method:: - >>> connection.delete_bucket('my-bucket') + >>> bucket.delete() Remember, the bucket you're deleting needs to be empty, otherwise you'll -get an error. +get an error (409 conflict). If you have a full bucket, you can delete it this way:: - >>> bucket = connection.delete_bucket('my-bucket', force=True) + >>> bucket.delete(force=True) Listing available buckets ------------------------- diff --git a/docs/_components/storage-quickstart.rst b/docs/_components/storage-quickstart.rst index 64a83f3902d2..7d7a22710900 100644 --- a/docs/_components/storage-quickstart.rst +++ b/docs/_components/storage-quickstart.rst @@ -58,7 +58,7 @@ you can create buckets and blobs:: >>> from gcloud import storage >>> storage.get_all_buckets(connection) [, ...] - >>> bucket = connection.create_bucket('my-new-bucket') + >>> bucket = storage.create_bucket('my-new-bucket', connection=connection) >>> print bucket >>> blob = bucket.new_blob('my-test-file.txt') diff --git a/docs/index.rst b/docs/index.rst index e0cd3e033ffd..a6f28a6b88e2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,6 +47,7 @@ Cloud Storage .. code-block:: python from gcloud import storage - bucket = storage.get_bucket('', '') + storage.set_defaults() + bucket = storage.get_bucket('') blob = bucket.new_blob('my-test-file.txt') blob = blob.upload_contents_from_string('this is test content!') diff --git a/gcloud/storage/__init__.py b/gcloud/storage/__init__.py index e9377c481f9d..ff30ba717dfe 100644 --- a/gcloud/storage/__init__.py +++ b/gcloud/storage/__init__.py @@ -16,8 +16,9 @@ You'll typically use these to get started with the API: ->>> import gcloud.storage ->>> bucket = gcloud.storage.get_bucket('bucket-id-here', 'project-id') +>>> from gcloud import storage +>>> storage.set_defaults() +>>> bucket = storage.get_bucket('bucket-id-here') >>> # Then do other things... >>> blob = bucket.get_blob('/remote/path/to/file.txt') >>> print blob.download_as_string() @@ -44,7 +45,9 @@ from gcloud.storage._implicit_environ import get_default_bucket from gcloud.storage._implicit_environ import get_default_connection from gcloud.storage._implicit_environ import get_default_project +from gcloud.storage.api import create_bucket from gcloud.storage.api import get_all_buckets +from gcloud.storage.api import get_bucket from gcloud.storage.api import lookup_bucket from gcloud.storage.blob import Blob from gcloud.storage.bucket import Bucket @@ -148,8 +151,8 @@ def get_connection(project=None): >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket1 = connection.get_bucket('bucket1') - >>> bucket2 = connection.get_bucket('bucket2') + >>> bucket1 = storage.get_bucket('bucket1', connection=connection) + >>> bucket2 = storage.get_bucket('bucket2', connection=connection) :type project: string or ``NoneType`` :param project: Optional. The name of the project to connect to. If not @@ -163,28 +166,3 @@ def get_connection(project=None): implicit_credentials = credentials.get_credentials() scoped_credentials = implicit_credentials.create_scoped(SCOPE) return Connection(project=project, credentials=scoped_credentials) - - -def get_bucket(bucket_name, project): - """Shortcut method to establish a connection to a particular bucket. - - You'll generally use this as the first call to working with the API: - - >>> from gcloud import storage - >>> bucket = storage.get_bucket(project, bucket_name) - >>> # Now you can do things with the bucket. - >>> bucket.exists('/path/to/file.txt') - False - - :type bucket_name: string - :param bucket_name: The id of the bucket you want to use. - This is akin to a disk name on a file system. - - :type project: string - :param project: The name of the project to connect to. - - :rtype: :class:`gcloud.storage.bucket.Bucket` - :returns: A bucket with a connection using the provided credentials. - """ - connection = get_connection(project) - return connection.get_bucket(bucket_name) diff --git a/gcloud/storage/acl.py b/gcloud/storage/acl.py index 04f5d6b539d1..656eb08ed433 100644 --- a/gcloud/storage/acl.py +++ b/gcloud/storage/acl.py @@ -20,7 +20,7 @@ >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket(bucket_name) + >>> bucket = storage.get_bucket(bucket_name, connection=connection) >>> acl = bucket.acl Adding and removing permissions can be done with the following methods @@ -427,8 +427,8 @@ def save(self, acl=None): You can use this to set access controls to be consistent from one bucket to another:: - >>> bucket1 = connection.get_bucket(bucket1_name) - >>> bucket2 = connection.get_bucket(bucket2_name) + >>> bucket1 = storage.get_bucket(bucket1_name, connection=connection) + >>> bucket2 = storage.get_bucket(bucket2_name, connection=connection) >>> bucket2.acl.save(bucket1.acl) :type acl: :class:`gcloud.storage.acl.ACL`, or a compatible list. diff --git a/gcloud/storage/api.py b/gcloud/storage/api.py index 15549245a15e..169efb541a92 100644 --- a/gcloud/storage/api.py +++ b/gcloud/storage/api.py @@ -54,7 +54,7 @@ def lookup_bucket(bucket_name, connection=None): connection = get_default_connection() try: - return connection.get_bucket(bucket_name) + return get_bucket(bucket_name, connection=connection) except NotFound: return None @@ -84,6 +84,77 @@ def get_all_buckets(connection=None): return iter(_BucketIterator(connection=connection)) +def get_bucket(bucket_name, connection=None): + """Get a bucket by name. + + If the bucket isn't found, this will raise a + :class:`gcloud.storage.exceptions.NotFound`. + + For example:: + + >>> from gcloud import storage + >>> from gcloud.exceptions import NotFound + >>> try: + >>> bucket = storage.get_bucket('my-bucket') + >>> except NotFound: + >>> print 'Sorry, that bucket does not exist!' + + This implements "storage.buckets.get". + + :type bucket_name: string + :param bucket_name: The name of the bucket to get. + + :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 bucket matching the name provided. + :raises: :class:`gcloud.exceptions.NotFound` + """ + if connection is None: + connection = get_default_connection() + + bucket_path = Bucket.path_helper(bucket_name) + response = connection.api_request(method='GET', path=bucket_path) + return Bucket(properties=response, connection=connection) + + +def create_bucket(bucket_name, connection=None): + """Create a new bucket. + + For example:: + + >>> from gcloud import storage + >>> storage.set_defaults() + >>> bucket = storage.create_bucket('my-bucket') + >>> print bucket + + + This implements "storage.buckets.insert". + + :type bucket_name: string + :param bucket_name: The bucket name to create. + + :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:`gcloud.exceptions.Conflict` if + there is a confict (bucket already exists, invalid name, etc.) + """ + if connection is None: + connection = get_default_connection() + + response = connection.api_request(method='POST', path='/b', + data={'name': bucket_name}) + return Bucket(properties=response, connection=connection) + + class _BucketIterator(Iterator): """An iterator listing all buckets. diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index cba3b43ced62..2564a6f5e303 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -191,7 +191,7 @@ def get_blob(self, blob): >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket('my-bucket') + >>> bucket = storage.get_bucket('my-bucket', connection=connection) >>> print bucket.get_blob('/path/to/blob.txt') >>> print bucket.get_blob('/does-not-exist.txt') @@ -322,7 +322,7 @@ def delete(self, force=False): # Ignore 404 errors on delete. self.delete_blobs(blobs, on_error=lambda blob: None) - self.connection.delete_bucket(self.name) + self.connection.api_request(method='DELETE', path=self.path) def delete_blob(self, blob): """Deletes a blob from the current bucket. @@ -335,7 +335,7 @@ def delete_blob(self, blob): >>> from gcloud.exceptions import NotFound >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket('my-bucket') + >>> bucket = storage.get_bucket('my-bucket', connection=connection) >>> print bucket.get_all_blobs() [] >>> bucket.delete_blob('my-file.txt') @@ -417,7 +417,7 @@ def upload_file(self, filename, blob=None): >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket('my-bucket') + >>> bucket = storage.get_bucket('my-bucket', connection=connection) >>> bucket.upload_file('~/my-file.txt', 'remote-text-file.txt') >>> print bucket.get_all_blobs() [] @@ -428,7 +428,7 @@ def upload_file(self, filename, blob=None): >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket('my-bucket') + >>> bucket = storage.get_bucket('my-bucket', connection=connection) >>> bucket.upload_file('~/my-file.txt') >>> print bucket.get_all_blobs() [] @@ -460,7 +460,7 @@ def upload_file_object(self, file_obj, blob=None): >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket('my-bucket') + >>> bucket = storage.get_bucket('my-bucket', connection=connection) >>> bucket.upload_file(open('~/my-file.txt'), 'remote-text-file.txt') >>> print bucket.get_all_blobs() [] @@ -471,7 +471,7 @@ def upload_file_object(self, file_obj, blob=None): >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket('my-bucket') + >>> bucket = storage.get_bucket('my-bucket', connection=connection) >>> bucket.upload_file(open('~/my-file.txt')) >>> print bucket.get_all_blobs() [] @@ -725,7 +725,7 @@ def configure_website(self, main_page_suffix=None, not_found_page=None): >>> from gcloud import storage >>> connection = storage.get_connection(project) - >>> bucket = connection.get_bucket(bucket_name) + >>> bucket = storage.get_bucket(bucket_name, connection=connection) >>> bucket.configure_website('index.html', '404.html') You probably should also make the whole bucket public:: diff --git a/gcloud/storage/connection.py b/gcloud/storage/connection.py index eac467df313f..036e0663bf0a 100644 --- a/gcloud/storage/connection.py +++ b/gcloud/storage/connection.py @@ -20,42 +20,21 @@ from gcloud import connection as base_connection from gcloud.exceptions import make_exception -from gcloud.storage.bucket import Bucket class Connection(base_connection.Connection): """A connection to Google Cloud Storage via the JSON REST API. This defines :meth:`Connection.api_request` for making a generic JSON - API request and most API requests are created elsewhere (e.g. in + API request and API requests are created elsewhere (e.g. in + :mod:`gcloud.storage.api` and :class:`gcloud.storage.bucket.Bucket` and :class:`gcloud.storage.blob.Blob`). - Methods for getting, creating and deleting individual buckets as well - as listing buckets associated with a project are defined here. This - corresponds to the "storage.buckets" resource in the API. - See :class:`gcloud.connection.Connection` for a full list of parameters. This subclass differs only in needing a project name (which you specify when creating a project in the Cloud Console). - - A typical use of this is to operate on - :class:`gcloud.storage.bucket.Bucket` objects:: - - >>> from gcloud import storage - >>> connection = storage.get_connection(project) - >>> bucket = connection.create_bucket('my-bucket-name') - - You can then delete this bucket:: - - >>> bucket.delete() - >>> # or - >>> connection.delete_bucket(bucket.name) - - If you want to access an existing bucket:: - - >>> bucket = connection.get_bucket('my-bucket-name') """ API_BASE_URL = base_connection.API_BASE_URL @@ -259,92 +238,3 @@ def api_request(self, method, path, query_params=None, return json.loads(content) return content - - def get_bucket(self, bucket_name): - """Get a bucket by name. - - If the bucket isn't found, this will raise a - :class:`gcloud.storage.exceptions.NotFound`. - - For example:: - - >>> from gcloud import storage - >>> from gcloud.exceptions import NotFound - >>> connection = storage.get_connection(project) - >>> try: - >>> bucket = connection.get_bucket('my-bucket') - >>> except NotFound: - >>> print 'Sorry, that bucket does not exist!' - - This implements "storage.buckets.get". - - :type bucket_name: string - :param bucket_name: The name of the bucket to get. - - :rtype: :class:`gcloud.storage.bucket.Bucket` - :returns: The bucket matching the name provided. - :raises: :class:`gcloud.exceptions.NotFound` - """ - bucket = Bucket(connection=self, name=bucket_name) - response = self.api_request(method='GET', path=bucket.path) - return Bucket(properties=response, connection=self) - - def create_bucket(self, bucket_name): - """Create a new bucket. - - For example:: - - >>> from gcloud import storage - >>> connection = storage.get_connection(project) - >>> bucket = connection.create_bucket('my-bucket') - >>> print bucket - - - This implements "storage.buckets.insert". - - :type bucket_name: string - :param bucket_name: The bucket name to create. - - :rtype: :class:`gcloud.storage.bucket.Bucket` - :returns: The newly created bucket. - :raises: :class:`gcloud.exceptions.Conflict` if - there is a confict (bucket already exists, invalid name, etc.) - """ - response = self.api_request(method='POST', path='/b', - data={'name': bucket_name}) - return Bucket(properties=response, connection=self) - - def delete_bucket(self, bucket_name): - """Delete a bucket. - - You can use this method to delete a bucket by name. - - >>> from gcloud import storage - >>> connection = storage.get_connection(project) - >>> connection.delete_bucket('my-bucket') - - If the bucket doesn't exist, this will raise a - :class:`gcloud.exceptions.NotFound`:: - - >>> from gcloud.exceptions import NotFound - >>> try: - >>> connection.delete_bucket('my-bucket') - >>> except NotFound: - >>> print 'That bucket does not exist!' - - If the bucket still has objects in it, this will raise a - :class:`gcloud.exceptions.Conflict`:: - - >>> from gcloud.exceptions import Conflict - >>> try: - >>> connection.delete_bucket('my-bucket') - >>> except Conflict: - >>> print 'That bucket is not empty!' - - This implements "storage.buckets.delete". - - :type bucket_name: string - :param bucket_name: The bucket name to delete. - """ - bucket_path = Bucket.path_helper(bucket_name) - self.api_request(method='DELETE', path=bucket_path) diff --git a/gcloud/storage/demo/demo.py b/gcloud/storage/demo/demo.py index 7a42e8ff3f7d..93cc46a7f675 100644 --- a/gcloud/storage/demo/demo.py +++ b/gcloud/storage/demo/demo.py @@ -17,7 +17,7 @@ # Now let's create a new bucket... bucket_name = ("bucket-%s" % time.time()).replace(".", "") # Get rid of dots. print(bucket_name) -bucket = connection.create_bucket(bucket_name) +bucket = storage.create_bucket(bucket_name, connection=connection) print(bucket) # Let's look at all of the buckets again... diff --git a/gcloud/storage/test___init__.py b/gcloud/storage/test___init__.py index aeefb9ca01e5..b6e8976b6d53 100644 --- a/gcloud/storage/test___init__.py +++ b/gcloud/storage/test___init__.py @@ -54,41 +54,6 @@ def test_default_project(self): self.assertTrue(client._get_app_default_called) -class Test_get_bucket(unittest2.TestCase): - - def _callFUT(self, *args, **kw): - from gcloud.storage import get_bucket - return get_bucket(*args, **kw) - - def test_it(self): - from gcloud import storage - from gcloud._testing import _Monkey - - bucket = object() - - class _Connection(object): - - def get_bucket(self, bucket_name): - self._called_with = bucket_name - return bucket - - connection = _Connection() - _called_with = [] - - def get_connection(*args, **kw): - _called_with.append((args, kw)) - return connection - - BUCKET = 'bucket' - PROJECT = 'project' - with _Monkey(storage, get_connection=get_connection): - found = self._callFUT(BUCKET, PROJECT) - - self.assertTrue(found is bucket) - self.assertEqual(_called_with, [((PROJECT,), {})]) - self.assertEqual(connection._called_with, BUCKET) - - class Test_set_default_bucket(unittest2.TestCase): def setUp(self): diff --git a/gcloud/storage/test_api.py b/gcloud/storage/test_api.py index 6291324786b3..e09f0d51b1fe 100644 --- a/gcloud/storage/test_api.py +++ b/gcloud/storage/test_api.py @@ -21,7 +21,7 @@ def _callFUT(self, bucket_name, connection=None): from gcloud.storage.api import lookup_bucket return lookup_bucket(bucket_name, connection=connection) - def test_lookup_bucket_miss(self): + def test_miss(self): from gcloud.storage.connection import Connection PROJECT = 'project' NONESUCH = 'nonesuch' @@ -73,10 +73,10 @@ def _lookup_bucket_hit_helper(self, use_default=False): self.assertEqual(http._called_with['method'], 'GET') self.assertEqual(http._called_with['uri'], URI) - def test_lookup_bucket_hit(self): + def test_hit(self): self._lookup_bucket_hit_helper(use_default=False) - def test_lookup_bucket_use_default(self): + def test_use_default(self): self._lookup_bucket_hit_helper(use_default=True) @@ -140,6 +140,114 @@ def test_non_use_default(self): self._get_all_buckets_non_empty_helper(use_default=True) +class Test_get_bucket(unittest2.TestCase): + + def _callFUT(self, bucket_name, connection=None): + from gcloud.storage.api import get_bucket + return get_bucket(bucket_name, connection=connection) + + def test_miss(self): + from gcloud.exceptions import NotFound + from gcloud.storage.connection import Connection + PROJECT = 'project' + NONESUCH = 'nonesuch' + conn = Connection(PROJECT) + URI = '/'.join([ + conn.API_BASE_URL, + 'storage', + conn.API_VERSION, + 'b', + 'nonesuch?project=%s' % PROJECT, + ]) + http = conn._http = Http( + {'status': '404', 'content-type': 'application/json'}, + '{}', + ) + self.assertRaises(NotFound, self._callFUT, NONESUCH, connection=conn) + self.assertEqual(http._called_with['method'], 'GET') + self.assertEqual(http._called_with['uri'], URI) + + def _get_bucket_hit_helper(self, use_default=False): + from gcloud.storage._testing import _monkey_defaults + from gcloud.storage.bucket import Bucket + from gcloud.storage.connection import Connection + PROJECT = 'project' + BLOB_NAME = 'blob-name' + conn = Connection(PROJECT) + URI = '/'.join([ + conn.API_BASE_URL, + 'storage', + conn.API_VERSION, + 'b', + '%s?project=%s' % (BLOB_NAME, PROJECT), + ]) + http = conn._http = Http( + {'status': '200', 'content-type': 'application/json'}, + '{"name": "%s"}' % BLOB_NAME, + ) + + if use_default: + with _monkey_defaults(connection=conn): + bucket = self._callFUT(BLOB_NAME) + else: + bucket = self._callFUT(BLOB_NAME, connection=conn) + + self.assertTrue(isinstance(bucket, Bucket)) + self.assertTrue(bucket.connection is conn) + self.assertEqual(bucket.name, BLOB_NAME) + self.assertEqual(http._called_with['method'], 'GET') + self.assertEqual(http._called_with['uri'], URI) + + def test_hit(self): + self._get_bucket_hit_helper(use_default=False) + + def test_hit_use_default(self): + self._get_bucket_hit_helper(use_default=True) + + +class Test_create_bucket(unittest2.TestCase): + + def _callFUT(self, bucket_name, connection=None): + from gcloud.storage.api import create_bucket + return create_bucket(bucket_name, connection=connection) + + def _create_bucket_success_helper(self, use_default=False): + from gcloud.storage._testing import _monkey_defaults + from gcloud.storage.connection import Connection + from gcloud.storage.bucket import Bucket + PROJECT = 'project' + BLOB_NAME = 'blob-name' + conn = Connection(PROJECT) + URI = '/'.join([ + conn.API_BASE_URL, + 'storage', + conn.API_VERSION, + 'b?project=%s' % PROJECT, + ]) + http = conn._http = Http( + {'status': '200', 'content-type': 'application/json'}, + '{"name": "%s"}' % BLOB_NAME, + ) + + if use_default: + with _monkey_defaults(connection=conn): + bucket = self._callFUT(BLOB_NAME) + else: + bucket = self._callFUT(BLOB_NAME, connection=conn) + + self.assertTrue(isinstance(bucket, Bucket)) + self.assertTrue(bucket.connection is conn) + self.assertEqual(bucket.name, BLOB_NAME) + self.assertEqual(http._called_with['method'], 'POST') + self.assertEqual(http._called_with['uri'], URI) + + def test_success(self): + self._create_bucket_success_helper(use_default=False) + + def test_success_use_default(self): + self._create_bucket_success_helper(use_default=True) + + class Test__BucketIterator(unittest2.TestCase): def _getTargetClass(self): diff --git a/gcloud/storage/test_batch.py b/gcloud/storage/test_batch.py index d671373649b7..c7dbbba53a5e 100644 --- a/gcloud/storage/test_batch.py +++ b/gcloud/storage/test_batch.py @@ -383,18 +383,6 @@ def api_request(self, method, path, query_params=None, expect_json=True): # pragma: NO COVER pass - def get_all_buckets(self): # pragma: NO COVER - pass - - def get_bucket(self, name): # pragma: NO COVER - pass - - def create_bucket(self, name): # pragma: NO COVER - pass - - def delete_bucket(self, name): # pragma: NO COVER - pass - class _Response(dict): diff --git a/gcloud/storage/test_bucket.py b/gcloud/storage/test_bucket.py index 4bef81ad5f4d..08811e200616 100644 --- a/gcloud/storage/test_bucket.py +++ b/gcloud/storage/test_bucket.py @@ -331,16 +331,18 @@ def test_delete_default_miss(self): connection = _Connection() bucket = self._makeOne(connection, NAME) self.assertRaises(NotFound, bucket.delete) - self.assertEqual(connection._deleted, [NAME]) + expected_cw = [{'method': 'DELETE', 'path': bucket.path}] + self.assertEqual(connection._deleted_buckets, expected_cw) def test_delete_explicit_hit(self): NAME = 'name' GET_BLOBS_RESP = {'items': []} connection = _Connection(GET_BLOBS_RESP) - connection._delete_ok = True + connection._delete_bucket = True bucket = self._makeOne(connection, NAME) self.assertEqual(bucket.delete(force=True), None) - self.assertEqual(connection._deleted, [NAME]) + expected_cw = [{'method': 'DELETE', 'path': bucket.path}] + self.assertEqual(connection._deleted_buckets, expected_cw) def test_delete_explicit_force_delete_blobs(self): NAME = 'name' @@ -355,10 +357,11 @@ def test_delete_explicit_force_delete_blobs(self): DELETE_BLOB1_RESP = DELETE_BLOB2_RESP = {} connection = _Connection(GET_BLOBS_RESP, DELETE_BLOB1_RESP, DELETE_BLOB2_RESP) - connection._delete_ok = True + connection._delete_bucket = True bucket = self._makeOne(connection, NAME) self.assertEqual(bucket.delete(force=True), None) - self.assertEqual(connection._deleted, [NAME]) + expected_cw = [{'method': 'DELETE', 'path': bucket.path}] + self.assertEqual(connection._deleted_buckets, expected_cw) def test_delete_explicit_force_miss_blobs(self): NAME = 'name' @@ -366,10 +369,11 @@ def test_delete_explicit_force_miss_blobs(self): GET_BLOBS_RESP = {'items': [{'name': BLOB_NAME}]} # Note the connection does not have a response for the blob. connection = _Connection(GET_BLOBS_RESP) - connection._delete_ok = True + connection._delete_bucket = True bucket = self._makeOne(connection, NAME) self.assertEqual(bucket.delete(force=True), None) - self.assertEqual(connection._deleted, [NAME]) + expected_cw = [{'method': 'DELETE', 'path': bucket.path}] + self.assertEqual(connection._deleted_buckets, expected_cw) def test_delete_explicit_too_many(self): NAME = 'name' @@ -382,12 +386,13 @@ def test_delete_explicit_too_many(self): ], } connection = _Connection(GET_BLOBS_RESP) - connection._delete_ok = True + connection._delete_bucket = True bucket = self._makeOne(connection, NAME) # Make the Bucket refuse to delete with 2 objects. bucket._MAX_OBJECTS_FOR_BUCKET_DELETE = 1 self.assertRaises(ValueError, bucket.delete, force=True) + self.assertEqual(connection._deleted_buckets, []) def test_delete_blob_miss(self): from gcloud.exceptions import NotFound @@ -1079,17 +1084,33 @@ def get_items_from_response(self, response): class _Connection(object): - _delete_ok = False + _delete_bucket = False def __init__(self, *responses): self._responses = responses self._requested = [] - self._deleted = [] + self._deleted_buckets = [] + + @staticmethod + def _is_bucket_path(path): + if not path.startswith('/b/'): # pragma: NO COVER + return False + # Now just ensure the path only has /b/ and one more segment. + return path.count('/') == 2 def api_request(self, **kw): from gcloud.exceptions import NotFound self._requested.append(kw) + method = kw.get('method') + path = kw.get('path', '') + if method == 'DELETE' and self._is_bucket_path(path): + self._deleted_buckets.append(kw) + if self._delete_bucket: + return + else: + raise NotFound('miss') + try: response, self._responses = self._responses[0], self._responses[1:] except: @@ -1097,13 +1118,6 @@ def api_request(self, **kw): else: return response - def delete_bucket(self, bucket): - from gcloud.exceptions import NotFound - self._deleted.append(bucket) - if not self._delete_ok: - raise NotFound('miss') - return True - class _Bucket(object): path = '/b/name' diff --git a/gcloud/storage/test_connection.py b/gcloud/storage/test_connection.py index 1a79fb2c89ad..fc5aa6615f52 100644 --- a/gcloud/storage/test_connection.py +++ b/gcloud/storage/test_connection.py @@ -281,94 +281,6 @@ def test_api_request_w_500(self): ) self.assertRaises(InternalServerError, conn.api_request, 'GET', '/') - def test_get_bucket_miss(self): - from gcloud.exceptions import NotFound - PROJECT = 'project' - NONESUCH = 'nonesuch' - conn = self._makeOne(PROJECT) - URI = '/'.join([ - conn.API_BASE_URL, - 'storage', - conn.API_VERSION, - 'b', - 'nonesuch?project=%s' % PROJECT, - ]) - http = conn._http = Http( - {'status': '404', 'content-type': 'application/json'}, - '{}', - ) - self.assertRaises(NotFound, conn.get_bucket, NONESUCH) - self.assertEqual(http._called_with['method'], 'GET') - self.assertEqual(http._called_with['uri'], URI) - - def test_get_bucket_hit(self): - from gcloud.storage.bucket import Bucket - PROJECT = 'project' - BLOB_NAME = 'blob-name' - conn = self._makeOne(PROJECT) - URI = '/'.join([ - conn.API_BASE_URL, - 'storage', - conn.API_VERSION, - 'b', - '%s?project=%s' % (BLOB_NAME, PROJECT), - ]) - http = conn._http = Http( - {'status': '200', 'content-type': 'application/json'}, - '{"name": "%s"}' % BLOB_NAME, - ) - bucket = conn.get_bucket(BLOB_NAME) - self.assertTrue(isinstance(bucket, Bucket)) - self.assertTrue(bucket.connection is conn) - self.assertEqual(bucket.name, BLOB_NAME) - self.assertEqual(http._called_with['method'], 'GET') - self.assertEqual(http._called_with['uri'], URI) - - def test_create_bucket_ok(self): - from gcloud.storage.bucket import Bucket - PROJECT = 'project' - BLOB_NAME = 'blob-name' - conn = self._makeOne(PROJECT) - URI = '/'.join([ - conn.API_BASE_URL, - 'storage', - conn.API_VERSION, - 'b?project=%s' % PROJECT, - ]) - http = conn._http = Http( - {'status': '200', 'content-type': 'application/json'}, - '{"name": "%s"}' % BLOB_NAME, - ) - bucket = conn.create_bucket(BLOB_NAME) - self.assertTrue(isinstance(bucket, Bucket)) - self.assertTrue(bucket.connection is conn) - self.assertEqual(bucket.name, BLOB_NAME) - self.assertEqual(http._called_with['method'], 'POST') - self.assertEqual(http._called_with['uri'], URI) - - def test_delete_bucket_defaults_miss(self): - _deleted_blobs = [] - - PROJECT = 'project' - BLOB_NAME = 'blob-name' - conn = self._makeOne(PROJECT) - URI = '/'.join([ - conn.API_BASE_URL, - 'storage', - conn.API_VERSION, - 'b', - '%s?project=%s' % (BLOB_NAME, PROJECT), - ]) - http = conn._http = Http( - {'status': '200', 'content-type': 'application/json'}, - '{}', - ) - - self.assertEqual(conn.delete_bucket(BLOB_NAME), None) - self.assertEqual(_deleted_blobs, []) - self.assertEqual(http._called_with['method'], 'DELETE') - self.assertEqual(http._called_with['uri'], URI) - class Http(object): diff --git a/regression/storage.py b/regression/storage.py index 86899c04769b..2f0a428490e0 100644 --- a/regression/storage.py +++ b/regression/storage.py @@ -38,7 +38,7 @@ def setUpModule(): bucket_name = 'new%d' % (1000 * time.time(),) # In the **very** rare case the bucket name is reserved, this # fails with a ConnectionError. - SHARED_BUCKETS['test_bucket'] = CONNECTION.create_bucket(bucket_name) + SHARED_BUCKETS['test_bucket'] = storage.create_bucket(bucket_name) def tearDownModule(): @@ -59,8 +59,8 @@ def tearDown(self): def test_create_bucket(self): new_bucket_name = 'a-new-bucket' self.assertRaises(exceptions.NotFound, - CONNECTION.get_bucket, new_bucket_name) - created = CONNECTION.create_bucket(new_bucket_name) + storage.get_bucket, new_bucket_name) + created = storage.create_bucket(new_bucket_name) self.case_buckets_to_delete.append(new_bucket_name) self.assertEqual(created.name, new_bucket_name) @@ -72,7 +72,7 @@ def test_get_buckets(self): ] created_buckets = [] for bucket_name in buckets_to_create: - bucket = CONNECTION.create_bucket(bucket_name) + bucket = storage.create_bucket(bucket_name) self.case_buckets_to_delete.append(bucket_name) # Retrieve the buckets.