Skip to content

Commit a421913

Browse files
author
Jim Fulton
authored
feat: HTTPIterator now accepts a page_size parameter to control page … (#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy