188 lines
6.5 KiB
Python
188 lines
6.5 KiB
Python
|
# 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.
|
||
|
|
||
|
"""Iterators for paging through API responses.
|
||
|
|
||
|
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 ``get_items_from_response``
|
||
|
method so that given a response (containing a page of results) it parses
|
||
|
those results into an iterable of the actual objects you want::
|
||
|
|
||
|
class MyIterator(Iterator):
|
||
|
def get_items_from_response(self, response):
|
||
|
items = response.get('items', [])
|
||
|
for item in items:
|
||
|
my_item = MyItemClass(other_arg=True)
|
||
|
my_item._set_properties(item)
|
||
|
yield my_item
|
||
|
|
||
|
You then can use this to get **all** the results from a resource::
|
||
|
|
||
|
>>> iterator = MyIterator(...)
|
||
|
>>> list(iterator) # Convert to a list (consumes all values).
|
||
|
|
||
|
Or you can walk your way through items and call off the search early if
|
||
|
you find what you're looking for (resulting in possibly fewer
|
||
|
requests)::
|
||
|
|
||
|
>>> for item in MyIterator(...):
|
||
|
>>> print item.name
|
||
|
>>> if not item.is_valid:
|
||
|
>>> break
|
||
|
"""
|
||
|
|
||
|
|
||
|
class Iterator(object):
|
||
|
"""A generic class for iterating through Cloud JSON APIs list responses.
|
||
|
|
||
|
:type client: :class:`gcloud.client.Client`
|
||
|
:param client: The client, which owns a connection to make requests.
|
||
|
|
||
|
:type path: string
|
||
|
:param path: The path to query for the list of items.
|
||
|
|
||
|
:type extra_params: dict or None
|
||
|
:param extra_params: Extra query string parameters for the API call.
|
||
|
"""
|
||
|
|
||
|
PAGE_TOKEN = 'pageToken'
|
||
|
RESERVED_PARAMS = frozenset([PAGE_TOKEN])
|
||
|
|
||
|
def __init__(self, client, path, extra_params=None):
|
||
|
self.client = client
|
||
|
self.path = path
|
||
|
self.page_number = 0
|
||
|
self.next_page_token = None
|
||
|
self.extra_params = extra_params or {}
|
||
|
reserved_in_use = self.RESERVED_PARAMS.intersection(
|
||
|
self.extra_params)
|
||
|
if reserved_in_use:
|
||
|
raise ValueError(('Using a reserved parameter',
|
||
|
reserved_in_use))
|
||
|
|
||
|
def __iter__(self):
|
||
|
"""Iterate through the list of items."""
|
||
|
while self.has_next_page():
|
||
|
response = self.get_next_page_response()
|
||
|
for item in self.get_items_from_response(response):
|
||
|
yield item
|
||
|
|
||
|
def has_next_page(self):
|
||
|
"""Determines whether or not this iterator has more pages.
|
||
|
|
||
|
:rtype: boolean
|
||
|
:returns: Whether the iterator has more pages or not.
|
||
|
"""
|
||
|
if self.page_number == 0:
|
||
|
return True
|
||
|
|
||
|
return self.next_page_token is not None
|
||
|
|
||
|
def get_query_params(self):
|
||
|
"""Getter for query parameters for the next request.
|
||
|
|
||
|
:rtype: dict
|
||
|
:returns: A dictionary of query parameters.
|
||
|
"""
|
||
|
result = ({self.PAGE_TOKEN: self.next_page_token}
|
||
|
if self.next_page_token else {})
|
||
|
result.update(self.extra_params)
|
||
|
return result
|
||
|
|
||
|
def get_next_page_response(self):
|
||
|
"""Requests the next page from the path provided.
|
||
|
|
||
|
:rtype: dict
|
||
|
:returns: The parsed JSON response of the next page's contents.
|
||
|
"""
|
||
|
if not self.has_next_page():
|
||
|
raise RuntimeError('No more pages. Try resetting the iterator.')
|
||
|
|
||
|
response = self.client.connection.api_request(
|
||
|
method='GET', path=self.path, query_params=self.get_query_params())
|
||
|
|
||
|
self.page_number += 1
|
||
|
self.next_page_token = response.get('nextPageToken')
|
||
|
|
||
|
return response
|
||
|
|
||
|
def reset(self):
|
||
|
"""Resets the iterator to the beginning."""
|
||
|
self.page_number = 0
|
||
|
self.next_page_token = None
|
||
|
|
||
|
def get_items_from_response(self, response):
|
||
|
"""Factory method called while iterating. This should be overriden.
|
||
|
|
||
|
This method should be overridden by a subclass. It should
|
||
|
accept the API response of a request for the next page of items,
|
||
|
and return a list (or other iterable) of items.
|
||
|
|
||
|
Typically this method will construct a Bucket or a Blob from the
|
||
|
page of results in the response.
|
||
|
|
||
|
:type response: dict
|
||
|
:param response: The response of asking for the next page of items.
|
||
|
|
||
|
:rtype: iterable
|
||
|
:returns: Items that the iterator should yield.
|
||
|
"""
|
||
|
raise NotImplementedError
|
||
|
|
||
|
|
||
|
class MethodIterator(object):
|
||
|
"""Method-based iterator iterating through Cloud JSON APIs list responses.
|
||
|
|
||
|
:type method: instance method
|
||
|
:param method: ``list_foo`` method of a domain object, taking as arguments
|
||
|
``page_token``, ``page_size``, and optional additional
|
||
|
keyword arguments.
|
||
|
|
||
|
:type page_token: string or ``NoneType``
|
||
|
:param page_token: Initial page token to pass. if ``None``, fetch the
|
||
|
first page from the ``method`` API call.
|
||
|
|
||
|
:type page_size: integer or ``NoneType``
|
||
|
:param page_size: Maximum number of items to return from the ``method``
|
||
|
API call; if ``None``, uses the default for the API.
|
||
|
|
||
|
:type max_calls: integer or ``NoneType``
|
||
|
:param max_calls: Maximum number of times to make the ``method``
|
||
|
API call; if ``None``, applies no limit.
|
||
|
|
||
|
:type kw: dict
|
||
|
:param kw: optional keyword argments to be passed to ``method``.
|
||
|
"""
|
||
|
def __init__(self, method, page_token=None, page_size=None,
|
||
|
max_calls=None, **kw):
|
||
|
self._method = method
|
||
|
self._token = page_token
|
||
|
self._page_size = page_size
|
||
|
self._kw = kw
|
||
|
self._max_calls = max_calls
|
||
|
self._page_num = 0
|
||
|
|
||
|
def __iter__(self):
|
||
|
while self._max_calls is None or self._page_num < self._max_calls:
|
||
|
items, new_token = self._method(
|
||
|
page_token=self._token, page_size=self._page_size, **self._kw)
|
||
|
for item in items:
|
||
|
yield item
|
||
|
if new_token is None:
|
||
|
return
|
||
|
self._page_num += 1
|
||
|
self._token = new_token
|