diff --git a/core/google/cloud/iterator.py b/core/google/cloud/iterator.py index 3721f257151f..46107753c251 100644 --- a/core/google/cloud/iterator.py +++ b/core/google/cloud/iterator.py @@ -17,22 +17,19 @@ These iterators simplify the process of paging through API responses where the response is a list of results with a ``nextPageToken``. -To make an iterator work, just override the ``PAGE_CLASS`` class -attribute so that given a response (containing a page of results) can -be parsed into an iterable page of the actual objects you want:: +To make an iterator work, you may need to override the +``ITEMS_KEY`` class attribute so that given a response (containing a page of +results) can be parsed into an iterable page of the actual objects you want:: - class MyPage(Page): + class MyIterator(Iterator): + + ITEMS_KEY = 'blocks' def _item_to_value(self, item): my_item = MyItemClass(other_arg=True) my_item._set_properties(item) return my_item - - class MyIterator(Iterator): - - PAGE_CLASS = MyPage - You then can use this to get **all** the results from a resource:: >>> iterator = MyIterator(...) @@ -69,6 +66,30 @@ class MyIterator(Iterator): 2 >>> iterator.page.remaining 19 + +It's also possible to consume an entire page and handle the paging process +manually:: + + >>> iterator = MyIterator(...) + >>> items = list(iterator.page) + >>> items + [ + , + , + , + ] + >>> iterator.page.remaining + 0 + >>> iterator.page.num_items + 3 + >>> iterator.next_page_token + 'eav1OzQB0OM8rLdGXOEsyQWSG' + >>> # And just do the same thing to consume the next page. + >>> list(iterator.page) + [ + , + , + ] """ @@ -83,16 +104,19 @@ class Page(object): :type response: dict :param response: The JSON API response for a page. - """ - ITEMS_KEY = 'items' + :type items_key: str + :param items_key: The dictionary key used to retrieve items + from the response. + """ - def __init__(self, parent, response): + def __init__(self, parent, response, items_key): self._parent = parent - items = response.get(self.ITEMS_KEY, ()) + items = response.get(items_key, ()) self._num_items = len(items) self._remaining = self._num_items self._item_iter = iter(items) + self.response = response @property def num_items(self): @@ -116,23 +140,10 @@ def __iter__(self): """The :class:`Page` is an iterator.""" return self - def _item_to_value(self, item): - """Get the next item in the page. - - This method (along with the constructor) is the workhorse - of this class. Subclasses will need to implement this method. - - :type item: dict - :param item: An item to be converted to a native object. - - :raises NotImplementedError: Always - """ - raise NotImplementedError - def next(self): """Get the next value in the iterator.""" item = six.next(self._item_iter) - result = self._item_to_value(item) + result = self._parent._item_to_value(item) # Since we've successfully got the next value from the # iterator, we update the number of remaining. self._remaining -= 1 @@ -145,7 +156,8 @@ def next(self): class Iterator(object): """A generic class for iterating through Cloud JSON APIs list responses. - Sub-classes need to over-write ``PAGE_CLASS``. + Sub-classes need to over-write :attr:`ITEMS_KEY` and to define + :meth:`_item_to_value`. :type client: :class:`google.cloud.client.Client` :param client: The client, which owns a connection to make requests. @@ -166,8 +178,9 @@ class Iterator(object): PAGE_TOKEN = 'pageToken' MAX_RESULTS = 'maxResults' RESERVED_PARAMS = frozenset([PAGE_TOKEN, MAX_RESULTS]) - PAGE_CLASS = Page PATH = None + ITEMS_KEY = 'items' + """The dictionary key used to retrieve items from each response.""" def __init__(self, client, page_token=None, max_results=None, extra_params=None, path=None): @@ -200,6 +213,7 @@ def page(self): :rtype: :class:`Page` :returns: The page of items that has been retrieved. """ + self._update_page() return self._page def __iter__(self): @@ -207,24 +221,38 @@ def __iter__(self): return self def _update_page(self): - """Replace the current page. + """Update the current page if needed. - Does nothing if the current page is non-null and has items - remaining. + Subclasses will need to implement this method if they + use data from the ``response`` other than the items. + :rtype: bool + :returns: Flag indicated if the page was updated. :raises: :class:`~exceptions.StopIteration` if there is no next page. """ - if self.page is not None and self.page.remaining > 0: - return - if self.has_next_page(): + if self._page is not None and self._page.remaining > 0: + return False + elif self.has_next_page(): response = self._get_next_page_response() - self._page = self.PAGE_CLASS(self, response) + self._page = Page(self, response, self.ITEMS_KEY) + return True else: raise StopIteration + def _item_to_value(self, item): + """Get the next item in the page. + + Subclasses will need to implement this method. + + :type item: dict + :param item: An item to be converted to a native object. + + :raises NotImplementedError: Always + """ + raise NotImplementedError + def next(self): """Get the next value in the iterator.""" - self._update_page() item = six.next(self.page) self.num_results += 1 return item diff --git a/core/unit_tests/test_iterator.py b/core/unit_tests/test_iterator.py index 7f0a80b4e335..d788bcc486e7 100644 --- a/core/unit_tests/test_iterator.py +++ b/core/unit_tests/test_iterator.py @@ -25,41 +25,34 @@ def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_constructor(self): - klass = self._getTargetClass() parent = object() - response = {klass.ITEMS_KEY: (1, 2, 3)} - page = self._makeOne(parent, response) + items_key = 'potatoes' + response = {items_key: (1, 2, 3)} + page = self._makeOne(parent, response, items_key) self.assertIs(page._parent, parent) self.assertEqual(page._num_items, 3) self.assertEqual(page._remaining, 3) def test_num_items_property(self): - page = self._makeOne(None, {}) + page = self._makeOne(None, {}, '') num_items = 42 page._num_items = num_items self.assertEqual(page.num_items, num_items) def test_remaining_property(self): - page = self._makeOne(None, {}) + page = self._makeOne(None, {}, '') remaining = 1337 page._remaining = remaining self.assertEqual(page.remaining, remaining) def test___iter__(self): - page = self._makeOne(None, {}) + page = self._makeOne(None, {}, '') self.assertIs(iter(page), page) - def test__item_to_value(self): - page = self._makeOne(None, {}) - with self.assertRaises(NotImplementedError): - page._item_to_value(None) - def test_iterator_calls__item_to_value(self): import six - klass = self._getTargetClass() - - class CountItPage(klass): + class Parent(object): calls = 0 values = None @@ -68,20 +61,22 @@ def _item_to_value(self, item): self.calls += 1 return item - response = {klass.ITEMS_KEY: [10, 11, 12]} - page = CountItPage(None, response) + items_key = 'turkeys' + response = {items_key: [10, 11, 12]} + parent = Parent() + page = self._makeOne(parent, response, items_key) page._remaining = 100 - self.assertEqual(page.calls, 0) + self.assertEqual(parent.calls, 0) self.assertEqual(page.remaining, 100) self.assertEqual(six.next(page), 10) - self.assertEqual(page.calls, 1) + self.assertEqual(parent.calls, 1) self.assertEqual(page.remaining, 99) self.assertEqual(six.next(page), 11) - self.assertEqual(page.calls, 2) + self.assertEqual(parent.calls, 2) self.assertEqual(page.remaining, 98) self.assertEqual(six.next(page), 12) - self.assertEqual(page.calls, 3) + self.assertEqual(parent.calls, 3) self.assertEqual(page.remaining, 97) @@ -132,7 +127,6 @@ def test___iter__(self): def test_iterate(self): import six - from google.cloud.iterator import Page path = '/foo' key1 = 'key1' @@ -140,7 +134,9 @@ def test_iterate(self): item1, item2 = object(), object() ITEMS = {key1: item1, key2: item2} - class _Page(Page): + klass = self._getTargetClass() + + class WithItemToValue(klass): def _item_to_value(self, item): return ITEMS[item['name']] @@ -148,8 +144,7 @@ def _item_to_value(self, item): connection = _Connection( {'items': [{'name': key1}, {'name': key2}]}) client = _Client(connection) - iterator = self._makeOne(client, path=path) - iterator.PAGE_CLASS = _Page + iterator = WithItemToValue(client, path=path) self.assertEqual(iterator.num_results, 0) val1 = six.next(iterator) @@ -274,6 +269,11 @@ def test__get_next_page_response_new_no_token_in_response(self): self.assertEqual(kw['path'], path) self.assertEqual(kw['query_params'], {}) + def test__item_to_value_virtual(self): + iterator = self._makeOne(None) + with self.assertRaises(NotImplementedError): + iterator._item_to_value({}) + def test_reset(self): connection = _Connection() client = _Client(connection) @@ -287,7 +287,7 @@ def test_reset(self): self.assertEqual(iterator.page_number, 0) self.assertEqual(iterator.num_results, 0) self.assertIsNone(iterator.next_page_token) - self.assertIsNone(iterator.page) + self.assertIsNone(iterator._page) class _Connection(object): diff --git a/resource_manager/google/cloud/resource_manager/client.py b/resource_manager/google/cloud/resource_manager/client.py index 1f205edb6fc9..5d9e50c07efe 100644 --- a/resource_manager/google/cloud/resource_manager/client.py +++ b/resource_manager/google/cloud/resource_manager/client.py @@ -17,7 +17,6 @@ from google.cloud.client import Client as BaseClient from google.cloud.iterator import Iterator -from google.cloud.iterator import Page from google.cloud.resource_manager.connection import Connection from google.cloud.resource_manager.project import Project @@ -159,30 +158,6 @@ def list_projects(self, filter_params=None, page_size=None): return _ProjectIterator(self, extra_params=extra_params) -class _ProjectPage(Page): - """Iterator for a single page of results. - - :type parent: :class:`_ProjectIterator` - :param parent: The iterator that owns the current page. - - :type response: dict - :param response: The JSON API response for a page of projects. - """ - - ITEMS_KEY = 'projects' - - def _item_to_value(self, resource): - """Convert a JSON project to the native object. - - :type resource: dict - :param resource: An resource to be converted to a project. - - :rtype: :class:`.Project` - :returns: The next project in the page. - """ - return Project.from_api_repr(resource, client=self._parent.client) - - class _ProjectIterator(Iterator): """An iterator over a list of Project resources. @@ -204,5 +179,16 @@ class _ProjectIterator(Iterator): the API call. """ - PAGE_CLASS = _ProjectPage PATH = '/projects' + ITEMS_KEY = 'projects' + + def _item_to_value(self, resource): + """Convert a JSON project to the native object. + + :type resource: dict + :param resource: An resource to be converted to a project. + + :rtype: :class:`.Project` + :returns: The next project in the page. + """ + return Project.from_api_repr(resource, client=self.client) diff --git a/resource_manager/unit_tests/test_client.py b/resource_manager/unit_tests/test_client.py index fbfbeb4c7c63..8eb58e730f07 100644 --- a/resource_manager/unit_tests/test_client.py +++ b/resource_manager/unit_tests/test_client.py @@ -15,27 +15,36 @@ import unittest -class Test__ProjectPage(unittest.TestCase): +class Test__ProjectIterator(unittest.TestCase): def _getTargetClass(self): - from google.cloud.resource_manager.client import _ProjectPage - return _ProjectPage + from google.cloud.resource_manager.client import _ProjectIterator + return _ProjectIterator def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) - def test_empty_response(self): - from google.cloud.resource_manager.client import _ProjectIterator + def test_constructor(self): + client = object() + iterator = self._makeOne(client) + self.assertEqual(iterator.path, '/projects') + self.assertEqual(iterator.page_number, 0) + self.assertIsNone(iterator.next_page_token) + self.assertIs(iterator.client, client) + self.assertEqual(iterator.extra_params, {}) + + def test_page_empty_response(self): + from google.cloud.iterator import Page client = object() - iterator = _ProjectIterator(client) - page = self._makeOne(iterator, {}) + iterator = self._makeOne(client) + page = Page(iterator, {}, iterator.ITEMS_KEY) + iterator._page = page self.assertEqual(page.num_items, 0) self.assertEqual(page.remaining, 0) self.assertEqual(list(page), []) - def test_non_empty_response(self): - from google.cloud.resource_manager.client import _ProjectIterator + def test_page_non_empty_response(self): from google.cloud.resource_manager.project import Project project_id = 'project-id' @@ -51,13 +60,17 @@ def test_non_empty_response(self): 'lifecycleState': project_lifecycle_state, } response = {'projects': [api_resource]} - client = object() - iterator = _ProjectIterator(client) - page = self._makeOne(iterator, response) + def dummy_response(): + return response + + iterator = self._makeOne(client) + iterator._get_next_page_response = dummy_response + + page = iterator.page self.assertEqual(page.num_items, 1) - project = page.next() + project = iterator.next() self.assertEqual(page.remaining, 0) self.assertIsInstance(project, Project) self.assertEqual(project.project_id, project_id) @@ -68,25 +81,6 @@ def test_non_empty_response(self): self.assertEqual(project.status, project_lifecycle_state) -class Test__ProjectIterator(unittest.TestCase): - - def _getTargetClass(self): - from google.cloud.resource_manager.client import _ProjectIterator - return _ProjectIterator - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_constructor(self): - client = object() - iterator = self._makeOne(client) - self.assertEqual(iterator.path, '/projects') - self.assertEqual(iterator.page_number, 0) - self.assertIsNone(iterator.next_page_token) - self.assertIs(iterator.client, client) - self.assertEqual(iterator.extra_params, {}) - - class TestClient(unittest.TestCase): def _getTargetClass(self): diff --git a/storage/google/cloud/storage/bucket.py b/storage/google/cloud/storage/bucket.py index c8e56a4dbcf1..e1b04ea224cd 100644 --- a/storage/google/cloud/storage/bucket.py +++ b/storage/google/cloud/storage/bucket.py @@ -21,7 +21,6 @@ from google.cloud._helpers import _rfc3339_to_datetime from google.cloud.exceptions import NotFound from google.cloud.iterator import Iterator -from google.cloud.iterator import Page from google.cloud.storage._helpers import _PropertyMixin from google.cloud.storage._helpers import _scalar_property from google.cloud.storage.acl import BucketACL @@ -29,37 +28,6 @@ from google.cloud.storage.blob import Blob -class _BlobPage(Page): - """Iterator for a single page of results. - - :type parent: :class:`_BlobIterator` - :param parent: The iterator that owns the current page. - - :type response: dict - :param response: The JSON API response for a page of blobs. - """ - - def __init__(self, parent, response): - super(_BlobPage, self).__init__(parent, response) - # Grab the prefixes from the response. - self._prefixes = tuple(response.get('prefixes', ())) - parent.prefixes.update(self._prefixes) - - def _item_to_value(self, item): - """Convert a JSON blob to the native object. - - :type item: dict - :param item: An item to be converted to a blob. - - :rtype: :class:`.Blob` - :returns: The next blob in the page. - """ - name = item.get('name') - blob = Blob(name, bucket=self._parent.bucket) - blob._set_properties(item) - return blob - - class _BlobIterator(Iterator): """An iterator listing blobs in a bucket @@ -83,8 +51,6 @@ class _BlobIterator(Iterator): Defaults to the bucket's client. """ - PAGE_CLASS = _BlobPage - def __init__(self, bucket, page_token=None, max_results=None, extra_params=None, client=None): if client is None: @@ -96,6 +62,37 @@ def __init__(self, bucket, page_token=None, max_results=None, page_token=page_token, max_results=max_results, extra_params=extra_params) + def _item_to_value(self, item): + """Convert a JSON blob to the native object. + + :type item: dict + :param item: An item to be converted to a blob. + + :rtype: :class:`.Blob` + :returns: The next blob in the page. + """ + name = item.get('name') + blob = Blob(name, bucket=self.bucket) + blob._set_properties(item) + return blob + + def _update_page(self): + """Update the current page if needed. + + If the current page was updated, also updates the cumulative + prefixes list on this iterator and sets the local prefixes on + the page. + + :rtype: bool + :returns: Flag indicated if the page was updated. + """ + updated = super(_BlobIterator, self)._update_page() + if updated: + prefixes = tuple(self._page.response.get('prefixes', ())) + self._page.prefixes = prefixes + self.prefixes.update(prefixes) + return updated + class Bucket(_PropertyMixin): """A class representing a Bucket on Cloud Storage. diff --git a/storage/google/cloud/storage/client.py b/storage/google/cloud/storage/client.py index 94e0d9406ffa..96c7b88a660d 100644 --- a/storage/google/cloud/storage/client.py +++ b/storage/google/cloud/storage/client.py @@ -19,7 +19,6 @@ from google.cloud.client import JSONClient from google.cloud.exceptions import NotFound from google.cloud.iterator import Iterator -from google.cloud.iterator import Page from google.cloud.storage.batch import Batch from google.cloud.storage.bucket import Bucket from google.cloud.storage.connection import Connection @@ -272,39 +271,14 @@ def list_buckets(self, max_results=None, page_token=None, prefix=None, return result -class _BucketPage(Page): - """Iterator for a single page of results. - - :type parent: :class:`_BucketIterator` - :param parent: The iterator that owns the current page. - - :type response: dict - :param response: The JSON API response for a page of buckets. - """ - - def _item_to_value(self, item): - """Convert a JSON bucket to the native object. - - :type item: dict - :param item: An item to be converted to a bucket. - - :rtype: :class:`.Bucket` - :returns: The next bucket in the page. - """ - name = item.get('name') - bucket = Bucket(self._parent.client, name) - bucket._set_properties(item) - return bucket - - 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:`google.cloud.storage.connection.Connection` + helper methods on :class:`~google.cloud.storage.connection.Connection` objects. - :type client: :class:`google.cloud.storage.client.Client` + :type client: :class:`~google.cloud.storage.client.Client` :param client: The client to use for making connections. :type page_token: str @@ -317,5 +291,18 @@ class _BucketIterator(Iterator): :param extra_params: Extra query string parameters for the API call. """ - PAGE_CLASS = _BucketPage PATH = '/b' + + def _item_to_value(self, item): + """Convert a JSON bucket to the native object. + + :type item: dict + :param item: An item to be converted to a bucket. + + :rtype: :class:`.Bucket` + :returns: The next bucket in the page. + """ + name = item.get('name') + bucket = Bucket(self.client, name) + bucket._set_properties(item) + return bucket diff --git a/storage/unit_tests/test_bucket.py b/storage/unit_tests/test_bucket.py index 924789a102ca..9584d6d506b7 100644 --- a/storage/unit_tests/test_bucket.py +++ b/storage/unit_tests/test_bucket.py @@ -15,72 +15,66 @@ import unittest -class Test__BlobPage(unittest.TestCase): +class Test__BlobIterator(unittest.TestCase): def _getTargetClass(self): - from google.cloud.storage.bucket import _BlobPage - return _BlobPage + from google.cloud.storage.bucket import _BlobIterator + return _BlobIterator def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) - def test_empty_response(self): - from google.cloud.storage.bucket import _BlobIterator + def test_ctor(self): + connection = _Connection() + client = _Client(connection) + bucket = _Bucket() + iterator = self._makeOne(bucket, client=client) + self.assertIs(iterator.bucket, bucket) + self.assertIs(iterator.client, client) + self.assertEqual(iterator.path, '%s/o' % bucket.path) + self.assertEqual(iterator.page_number, 0) + self.assertIsNone(iterator.next_page_token) + self.assertEqual(iterator.prefixes, set()) + + def test_page_empty_response(self): + from google.cloud.iterator import Page connection = _Connection() client = _Client(connection) bucket = _Bucket() - iterator = _BlobIterator(bucket, client=client) - page = self._makeOne(iterator, {}) + iterator = self._makeOne(bucket, client=client) + page = Page(iterator, {}, iterator.ITEMS_KEY) + iterator._page = page blobs = list(page) self.assertEqual(blobs, []) self.assertEqual(iterator.prefixes, set()) - def test_non_empty_response(self): + def test_page_non_empty_response(self): from google.cloud.storage.blob import Blob - from google.cloud.storage.bucket import _BlobIterator blob_name = 'blob-name' response = {'items': [{'name': blob_name}], 'prefixes': ['foo']} connection = _Connection() client = _Client(connection) bucket = _Bucket() - iterator = _BlobIterator(bucket, client=client) - page = self._makeOne(iterator, response) - self.assertEqual(page._prefixes, ('foo',)) + def dummy_response(): + return response + + iterator = self._makeOne(bucket, client=client) + iterator._get_next_page_response = dummy_response + + page = iterator.page + self.assertEqual(page.prefixes, ('foo',)) self.assertEqual(page.num_items, 1) - blob = page.next() + blob = iterator.next() self.assertEqual(page.remaining, 0) self.assertIsInstance(blob, Blob) self.assertEqual(blob.name, blob_name) self.assertEqual(iterator.prefixes, set(['foo'])) - -class Test__BlobIterator(unittest.TestCase): - - def _getTargetClass(self): - from google.cloud.storage.bucket import _BlobIterator - return _BlobIterator - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - connection = _Connection() - client = _Client(connection) - bucket = _Bucket() - iterator = self._makeOne(bucket, client=client) - self.assertIs(iterator.bucket, bucket) - self.assertIs(iterator.client, client) - self.assertEqual(iterator.path, '%s/o' % bucket.path) - self.assertEqual(iterator.page_number, 0) - self.assertIsNone(iterator.next_page_token) - self.assertEqual(iterator.prefixes, set()) - def test_cumulative_prefixes(self): from google.cloud.storage.blob import Blob - from google.cloud.storage.bucket import _BlobPage BLOB_NAME = 'blob-name1' response1 = { @@ -94,19 +88,26 @@ def test_cumulative_prefixes(self): connection = _Connection() client = _Client(connection) bucket = _Bucket() + responses = [response1, response2] + + def dummy_response(): + return responses.pop(0) + iterator = self._makeOne(bucket, client=client) + iterator._get_next_page_response = dummy_response + # Parse first response. - page1 = _BlobPage(iterator, response1) - self.assertEqual(page1._prefixes, ('foo',)) + page1 = iterator.page + self.assertEqual(page1.prefixes, ('foo',)) self.assertEqual(page1.num_items, 1) - blob = page1.next() + blob = iterator.next() self.assertEqual(page1.remaining, 0) self.assertIsInstance(blob, Blob) self.assertEqual(blob.name, BLOB_NAME) self.assertEqual(iterator.prefixes, set(['foo'])) # Parse second response. - page2 = _BlobPage(iterator, response2) - self.assertEqual(page2._prefixes, ('bar',)) + page2 = iterator.page + self.assertEqual(page2.prefixes, ('bar',)) self.assertEqual(page2.num_items, 0) self.assertEqual(iterator.prefixes, set(['foo', 'bar'])) @@ -970,7 +971,6 @@ def test_make_public_w_future_reload_default(self): def test_make_public_recursive(self): from google.cloud.storage.acl import _ACLEntity from google.cloud.storage.bucket import _BlobIterator - from google.cloud.storage.bucket import _BlobPage _saved = [] @@ -996,12 +996,9 @@ def save(self, client=None): _saved.append( (self._bucket, self._name, self._granted, client)) - class _Page(_BlobPage): - def _item_to_value(self, item): - return _Blob(self._parent.bucket, item['name']) - class _Iterator(_BlobIterator): - PAGE_CLASS = _Page + def _item_to_value(self, item): + return _Blob(self.bucket, item['name']) NAME = 'name' BLOB_NAME = 'blob-name' diff --git a/storage/unit_tests/test_client.py b/storage/unit_tests/test_client.py index f75965c268fb..c2b2b79274de 100644 --- a/storage/unit_tests/test_client.py +++ b/storage/unit_tests/test_client.py @@ -370,60 +370,56 @@ def test_list_buckets_all_arguments(self): self.assertEqual(parse_qs(uri_parts.query), EXPECTED_QUERY) -class Test__BucketPage(unittest.TestCase): +class Test__BucketIterator(unittest.TestCase): def _getTargetClass(self): - from google.cloud.storage.client import _BucketPage - return _BucketPage + from google.cloud.storage.client import _BucketIterator + return _BucketIterator def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) - def test_empty_response(self): - from google.cloud.storage.client import _BucketIterator + def test_ctor(self): + connection = object() + client = _Client(connection) + iterator = self._makeOne(client) + self.assertEqual(iterator.path, '/b') + self.assertEqual(iterator.page_number, 0) + self.assertIsNone(iterator.next_page_token) + self.assertIs(iterator.client, client) + + def test_page_empty_response(self): + from google.cloud.iterator import Page connection = object() client = _Client(connection) - iterator = _BucketIterator(client) - page = self._makeOne(iterator, {}) + iterator = self._makeOne(client) + page = Page(iterator, {}, iterator.ITEMS_KEY) + iterator._page = page self.assertEqual(list(page), []) - def test_non_empty_response(self): + def test_page_non_empty_response(self): from google.cloud.storage.bucket import Bucket - from google.cloud.storage.client import _BucketIterator BLOB_NAME = 'blob-name' response = {'items': [{'name': BLOB_NAME}]} connection = object() client = _Client(connection) - iterator = _BucketIterator(client) - page = self._makeOne(iterator, response) + + def dummy_response(): + return response + + iterator = self._makeOne(client) + iterator._get_next_page_response = dummy_response + + page = iterator.page self.assertEqual(page.num_items, 1) - bucket = page.next() + bucket = iterator.next() self.assertEqual(page.remaining, 0) self.assertIsInstance(bucket, Bucket) self.assertEqual(bucket.name, BLOB_NAME) -class Test__BucketIterator(unittest.TestCase): - - def _getTargetClass(self): - from google.cloud.storage.client import _BucketIterator - return _BucketIterator - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - connection = object() - client = _Client(connection) - iterator = self._makeOne(client) - self.assertEqual(iterator.path, '/b') - self.assertEqual(iterator.page_number, 0) - self.assertIsNone(iterator.next_page_token) - self.assertIs(iterator.client, client) - - class _Credentials(object): _scopes = None