diff --git a/docs/_components/storage-getting-started.rst b/docs/_components/storage-getting-started.rst index 69d85e325a9a3..4bb914e14c64f 100644 --- a/docs/_components/storage-getting-started.rst +++ b/docs/_components/storage-getting-started.rst @@ -186,13 +186,8 @@ If you have a full bucket, you can delete it this way:: Listing available buckets ------------------------- -The :class:`Connection ` object -itself is iterable, so you can loop over it, or call ``list`` on it to get -a list object:: - - >>> for bucket in connection.get_all_buckets(): + >>> for bucket in storage.get_all_buckets(connection): ... print bucket.name - >>> print list(connection) Managing access control ----------------------- diff --git a/docs/_components/storage-quickstart.rst b/docs/_components/storage-quickstart.rst index 3d1f4dc346a06..64a83f3902d2c 100644 --- a/docs/_components/storage-quickstart.rst +++ b/docs/_components/storage-quickstart.rst @@ -55,7 +55,8 @@ and instantiating the demo connection:: Once you have the connection, you can create buckets and blobs:: - >>> connection.get_all_buckets() + >>> from gcloud import storage + >>> storage.get_all_buckets(connection) [, ...] >>> bucket = connection.create_bucket('my-new-bucket') >>> print bucket diff --git a/gcloud/storage/__init__.py b/gcloud/storage/__init__.py index ab1079dd07792..26fdf0c21c20b 100644 --- a/gcloud/storage/__init__.py +++ b/gcloud/storage/__init__.py @@ -44,6 +44,7 @@ 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 get_all_buckets from gcloud.storage.blob import Blob from gcloud.storage.bucket import Bucket from gcloud.storage.connection import Connection diff --git a/gcloud/storage/api.py b/gcloud/storage/api.py new file mode 100644 index 0000000000000..36a781a5ed705 --- /dev/null +++ b/gcloud/storage/api.py @@ -0,0 +1,72 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Methods for interacting with Google Cloud Storage. + +Allows interacting with Cloud Storage via user-friendly objects +rather than via Connection. +""" + +from gcloud.storage._implicit_environ import get_default_connection +from gcloud.storage.bucket import Bucket +from gcloud.storage.iterator import Iterator + + +def get_all_buckets(connection=None): + """Get all buckets in the project. + + This will not populate the list of blobs available in each + bucket. + + >>> from gcloud import storage + >>> for bucket in storage.get_all_buckets(): + >>> print bucket + + This implements "storage.buckets.list". + + :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: list of :class:`gcloud.storage.bucket.Bucket` objects. + :returns: All buckets belonging to this project. + """ + if connection is None: + connection = get_default_connection() + return iter(_BucketIterator(connection=connection)) + + +class _BucketIterator(Iterator): + """An iterator listing all buckets. + + You shouldn't have to use this directly, but instead should use the + helper methods on :class:`gcloud.storage.connection.Connection` + objects. + + :type connection: :class:`gcloud.storage.connection.Connection` + :param connection: The connection to use for querying the list of buckets. + """ + + def __init__(self, connection): + super(_BucketIterator, self).__init__(connection=connection, path='/b') + + def get_items_from_response(self, response): + """Factory method which yields :class:`.Bucket` items from a response. + + :type response: dict + :param response: The JSON API response for a page of buckets. + """ + for item in response.get('items', []): + yield Bucket(properties=item, connection=self.connection) diff --git a/gcloud/storage/connection.py b/gcloud/storage/connection.py index d00b37b8d24ef..eac467df313f8 100644 --- a/gcloud/storage/connection.py +++ b/gcloud/storage/connection.py @@ -21,7 +21,6 @@ from gcloud import connection as base_connection from gcloud.exceptions import make_exception from gcloud.storage.bucket import Bucket -from gcloud.storage.iterator import Iterator class Connection(base_connection.Connection): @@ -57,13 +56,6 @@ class Connection(base_connection.Connection): If you want to access an existing bucket:: >>> bucket = connection.get_bucket('my-bucket-name') - - You can also iterate through all :class:`gcloud.storage.bucket.Bucket` - objects inside the project:: - - >>> for bucket in connection.get_all_buckets(): - >>> print bucket - """ API_BASE_URL = base_connection.API_BASE_URL @@ -268,27 +260,6 @@ def api_request(self, method, path, query_params=None, return content - def get_all_buckets(self): - """Get all buckets in the project. - - This will not populate the list of blobs available in each - bucket. - - You can also iterate over the connection object, so these two - operations are identical:: - - >>> from gcloud import storage - >>> connection = storage.get_connection(project) - >>> for bucket in connection.get_all_buckets(): - >>> print bucket - - This implements "storage.buckets.list". - - :rtype: list of :class:`gcloud.storage.bucket.Bucket` objects. - :returns: All buckets belonging to this project. - """ - return iter(_BucketIterator(connection=self)) - def get_bucket(self, bucket_name): """Get a bucket by name. @@ -377,27 +348,3 @@ def delete_bucket(self, bucket_name): """ bucket_path = Bucket.path_helper(bucket_name) self.api_request(method='DELETE', path=bucket_path) - - -class _BucketIterator(Iterator): - """An iterator listing all buckets. - - You shouldn't have to use this directly, but instead should use the - helper methods on :class:`gcloud.storage.connection.Connection` - objects. - - :type connection: :class:`gcloud.storage.connection.Connection` - :param connection: The connection to use for querying the list of buckets. - """ - - def __init__(self, connection): - super(_BucketIterator, self).__init__(connection=connection, path='/b') - - def get_items_from_response(self, response): - """Factory method which yields :class:`.Bucket` items from a response. - - :type response: dict - :param response: The JSON API response for a page of buckets. - """ - for item in response.get('items', []): - yield Bucket(properties=item, connection=self.connection) diff --git a/gcloud/storage/demo/demo.py b/gcloud/storage/demo/demo.py index ff5912b8913b5..7a42e8ff3f7d2 100644 --- a/gcloud/storage/demo/demo.py +++ b/gcloud/storage/demo/demo.py @@ -1,18 +1,3 @@ -# Copyright 2014 Google Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# pragma NO COVER # Welcome to the gCloud Storage Demo! (hit enter) # We're going to walk through some of the basics..., @@ -21,12 +6,13 @@ # Let's start by importing the demo module and getting a connection: import time +from gcloud import storage from gcloud.storage import demo connection = demo.get_connection() # OK, now let's look at all of the buckets... -print(connection.get_all_buckets()) # This might take a second... +print(list(storage.get_all_buckets(connection))) # This might take a second... # Now let's create a new bucket... bucket_name = ("bucket-%s" % time.time()).replace(".", "") # Get rid of dots. @@ -35,7 +21,7 @@ print(bucket) # Let's look at all of the buckets again... -print(connection.get_all_buckets()) +print(list(storage.get_all_buckets(connection))) # How about we create a new blob inside this bucket. blob = bucket.new_blob("my-new-file.txt") diff --git a/gcloud/storage/test_api.py b/gcloud/storage/test_api.py new file mode 100644 index 0000000000000..c27e6e979d088 --- /dev/null +++ b/gcloud/storage/test_api.py @@ -0,0 +1,125 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest2 + + +class Test__BucketIterator(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.storage.api import _BucketIterator + return _BucketIterator + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor(self): + connection = object() + iterator = self._makeOne(connection) + self.assertTrue(iterator.connection is connection) + self.assertEqual(iterator.path, '/b') + self.assertEqual(iterator.page_number, 0) + self.assertEqual(iterator.next_page_token, None) + + def test_get_items_from_response_empty(self): + connection = object() + iterator = self._makeOne(connection) + self.assertEqual(list(iterator.get_items_from_response({})), []) + + def test_get_items_from_response_non_empty(self): + from gcloud.storage.bucket import Bucket + BLOB_NAME = 'blob-name' + response = {'items': [{'name': BLOB_NAME}]} + connection = object() + iterator = self._makeOne(connection) + buckets = list(iterator.get_items_from_response(response)) + self.assertEqual(len(buckets), 1) + bucket = buckets[0] + self.assertTrue(isinstance(bucket, Bucket)) + self.assertTrue(bucket.connection is connection) + self.assertEqual(bucket.name, BLOB_NAME) + + +class Test_get_all_buckets(unittest2.TestCase): + + def _callFUT(self, connection=None): + from gcloud.storage.api import get_all_buckets + return get_all_buckets(connection=connection) + + def test_empty(self): + from gcloud.storage.connection import Connection + PROJECT = 'project' + 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'}, + '{}', + ) + buckets = list(self._callFUT(conn)) + self.assertEqual(len(buckets), 0) + self.assertEqual(http._called_with['method'], 'GET') + self.assertEqual(http._called_with['uri'], URI) + + def _get_all_buckets_non_empty_helper(self, use_default=False): + from gcloud.storage._testing import _monkey_defaults + from gcloud.storage.connection import Connection + PROJECT = 'project' + BUCKET_NAME = 'bucket-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'}, + '{"items": [{"name": "%s"}]}' % BUCKET_NAME, + ) + + if use_default: + with _monkey_defaults(connection=conn): + buckets = list(self._callFUT()) + else: + buckets = list(self._callFUT(conn)) + + self.assertEqual(len(buckets), 1) + self.assertEqual(buckets[0].name, BUCKET_NAME) + self.assertEqual(http._called_with['method'], 'GET') + self.assertEqual(http._called_with['uri'], URI) + + def test_non_empty(self): + self._get_all_buckets_non_empty_helper(use_default=False) + + def test_non_use_default(self): + self._get_all_buckets_non_empty_helper(use_default=True) + + +class Http(object): + + _called_with = None + + def __init__(self, headers, content): + from httplib2 import Response + self._response = Response(headers) + self._content = content + + def request(self, **kw): + self._called_with = kw + return self._response, self._content diff --git a/gcloud/storage/test_connection.py b/gcloud/storage/test_connection.py index 1073f0969d18e..1a79fb2c89ad3 100644 --- a/gcloud/storage/test_connection.py +++ b/gcloud/storage/test_connection.py @@ -281,44 +281,6 @@ def test_api_request_w_500(self): ) self.assertRaises(InternalServerError, conn.api_request, 'GET', '/') - def test_get_all_buckets_empty(self): - PROJECT = 'project' - 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'}, - '{}', - ) - buckets = list(conn.get_all_buckets()) - self.assertEqual(len(buckets), 0) - self.assertEqual(http._called_with['method'], 'GET') - self.assertEqual(http._called_with['uri'], URI) - - def test_get_all_buckets_non_empty(self): - PROJECT = 'project' - BUCKET_NAME = 'bucket-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'}, - '{"items": [{"name": "%s"}]}' % BUCKET_NAME, - ) - buckets = list(conn.get_all_buckets()) - self.assertEqual(len(buckets), 1) - self.assertEqual(buckets[0].name, BUCKET_NAME) - self.assertEqual(http._called_with['method'], 'GET') - self.assertEqual(http._called_with['uri'], URI) - def test_get_bucket_miss(self): from gcloud.exceptions import NotFound PROJECT = 'project' @@ -408,42 +370,6 @@ def test_delete_bucket_defaults_miss(self): self.assertEqual(http._called_with['uri'], URI) -class Test__BucketIterator(unittest2.TestCase): - - def _getTargetClass(self): - from gcloud.storage.connection import _BucketIterator - return _BucketIterator - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - connection = object() - iterator = self._makeOne(connection) - self.assertTrue(iterator.connection is connection) - self.assertEqual(iterator.path, '/b') - self.assertEqual(iterator.page_number, 0) - self.assertEqual(iterator.next_page_token, None) - - def test_get_items_from_response_empty(self): - connection = object() - iterator = self._makeOne(connection) - self.assertEqual(list(iterator.get_items_from_response({})), []) - - def test_get_items_from_response_non_empty(self): - from gcloud.storage.bucket import Bucket - BLOB_NAME = 'blob-name' - response = {'items': [{'name': BLOB_NAME}]} - connection = object() - iterator = self._makeOne(connection) - buckets = list(iterator.get_items_from_response(response)) - self.assertEqual(len(buckets), 1) - bucket = buckets[0] - self.assertTrue(isinstance(bucket, Bucket)) - self.assertTrue(bucket.connection is connection) - self.assertEqual(bucket.name, BLOB_NAME) - - class Http(object): _called_with = None diff --git a/regression/storage.py b/regression/storage.py index b01fa2d9216d5..86899c04769b1 100644 --- a/regression/storage.py +++ b/regression/storage.py @@ -76,7 +76,7 @@ def test_get_buckets(self): self.case_buckets_to_delete.append(bucket_name) # Retrieve the buckets. - all_buckets = CONNECTION.get_all_buckets() + all_buckets = storage.get_all_buckets() created_buckets = [bucket for bucket in all_buckets if bucket.name in buckets_to_create] self.assertEqual(len(created_buckets), len(buckets_to_create))