Skip to content

Commit a421913

Browse files
author
Jim Fulton
authored
feat: HTTPIterator now accepts a page_size parameter to control page … (googleapis#197)
1 parent 7337c6b commit a421913

File tree

2 files changed

+78
-3
lines changed

2 files changed

+78
-3
lines changed

google/api_core/page_iterator.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def __init__(
179179
single item.
180180
"""
181181
self.max_results = max_results
182-
"""int: The maximum number of results to fetch."""
182+
"""int: The maximum number of results to fetch"""
183183

184184
# The attributes below will change over the life of the iterator.
185185
self.page_number = 0
@@ -298,7 +298,8 @@ class HTTPIterator(Iterator):
298298
can be found.
299299
page_token (str): A token identifying a page in a result set to start
300300
fetching results from.
301-
max_results (int): The maximum number of results to fetch.
301+
page_size (int): The maximum number of results to fetch per page
302+
max_results (int): The maximum number of results to fetch
302303
extra_params (dict): Extra query string parameters for the
303304
API call.
304305
page_start (Callable[
@@ -329,6 +330,7 @@ def __init__(
329330
item_to_value,
330331
items_key=_DEFAULT_ITEMS_KEY,
331332
page_token=None,
333+
page_size=None,
332334
max_results=None,
333335
extra_params=None,
334336
page_start=_do_nothing_page_start,
@@ -341,6 +343,7 @@ def __init__(
341343
self.path = path
342344
self._items_key = items_key
343345
self.extra_params = extra_params
346+
self._page_size = page_size
344347
self._page_start = page_start
345348
self._next_token = next_token
346349
# Verify inputs / provide defaults.
@@ -399,8 +402,18 @@ def _get_query_params(self):
399402
result = {}
400403
if self.next_page_token is not None:
401404
result[self._PAGE_TOKEN] = self.next_page_token
405+
406+
page_size = None
402407
if self.max_results is not None:
403-
result[self._MAX_RESULTS] = self.max_results - self.num_results
408+
page_size = self.max_results - self.num_results
409+
if self._page_size is not None:
410+
page_size = min(page_size, self._page_size)
411+
elif self._page_size is not None:
412+
page_size = self._page_size
413+
414+
if page_size is not None:
415+
result[self._MAX_RESULTS] = page_size
416+
404417
result.update(self.extra_params)
405418
return result
406419

tests/unit/test_page_iterator.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import math
1516
import types
1617

1718
import mock
@@ -235,6 +236,7 @@ def test_constructor(self):
235236
assert iterator.page_number == 0
236237
assert iterator.next_page_token is None
237238
assert iterator.num_results == 0
239+
assert iterator._page_size is None
238240

239241
def test_constructor_w_extra_param_collision(self):
240242
extra_params = {"pageToken": "val"}
@@ -432,6 +434,66 @@ def test__get_next_page_bad_http_method(self):
432434
with pytest.raises(ValueError):
433435
iterator._get_next_page_response()
434436

437+
@pytest.mark.parametrize(
438+
"page_size,max_results,pages",
439+
[(3, None, False), (3, 8, False), (3, None, True), (3, 8, True)])
440+
def test_page_size_items(self, page_size, max_results, pages):
441+
path = "/foo"
442+
NITEMS = 10
443+
444+
n = [0] # blast you python 2!
445+
446+
def api_request(*args, **kw):
447+
assert not args
448+
query_params = dict(
449+
maxResults=(
450+
page_size if max_results is None
451+
else min(page_size, max_results - n[0]))
452+
)
453+
if n[0]:
454+
query_params.update(pageToken='test')
455+
assert kw == {'method': 'GET', 'path': '/foo',
456+
'query_params': query_params}
457+
n_items = min(kw['query_params']['maxResults'], NITEMS - n[0])
458+
items = [dict(name=str(i + n[0])) for i in range(n_items)]
459+
n[0] += n_items
460+
result = dict(items=items)
461+
if n[0] < NITEMS:
462+
result.update(nextPageToken='test')
463+
return result
464+
465+
iterator = page_iterator.HTTPIterator(
466+
mock.sentinel.client,
467+
api_request,
468+
path=path,
469+
item_to_value=page_iterator._item_to_value_identity,
470+
page_size=page_size,
471+
max_results=max_results,
472+
)
473+
474+
assert iterator.num_results == 0
475+
476+
n_results = max_results if max_results is not None else NITEMS
477+
if pages:
478+
items_iter = iter(iterator.pages)
479+
npages = int(math.ceil(float(n_results) / page_size))
480+
for ipage in range(npages):
481+
assert (
482+
list(six.next(items_iter)) == [
483+
dict(name=str(i))
484+
for i in range(ipage * page_size,
485+
min((ipage + 1) * page_size, n_results),
486+
)
487+
])
488+
else:
489+
items_iter = iter(iterator)
490+
for i in range(n_results):
491+
assert six.next(items_iter) == dict(name=str(i))
492+
assert iterator.num_results == i + 1
493+
494+
with pytest.raises(StopIteration):
495+
six.next(items_iter)
496+
435497

436498
class TestGRPCIterator(object):
437499
def test_constructor(self):

0 commit comments

Comments
 (0)