class TestSearchOverResults(unittest.TestCase): def setUp(self): self.method = mock.Mock() self.paginate_config = { 'more_results': 'IsTruncated', 'output_token': 'NextToken', 'input_token': 'NextToken', 'result_key': 'Foo', } self.paginator = Paginator(self.method, self.paginate_config) responses = [ {'Foo': [{'a': 1}, {'b': 2}], 'IsTruncated': True, 'NextToken': '1'}, {'Foo': [{'a': 3}, {'b': 4}], 'IsTruncated': True, 'NextToken': '2'}, {'Foo': [{'a': 5}], 'IsTruncated': False, 'NextToken': '3'} ] self.method.side_effect = responses def test_yields_non_list_values(self): result = list(self.paginator.paginate().search('Foo[0].a')) self.assertEqual([1, 3, 5], result) def test_yields_individual_list_values(self): result = list(self.paginator.paginate().search('Foo[].*[]')) self.assertEqual([1, 2, 3, 4, 5], result) def test_empty_when_no_match(self): result = list(self.paginator.paginate().search('Foo[].qux')) self.assertEqual([], result) def test_no_yield_when_no_match_on_page(self): result = list(self.paginator.paginate().search('Foo[].b')) self.assertEqual([2, 4], result)
class TestIncludeResultKeys(unittest.TestCase): def setUp(self): self.method = mock.Mock() self.paginate_config = { 'output_token': 'Marker', 'input_token': 'Marker', 'result_key': ['ResultKey', 'Count', 'Log'], } self.paginator = Paginator(self.method, self.paginate_config) def test_different_kinds_of_result_key(self): self.method.side_effect = [ {'ResultKey': ['a'], 'Count': 1, 'Log': 'x', 'Marker': 'a'}, {'not_a_result_key': 'this page will be ignored', 'Marker': '_'}, {'ResultKey': ['b', 'c'], 'Count': 2, 'Log': 'y', 'Marker': 'b'}, {'ResultKey': ['d', 'e', 'f'], 'Count': 3, 'Log': 'z'}, ] pages = self.paginator.paginate() expected = { 'ResultKey': ['a', 'b', 'c', 'd', 'e', 'f'], 'Count': 6, 'Log': 'xyz', } self.assertEqual(pages.build_full_result(), expected) def test_result_key_is_missing(self): self.method.side_effect = [ {'not_a_result_key': 'this page will be ignored', 'Marker': '_'}, {'neither_this_one': 'this page will be ignored, too'}, ] pages = self.paginator.paginate() expected = {} self.assertEqual(pages.build_full_result(), expected)
def test_include_with_nested_result_keys(self): self.paginate_config['result_key'] = 'Result.Key' self.paginate_config['non_aggregate_keys'] = [ 'Outer', 'Result.Inner', ] self.paginator = Paginator(self.method, self.paginate_config) self.method.side_effect = [ # The non result keys shows hypothetical # example. This doesn't actually happen, # but in the case where the non result keys # are different across pages, we use the values # from the first page. {'Result': {'Key': ['foo'], 'Inner': 'v1'}, 'Outer': 'v2', 'NextToken': 't1'}, {'Result': {'Key': ['bar', 'baz'], 'Inner': 'v3'}, 'Outer': 'v4', 'NextToken': 't2'}, {'Result': {'Key': ['qux'], 'Inner': 'v5'}, 'Outer': 'v6', 'NextToken': 't3'}, ] pages = self.paginator.paginate() actual = pages.build_full_result() self.assertEqual(pages.non_aggregate_part, {'Outer': 'v2', 'Result': {'Inner': 'v1'}}) expected = { 'Result': {'Key': ['foo', 'bar', 'baz', 'qux'], 'Inner': 'v1'}, 'Outer': 'v2', } self.assertEqual(actual, expected)
def test_next_token_with_or_expression(self): self.pagination_config = { 'output_token': 'NextToken || NextToken2', 'input_token': 'NextToken', 'result_key': 'Foo', } self.paginator = Paginator(self.method, self.pagination_config) # Verify that despite varying between NextToken and NextToken2 # we still can extract the right next tokens. responses = [ {'NextToken': 'token1'}, {'NextToken2': 'token2'}, # The first match found wins, so because NextToken is # listed before NextToken2 in the 'output_tokens' config, # 'token3' is chosen over 'token4'. {'NextToken': 'token3', 'NextToken2': 'token4'}, {'not_next_token': 'foo'}, ] self.method.side_effect = responses list(self.paginator.paginate()) self.assertEqual( self.method.call_args_list, [mock.call(), mock.call(NextToken='token1'), mock.call(NextToken='token2'), mock.call(NextToken='token3')])
def setUp(self): self.method = mock.Mock() self.paginate_config = { 'output_token': 'NextToken', 'input_token': 'NextToken', 'result_key': 'Foo', } self.paginator = Paginator(self.method, self.paginate_config)
def setUp(self): self.method = mock.Mock() self.paginate_config = { 'output_token': 'Marker', 'input_token': 'Marker', 'result_key': ['ResultKey', 'Count', 'Log'], } self.paginator = Paginator(self.method, self.paginate_config)
def setUp(self): self.method = mock.Mock() self.paginate_config = { 'output_token': 'NextToken', 'input_token': 'NextToken', 'result_key': 'ResultKey', 'non_aggregate_keys': ['NotResultKey'], } self.paginator = Paginator(self.method, self.paginate_config)
def setUp(self): self.method = mock.Mock() # This is something we'd see in s3 pagination. self.paginate_config = { "output_token": "Marker", "input_token": "Marker", "result_key": ["Users", "Groups"], } self.paginator = Paginator(self.method, self.paginate_config)
def setUp(self): self.method = mock.Mock() # This is something we'd see in s3 pagination. self.paginate_config = { "output_token": ["ListBucketResults.NextKeyMarker", "ListBucketResults.NextUploadIdMarker"], "input_token": ["key_marker", "upload_id_marker"], "result_key": 'Foo', } self.paginator = Paginator(self.method, self.paginate_config)
def setUp(self): self.method = mock.Mock() # Probably the most complicated example we'll see: # multiple input/output/result keys. self.paginate_config = { "output_token": ["Marker1", "Marker2"], "input_token": ["InMarker1", "InMarker2"], "result_key": ["Users", "Groups"], } self.paginator = Paginator(self.method, self.paginate_config)
def setUp(self): self.method = mock.Mock() # This is something we'd see in s3 pagination. self.paginate_config = { 'output_token': [ 'NextMarker || ListBucketResult.Contents[-1].Key'], 'input_token': 'next_marker', 'result_key': 'Contents', } self.paginator = Paginator(self.method, self.paginate_config)
def setUp(self): self.method = mock.Mock() self.paginate_config = { "output_token": "Marker", "input_token": "Marker", "result_key": ["Users", "Groups"], 'limit_key': 'MaxKeys', } self.paginator = Paginator(self.method, self.paginate_config) self.endpoint = mock.Mock()
def setUp(self): self.method = mock.Mock() self.paginate_config = { "output_token": ["NextRecordName", "NextRecordType", "NextRecordIdentifier"], "input_token": ["StartRecordName", "StartRecordType", "StartRecordIdentifier"], "result_key": 'Foo', } self.paginator = Paginator(self.method, self.paginate_config)
def test_next_token_on_page_boundary(self): paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User1"], "Marker": "m1"}, {"Users": ["User2"], "Marker": "m2"}, {"Users": ["User3"]}, ] self.method.side_effect = responses expected_token = encode_token({"Marker": "m2"}) self.assertEqual( paginator.paginate( PaginationConfig={'MaxItems': 2}).build_full_result(), {'Users': ['User1', 'User2'], 'NextToken': expected_token})
def test_max_items_can_be_specified(self): paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User1"], "Marker": "m1"}, {"Users": ["User2"], "Marker": "m2"}, {"Users": ["User3"]}, ] self.method.side_effect = responses expected_token = encode_token({"Marker": "m1"}) self.assertEqual( paginator.paginate( PaginationConfig={'MaxItems': 1}).build_full_result(), {'Users': ['User1'], 'NextToken': expected_token})
def test_max_items_exceeds_actual_amount(self): # Because MaxItems=10 > number of users (3), we should just return # all of the users. paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User1"], "Marker": "m1"}, {"Users": ["User2"], "Marker": "m2"}, {"Users": ["User3"]}, ] self.method.side_effect = responses self.assertEqual( paginator.paginate( PaginationConfig={'MaxItems': 10}).build_full_result(), {'Users': ['User1', 'User2', 'User3']})
class TestMultipleTokens(unittest.TestCase): def setUp(self): self.method = mock.Mock() # This is something we'd see in s3 pagination. self.paginate_config = { "output_token": ["ListBucketResults.NextKeyMarker", "ListBucketResults.NextUploadIdMarker"], "input_token": ["key_marker", "upload_id_marker"], "result_key": 'Foo', } self.paginator = Paginator(self.method, self.paginate_config) def test_s3_list_multipart_uploads(self): responses = [ {"Foo": [1], "ListBucketResults": {"NextKeyMarker": "key1", "NextUploadIdMarker": "up1"}}, {"Foo": [2], "ListBucketResults": {"NextKeyMarker": "key2", "NextUploadIdMarker": "up2"}}, {"Foo": [3], "ListBucketResults": {"NextKeyMarker": "key3", "NextUploadIdMarker": "up3"}}, {} ] self.method.side_effect = responses list(self.paginator.paginate()) self.assertEqual( self.method.call_args_list, [mock.call(), mock.call(key_marker='key1', upload_id_marker='up1'), mock.call(key_marker='key2', upload_id_marker='up2'), mock.call(key_marker='key3', upload_id_marker='up3'), ])
def setUp(self): self.method = mock.Mock() self.paginate_config = { 'more_results': 'IsTruncated', 'output_token': 'NextToken', 'input_token': 'NextToken', 'result_key': 'Foo', } self.paginator = Paginator(self.method, self.paginate_config) responses = [ {'Foo': [{'a': 1}, {'b': 2}], 'IsTruncated': True, 'NextToken': '1'}, {'Foo': [{'a': 3}, {'b': 4}], 'IsTruncated': True, 'NextToken': '2'}, {'Foo': [{'a': 5}], 'IsTruncated': False, 'NextToken': '3'} ] self.method.side_effect = responses
def setUp(self): self.method = mock.Mock() # This is something like what we'd see in RDS. self.paginate_config = { "input_token": "Marker", "output_token": "Marker", "limit_key": "MaxRecords", "result_key": "EngineDefaults.Parameters" } self.paginator = Paginator(self.method, self.paginate_config) self.responses = [ {"EngineDefaults": {"Parameters": ["One", "Two"]}, "Marker": "m1"}, {"EngineDefaults": {"Parameters": ["Three", "Four"]}, "Marker": "m2"}, {"EngineDefaults": {"Parameters": ["Five"]}} ]
def test_build_full_result_with_single_key(self): self.paginate_config = { "output_token": "Marker", "input_token": "Marker", "result_key": "Users", "limit_key": "MaxKeys", } self.paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User1"], "Marker": "m1"}, {"Users": ["User2"], "Marker": "m2"}, {"Users": ["User3"]} ] self.method.side_effect = responses pages = self.paginator.paginate() complete = pages.build_full_result() self.assertEqual(complete, {'Users': ['User1', 'User2', 'User3']})
def test_next_token_is_string(self): self.paginate_config = { "output_token": "Marker", "input_token": "Marker", "result_key": "Users", "limit_key": "MaxKeys", } self.paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User1"], "Marker": "m1"}, {"Users": ["User2"], "Marker": "m2"}, {"Users": ["User3"]} ] self.method.side_effect = responses result = self.paginator.paginate(PaginationConfig={'MaxItems': 1}) result = result.build_full_result() token = result.get('NextToken') self.assertIsInstance(token, six.string_types)
def test_more_tokens_is_path_expression(self): self.paginate_config = { 'more_results': 'Foo.IsTruncated', 'output_token': 'NextToken', 'input_token': 'NextToken', 'result_key': 'Bar', } self.paginator = Paginator(self.method, self.paginate_config) responses = [ {'Foo': {'IsTruncated': True}, 'NextToken': 'token1'}, {'Foo': {'IsTruncated': False}, 'NextToken': 'token2'}, ] self.method.side_effect = responses list(self.paginator.paginate()) self.assertEqual( self.method.call_args_list, [mock.call(), mock.call(NextToken='token1')])
def test_max_items_can_be_specified_truncates_response(self): # We're saying we only want 4 items, but notice that the second # page of results returns users 4-6 so we have to truncated # part of that second page. paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User1", "User2", "User3"], "Marker": "m1"}, {"Users": ["User4", "User5", "User6"], "Marker": "m2"}, {"Users": ["User7"]}, ] self.method.side_effect = responses expected_token = encode_token( {"Marker": "m1", "boto_truncate_amount": 1}) self.assertEqual( paginator.paginate( PaginationConfig={'MaxItems': 4}).build_full_result(), {'Users': ['User1', 'User2', 'User3', 'User4'], 'NextToken': expected_token})
def test_include_with_multiple_result_keys(self): self.paginate_config['result_key'] = ['ResultKey1', 'ResultKey2'] self.paginator = Paginator(self.method, self.paginate_config) self.method.side_effect = [ {'ResultKey1': ['a', 'b'], 'ResultKey2': ['u', 'v'], 'NotResultKey': 'a', 'NextToken': 'token1'}, {'ResultKey1': ['c', 'd'], 'ResultKey2': ['w', 'x'], 'NotResultKey': 'a', 'NextToken': 'token2'}, {'ResultKey1': ['e', 'f'], 'ResultKey2': ['y', 'z'], 'NotResultKey': 'a'} ] pages = self.paginator.paginate() actual = pages.build_full_result() expected = { 'ResultKey1': ['a', 'b', 'c', 'd', 'e', 'f'], 'ResultKey2': ['u', 'v', 'w', 'x', 'y', 'z'], 'NotResultKey': 'a', } self.assertEqual(actual, expected)
def test_resume_next_marker_mid_page(self): # This is a simulation of picking up from the response # from test_MaxItems_can_be_specified_truncates_response # We got the first 4 users, when we pick up we should get # User5 - User7. paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User4", "User5", "User6"], "Marker": "m2"}, {"Users": ["User7"]}, ] self.method.side_effect = responses starting_token = encode_token( {"Marker": "m1", "boto_truncate_amount": 1}) pagination_config = {'StartingToken': starting_token} self.assertEqual( paginator.paginate( PaginationConfig=pagination_config).build_full_result(), {'Users': ['User5', 'User6', 'User7']}) self.assertEqual( self.method.call_args_list, [mock.call(Marker='m1'), mock.call(Marker='m2')])
class TestPaginatorWithPathExpressions(unittest.TestCase): def setUp(self): self.method = mock.Mock() # This is something we'd see in s3 pagination. self.paginate_config = { 'output_token': [ 'NextMarker || ListBucketResult.Contents[-1].Key'], 'input_token': 'next_marker', 'result_key': 'Contents', } self.paginator = Paginator(self.method, self.paginate_config) def test_s3_list_objects(self): responses = [ {'NextMarker': 'token1'}, {'NextMarker': 'token2'}, {'not_next_token': 'foo'}] self.method.side_effect = responses list(self.paginator.paginate()) self.assertEqual( self.method.call_args_list, [mock.call(), mock.call(next_marker='token1'), mock.call(next_marker='token2')]) def test_s3_list_object_complex(self): responses = [ {'NextMarker': 'token1'}, {'ListBucketResult': { 'Contents': [{"Key": "first"}, {"Key": "Last"}]}}, {'not_next_token': 'foo'}] self.method.side_effect = responses list(self.paginator.paginate()) self.assertEqual( self.method.call_args_list, [mock.call(), mock.call(next_marker='token1'), mock.call(next_marker='Last')])
def create_paginator(self, multiple_tokens=False): if multiple_tokens: paginator_config = { "output_token": ["Marker1", "Marker2"], "input_token": ["InMarker1", "InMarker2"], "result_key": ["Users", "Groups"], } else: paginator_config = { 'output_token': 'Marker', 'input_token': 'Marker', 'result_key': 'Users', } return Paginator(self.method, paginator_config)
def test_page_size(self): self.paginate_config = { "output_token": "Marker", "input_token": "Marker", "result_key": "Users", "limit_key": "MaxKeys", } self.paginator = Paginator(self.method, self.paginate_config) responses = [ {"Users": ["User1"], "Marker": "m1"}, {"Users": ["User2"], "Marker": "m2"}, {"Users": ["User3"]}, ] self.method.side_effect = responses users = [] for page in self.paginator.paginate(PaginationConfig={'PageSize': 1}): users += page['Users'] self.assertEqual( self.method.call_args_list, [mock.call(MaxKeys=1), mock.call(Marker='m1', MaxKeys=1), mock.call(Marker='m2', MaxKeys=1)] )
def test_more_tokens(self): # Some pagination configs have a 'more_token' key that # indicate whether or not the results are being paginated. self.paginate_config = { 'more_results': 'IsTruncated', 'output_token': 'NextToken', 'input_token': 'NextToken', 'result_key': 'Foo', } self.paginator = Paginator(self.method, self.paginate_config) responses = [ {'Foo': [1], 'IsTruncated': True, 'NextToken': 'token1'}, {'Foo': [2], 'IsTruncated': True, 'NextToken': 'token2'}, {'Foo': [3], 'IsTruncated': False, 'NextToken': 'token3'}, {'Foo': [4], 'not_next_token': 'foo'}, ] self.method.side_effect = responses list(self.paginator.paginate()) self.assertEqual( self.method.call_args_list, [mock.call(), mock.call(NextToken='token1'), mock.call(NextToken='token2')])
class TestPaginatorPageSize(unittest.TestCase): def setUp(self): self.method = mock.Mock() self.paginate_config = { "output_token": "Marker", "input_token": "Marker", "result_key": ["Users", "Groups"], 'limit_key': 'MaxKeys', } self.paginator = Paginator(self.method, self.paginate_config) self.endpoint = mock.Mock() def test_no_page_size(self): kwargs = {'arg1': 'foo', 'arg2': 'bar'} ref_kwargs = {'arg1': 'foo', 'arg2': 'bar'} pages = self.paginator.paginate(**kwargs) pages._inject_starting_params(kwargs) self.assertEqual(kwargs, ref_kwargs) def test_page_size(self): kwargs = {'arg1': 'foo', 'arg2': 'bar', 'PaginationConfig': {'PageSize': 5}} extracted_kwargs = {'arg1': 'foo', 'arg2': 'bar'} # Note that ``MaxKeys`` in ``setUp()`` is the parameter used for # the page size for pagination. ref_kwargs = {'arg1': 'foo', 'arg2': 'bar', 'MaxKeys': 5} pages = self.paginator.paginate(**kwargs) pages._inject_starting_params(extracted_kwargs) self.assertEqual(extracted_kwargs, ref_kwargs) def test_page_size_incorrectly_provided(self): kwargs = {'arg1': 'foo', 'arg2': 'bar', 'PaginationConfig': {'PageSize': 5}} del self.paginate_config['limit_key'] with self.assertRaises(PaginationError): self.paginator.paginate(**kwargs)