Skip to content

Commit 010951c

Browse files
anandswaminathandanielhochman
authored andcommitted
introduce concept of page_size, separate from num items returned limit (#139)
1 parent c6a67f4 commit 010951c

File tree

2 files changed

+88
-14
lines changed

2 files changed

+88
-14
lines changed

pynamodb/models.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ def query(cls,
510510
limit=None,
511511
last_evaluated_key=None,
512512
attributes_to_get=None,
513+
page_size=None,
513514
**filters):
514515
"""
515516
Provides a high level query API
@@ -522,6 +523,7 @@ def query(cls,
522523
Controls descending or ascending results
523524
:param last_evaluated_key: If set, provides the starting point for query.
524525
:param attributes_to_get: If set, only returns these elements
526+
:param page_size: Page size of the query to DynamoDB
525527
:param filters: A dictionary of filters to be used in the query
526528
"""
527529
cls._get_indexes()
@@ -538,6 +540,10 @@ def query(cls,
538540
key_attribute_classes[name] = attr
539541
else:
540542
non_key_attribute_classes[name] = attr
543+
544+
if page_size is None:
545+
page_size = limit
546+
541547
key_conditions, query_filters = cls._build_filters(
542548
QUERY_OPERATOR_MAP,
543549
non_key_operator_map=QUERY_FILTER_OPERATOR_MAP,
@@ -551,7 +557,7 @@ def query(cls,
551557
exclusive_start_key=last_evaluated_key,
552558
consistent_read=consistent_read,
553559
scan_index_forward=scan_index_forward,
554-
limit=limit,
560+
limit=page_size,
555561
key_conditions=key_conditions,
556562
attributes_to_get=attributes_to_get,
557563
query_filters=query_filters,
@@ -571,14 +577,7 @@ def query(cls,
571577
yield cls.from_raw_data(item)
572578

573579
while last_evaluated_key:
574-
log.debug("Fetching query page with exclusive start key: %s", last_evaluated_key)
575-
# If the user provided a limit, we need to subtract the number of results returned for each page
576-
if limit is not None:
577-
if limit == 0:
578-
return
579-
limit -= data.get(CAMEL_COUNT, 0)
580580
query_kwargs['exclusive_start_key'] = last_evaluated_key
581-
query_kwargs['limit'] = limit
582581
log.debug("Fetching query page with exclusive start key: %s", last_evaluated_key)
583582
data = cls._get_connection().query(hash_key, **query_kwargs)
584583
cls._throttle.add_record(data.get(CONSUMED_CAPACITY))
@@ -597,6 +596,7 @@ def scan(cls,
597596
limit=None,
598597
conditional_operator=None,
599598
last_evaluated_key=None,
599+
page_size=None,
600600
**filters):
601601
"""
602602
Iterates through all items in the table
@@ -605,6 +605,7 @@ def scan(cls,
605605
:param total_segments: If set, then specifies total segments
606606
:param limit: Used to limit the number of results returned
607607
:param last_evaluated_key: If set, provides the starting point for scan.
608+
:param page_size: Page size of the scan to DynamoDB
608609
:param filters: A list of item filters
609610
"""
610611
key_filter, scan_filter = cls._build_filters(
@@ -614,10 +615,13 @@ def scan(cls,
614615
filters=filters
615616
)
616617
key_filter.update(scan_filter)
618+
if page_size is None:
619+
page_size = limit
620+
617621
data = cls._get_connection().scan(
618622
exclusive_start_key=last_evaluated_key,
619623
segment=segment,
620-
limit=limit,
624+
limit=page_size,
621625
scan_filter=key_filter,
622626
total_segments=total_segments,
623627
conditional_operator=conditional_operator
@@ -635,7 +639,7 @@ def scan(cls,
635639
log.debug("Fetching scan page with exclusive start key: %s", last_evaluated_key)
636640
data = cls._get_connection().scan(
637641
exclusive_start_key=last_evaluated_key,
638-
limit=limit,
642+
limit=page_size,
639643
scan_filter=key_filter,
640644
segment=segment,
641645
total_segments=total_segments

pynamodb/tests/test_model.py

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,8 +1174,32 @@ def test_query_limit_less_than_available_items_multiple_page(self):
11741174
self.assertEqual(len(results), 25)
11751175
self.assertEqual(len(req.mock_calls), 3)
11761176
self.assertEquals(req.mock_calls[0][1][1]['Limit'], 25)
1177-
self.assertEquals(req.mock_calls[1][1][1]['Limit'], 15)
1178-
self.assertEquals(req.mock_calls[2][1][1]['Limit'], 5)
1177+
self.assertEquals(req.mock_calls[1][1][1]['Limit'], 25)
1178+
self.assertEquals(req.mock_calls[2][1][1]['Limit'], 25)
1179+
1180+
def test_query_limit_less_than_available_and_page_size(self):
1181+
with patch(PATCH_METHOD) as req:
1182+
req.return_value = MODEL_TABLE_DATA
1183+
UserModel('foo', 'bar')
1184+
1185+
with patch(PATCH_METHOD) as req:
1186+
items = []
1187+
for idx in range(30):
1188+
item = copy.copy(GET_MODEL_ITEM_DATA.get(ITEM))
1189+
item['user_id'] = {STRING_SHORT: 'id-{0}'.format(idx)}
1190+
items.append(item)
1191+
1192+
req.side_effect = [
1193+
{'Items': items[:10], 'LastEvaluatedKey': 'x'},
1194+
{'Items': items[10:20], 'LastEvaluatedKey': 'y'},
1195+
{'Items': items[20:30]},
1196+
]
1197+
results = list(UserModel.query('foo', limit=25, page_size=10))
1198+
self.assertEqual(len(results), 25)
1199+
self.assertEqual(len(req.mock_calls), 3)
1200+
self.assertEquals(req.mock_calls[0][1][1]['Limit'], 10)
1201+
self.assertEquals(req.mock_calls[1][1][1]['Limit'], 10)
1202+
self.assertEquals(req.mock_calls[2][1][1]['Limit'], 10)
11791203

11801204
def test_query_limit_greater_than_available_items_multiple_page(self):
11811205
with patch(PATCH_METHOD) as req:
@@ -1198,8 +1222,32 @@ def test_query_limit_greater_than_available_items_multiple_page(self):
11981222
self.assertEqual(len(results), 30)
11991223
self.assertEqual(len(req.mock_calls), 3)
12001224
self.assertEquals(req.mock_calls[0][1][1]['Limit'], 50)
1201-
self.assertEquals(req.mock_calls[1][1][1]['Limit'], 40)
1202-
self.assertEquals(req.mock_calls[2][1][1]['Limit'], 30)
1225+
self.assertEquals(req.mock_calls[1][1][1]['Limit'], 50)
1226+
self.assertEquals(req.mock_calls[2][1][1]['Limit'], 50)
1227+
1228+
def test_query_limit_greater_than_available_items_and_page_size(self):
1229+
with patch(PATCH_METHOD) as req:
1230+
req.return_value = MODEL_TABLE_DATA
1231+
UserModel('foo', 'bar')
1232+
1233+
with patch(PATCH_METHOD) as req:
1234+
items = []
1235+
for idx in range(30):
1236+
item = copy.copy(GET_MODEL_ITEM_DATA.get(ITEM))
1237+
item['user_id'] = {STRING_SHORT: 'id-{0}'.format(idx)}
1238+
items.append(item)
1239+
1240+
req.side_effect = [
1241+
{'Items': items[:10], 'LastEvaluatedKey': 'x'},
1242+
{'Items': items[10:20], 'LastEvaluatedKey': 'y'},
1243+
{'Items': items[20:30]},
1244+
]
1245+
results = list(UserModel.query('foo', limit=50, page_size=10))
1246+
self.assertEqual(len(results), 30)
1247+
self.assertEqual(len(req.mock_calls), 3)
1248+
self.assertEquals(req.mock_calls[0][1][1]['Limit'], 10)
1249+
self.assertEquals(req.mock_calls[1][1][1]['Limit'], 10)
1250+
self.assertEquals(req.mock_calls[2][1][1]['Limit'], 10)
12031251

12041252
def test_query(self):
12051253
"""
@@ -1390,6 +1438,26 @@ def fake_query(*args):
13901438
self.assertEqual(params, req.call_args[0][1])
13911439
self.assertTrue(len(queried) == len(items))
13921440

1441+
def test_scan_limit_with_page_size(self):
1442+
with patch(PATCH_METHOD) as req:
1443+
items = []
1444+
for idx in range(30):
1445+
item = copy.copy(GET_MODEL_ITEM_DATA.get(ITEM))
1446+
item['user_id'] = {STRING_SHORT: 'id-{0}'.format(idx)}
1447+
items.append(item)
1448+
1449+
req.side_effect = [
1450+
{'Items': items[:10], 'LastEvaluatedKey': 'x'},
1451+
{'Items': items[10:20], 'LastEvaluatedKey': 'y'},
1452+
{'Items': items[20:30]},
1453+
]
1454+
results = list(UserModel.scan(limit=25, page_size=10))
1455+
self.assertEqual(len(results), 25)
1456+
self.assertEqual(len(req.mock_calls), 3)
1457+
self.assertEquals(req.mock_calls[0][1][1]['Limit'], 10)
1458+
self.assertEquals(req.mock_calls[1][1][1]['Limit'], 10)
1459+
self.assertEquals(req.mock_calls[2][1][1]['Limit'], 10)
1460+
13931461
def test_scan_limit(self):
13941462
"""
13951463
Model.scan(limit)
@@ -1411,6 +1479,8 @@ def fake_scan(*args):
14111479
for item in UserModel.scan(limit=4):
14121480
count += 1
14131481
self.assertIsNotNone(item)
1482+
self.assertEqual(len(req.mock_calls), 1)
1483+
self.assertEquals(req.mock_calls[0][1][1]['Limit'], 4)
14141484
self.assertEqual(count, 4)
14151485

14161486
with patch(PATCH_METHOD) as req:

0 commit comments

Comments
 (0)