1717These iterators simplify the process of paging through API responses
1818where the response is a list of results with a ``nextPageToken``.
1919
20- To make an iterator work, just override the ``PAGE_CLASS`` class
21- attribute so that given a response (containing a page of results) can
22- be parsed into an iterable page of the actual objects you want::
20+ To make an iterator work, you may need to override the
21+ ``ITEMS_KEY`` class attribute so that given a response (containing a page of
22+ results) can be parsed into an iterable page of the actual objects you want::
2323
24- class MyPage(Page):
24+ class MyIterator(Iterator):
25+
26+ ITEMS_KEY = 'blocks'
2527
2628 def _item_to_value(self, item):
2729 my_item = MyItemClass(other_arg=True)
2830 my_item._set_properties(item)
2931 return my_item
3032
31-
32- class MyIterator(Iterator):
33-
34- PAGE_CLASS = MyPage
35-
3633You then can use this to get **all** the results from a resource::
3734
3835 >>> iterator = MyIterator(...)
@@ -69,6 +66,30 @@ class MyIterator(Iterator):
6966 2
7067 >>> iterator.page.remaining
7168 19
69+
70+ It's also possible to consume an entire page and handle the paging process
71+ manually::
72+
73+ >>> iterator = MyIterator(...)
74+ >>> items = list(iterator.page)
75+ >>> items
76+ [
77+ <MyItemClass at 0x7fd64a098ad0>,
78+ <MyItemClass at 0x7fd64a098ed0>,
79+ <MyItemClass at 0x7fd64a098e90>,
80+ ]
81+ >>> iterator.page.remaining
82+ 0
83+ >>> iterator.page.num_items
84+ 3
85+ >>> iterator.next_page_token
86+ 'eav1OzQB0OM8rLdGXOEsyQWSG'
87+ >>> # And just do the same thing to consume the next page.
88+ >>> list(iterator.page)
89+ [
90+ <MyItemClass at 0x7fea740abdd0>,
91+ <MyItemClass at 0x7fea740abe50>,
92+ ]
7293"""
7394
7495
@@ -83,16 +104,19 @@ class Page(object):
83104
84105 :type response: dict
85106 :param response: The JSON API response for a page.
86- """
87107
88- ITEMS_KEY = 'items'
108+ :type items_key: str
109+ :param items_key: The dictionary key used to retrieve items
110+ from the response.
111+ """
89112
90- def __init__ (self , parent , response ):
113+ def __init__ (self , parent , response , items_key ):
91114 self ._parent = parent
92- items = response .get (self . ITEMS_KEY , ())
115+ items = response .get (items_key , ())
93116 self ._num_items = len (items )
94117 self ._remaining = self ._num_items
95118 self ._item_iter = iter (items )
119+ self .response = response
96120
97121 @property
98122 def num_items (self ):
@@ -116,23 +140,10 @@ def __iter__(self):
116140 """The :class:`Page` is an iterator."""
117141 return self
118142
119- def _item_to_value (self , item ):
120- """Get the next item in the page.
121-
122- This method (along with the constructor) is the workhorse
123- of this class. Subclasses will need to implement this method.
124-
125- :type item: dict
126- :param item: An item to be converted to a native object.
127-
128- :raises NotImplementedError: Always
129- """
130- raise NotImplementedError
131-
132143 def next (self ):
133144 """Get the next value in the iterator."""
134145 item = six .next (self ._item_iter )
135- result = self ._item_to_value (item )
146+ result = self ._parent . _item_to_value (item )
136147 # Since we've successfully got the next value from the
137148 # iterator, we update the number of remaining.
138149 self ._remaining -= 1
@@ -145,7 +156,8 @@ def next(self):
145156class Iterator (object ):
146157 """A generic class for iterating through Cloud JSON APIs list responses.
147158
148- Sub-classes need to over-write ``PAGE_CLASS``.
159+ Sub-classes need to over-write :attr:`ITEMS_KEY` and to define
160+ :meth:`_item_to_value`.
149161
150162 :type client: :class:`google.cloud.client.Client`
151163 :param client: The client, which owns a connection to make requests.
@@ -166,8 +178,9 @@ class Iterator(object):
166178 PAGE_TOKEN = 'pageToken'
167179 MAX_RESULTS = 'maxResults'
168180 RESERVED_PARAMS = frozenset ([PAGE_TOKEN , MAX_RESULTS ])
169- PAGE_CLASS = Page
170181 PATH = None
182+ ITEMS_KEY = 'items'
183+ """The dictionary key used to retrieve items from each response."""
171184
172185 def __init__ (self , client , page_token = None , max_results = None ,
173186 extra_params = None , path = None ):
@@ -200,31 +213,46 @@ def page(self):
200213 :rtype: :class:`Page`
201214 :returns: The page of items that has been retrieved.
202215 """
216+ self ._update_page ()
203217 return self ._page
204218
205219 def __iter__ (self ):
206220 """The :class:`Iterator` is an iterator."""
207221 return self
208222
209223 def _update_page (self ):
210- """Replace the current page.
224+ """Update the current page if needed .
211225
212- Does nothing if the current page is non-null and has items
213- remaining .
226+ Subclasses will need to implement this method if they
227+ use data from the ``response`` other than the items .
214228
229+ :rtype: bool
230+ :returns: Flag indicated if the page was updated.
215231 :raises: :class:`~exceptions.StopIteration` if there is no next page.
216232 """
217- if self .page is not None and self .page .remaining > 0 :
218- return
219- if self .has_next_page ():
233+ if self ._page is not None and self ._page .remaining > 0 :
234+ return False
235+ elif self .has_next_page ():
220236 response = self ._get_next_page_response ()
221- self ._page = self .PAGE_CLASS (self , response )
237+ self ._page = Page (self , response , self .ITEMS_KEY )
238+ return True
222239 else :
223240 raise StopIteration
224241
242+ def _item_to_value (self , item ):
243+ """Get the next item in the page.
244+
245+ Subclasses will need to implement this method.
246+
247+ :type item: dict
248+ :param item: An item to be converted to a native object.
249+
250+ :raises NotImplementedError: Always
251+ """
252+ raise NotImplementedError
253+
225254 def next (self ):
226255 """Get the next value in the iterator."""
227- self ._update_page ()
228256 item = six .next (self .page )
229257 self .num_results += 1
230258 return item
0 commit comments