Repository: boto/botocore
Pull request #485 (2015-03-10 18:19:26)

Fix build_full_result for clients
Fixes ``build_full_result()`` for clients.

Before this would happen:
```
>>> import botocore.session
>>> session = botocore.session.get_session()
>>> client = session.create_client('s3')
>>> paginator = client.get_paginator('list_objects')
>>> pages = paginator.paginate(Bucket='mybucketfoo')
>>> pages.build_full_result()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "botocore/paginate.py", line 215, in build_full_result
    for _, page in self:
ValueError: too many values to unpack
```

Now this lists all of my objects.

Note that I only included that one unit test for clients to have a smoke test even though operation objects have much more tests. I was considering switching all of the operation object pagination test to clients, but I was hesitant to do that since the CLI still uses operation object pagination. Let me know what you think.

cc @jamesls @danielgtaylor 


Patch:
diff --git a/botocore/paginate.py b/botocore/paginate.py
index 6b9abf28e..19872b9fa 100644
--- a/botocore/paginate.py
+++ b/botocore/paginate.py
@@ -212,7 +212,15 @@ class PageIterator(object):
         for result_expression in self.result_keys:
             set_value_from_jmespath(complete_result,
                                     result_expression.expression, [])
-        for _, page in self:
+        for response in self:
+            page = response
+            # We want to try to catch operation object pagination
+            # and format correctly for those. They come in the form
+            # of a tuple of two elements: (http_response, parsed_responsed).
+            # We want the parsed_response as that is what the page iterator
+            # uses. We can remove it though once operation objects are removed.
+            if isinstance(response, tuple) and len(response) == 2:
+                page = response[1]
             # We're incrementally building the full response page
             # by page.  For each page in the response we need to
             # inject the necessary components from the page


Test patch:
diff --git a/tests/integration/test_client.py b/tests/integration/test_client.py
index 2f94bac01..371414bdc 100644
--- a/tests/integration/test_client.py
+++ b/tests/integration/test_client.py
@@ -155,5 +155,5 @@ class TestClientErrorMessages(unittest.TestCase):
         client = session.create_client(
             'cloudformation', region_name='bad-region-name')
         with self.assertRaisesRegexp(EndpointConnectionError,
-                                     'Could not connect to the endpoint URL'):
+                                     'verify your region'):
             client.list_stacks()
diff --git a/tests/unit/auth/test_signers.py b/tests/unit/auth/test_signers.py
index dba903e60..e2cbc4178 100644
--- a/tests/unit/auth/test_signers.py
+++ b/tests/unit/auth/test_signers.py
@@ -25,18 +25,6 @@ from botocore.awsrequest import AWSRequest
 from botocore.vendored.requests.models import Request
 
 
-class BaseTestWithFixedDate(unittest.TestCase):
-    def setUp(self):
-        self.datetime_patch = mock.patch('botocore.auth.datetime')
-        self.datetime_mock = self.datetime_patch.start()
-        self.fixed_date = datetime.datetime(2014, 3, 10, 17, 2, 55, 0)
-        self.datetime_mock.datetime.utcnow.return_value = self.fixed_date
-        self.datetime_mock.datetime.strptime.return_value = self.fixed_date
-
-    def tearDown(self):
-        self.datetime_patch.stop()
-
-
 class TestHMACV1(unittest.TestCase):
 
     maxDiff = None
@@ -268,12 +256,11 @@ class TestSigV3(unittest.TestCase):
                          [original_auth])
 
 
-class TestS3SigV4Auth(BaseTestWithFixedDate):
+class TestS3SigV4Auth(unittest.TestCase):
 
     maxDiff = None
 
     def setUp(self):
-        super(TestS3SigV4Auth, self).setUp()
         self.credentials = botocore.credentials.Credentials(
             access_key='foo', secret_key='bar', token='baz')
         self.auth = botocore.auth.S3SigV4Auth(
@@ -357,7 +344,7 @@ class TestSigV4(unittest.TestCase):
                 botocore.auth.datetime, 'datetime',
                 mock.Mock(wraps=datetime.datetime)) as mock_datetime:
             original_utcnow = datetime.datetime(2014, 1, 1, 0, 0)
-
+ 
             mock_datetime.utcnow.return_value = original_utcnow
             # Go through the add_auth process once. This will attach
             # a timestamp to the request at the beginning of auth.
@@ -381,12 +368,11 @@ class TestSigV4(unittest.TestCase):
             self.assertNotIn('20140102T000000Z', sts)
 
 
-class TestSigV4Resign(BaseTestWithFixedDate):
+class TestSigV4Resign(unittest.TestCase):
 
     maxDiff = None
 
     def setUp(self):
-        super(TestSigV4Resign, self).setUp()
         self.credentials = botocore.credentials.Credentials(
             access_key='foo', secret_key='bar', token='baz')
         self.auth = botocore.auth.SigV4Auth(self.credentials,
diff --git a/tests/unit/test_paginate.py b/tests/unit/test_paginate.py
index 8731aa922..d9ecfdf03 100644
--- a/tests/unit/test_paginate.py
+++ b/tests/unit/test_paginate.py
@@ -183,6 +183,17 @@ class TestFuturePaginator(unittest.TestCase):
              mock.call(Marker='m2', MaxKeys=1)]
         )
 
+    def test_build_full_result_with_single_key(self):
+        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']})
+
 
 class TestPaginatorObjectConstruction(unittest.TestCase):
     def test_pagination_delegates_to_paginator(self):