Repository: boto/botocore

Issue:
Fix output in multi result pagination (build_full_result)
Because we use izip_longest you can get a response like this:

```
{"CommonPrefixes": [null, null, null, null],
 "Content": [{...}, {...}, {...}, {...}
}
```

When really if the null we shouldn't add it to the list.  Then our response _should_ look like:

```
{"CommonPrefixes": [],
 "Content":  [{...}, {...}, {...}, {...}
}
```



Discussion:

Resolving patch:
diff --git a/botocore/paginate.py b/botocore/paginate.py
--- a/botocore/paginate.py
+++ b/botocore/paginate.py
@@ -20,7 +20,6 @@
 # IN THE SOFTWARE.
 #
 from itertools import tee
-from collections import defaultdict
 try:
     from itertools import zip_longest
 except ImportError:
@@ -146,11 +145,14 @@ def result_key_iters(self):
     def build_full_result(self):
         iterators = self.result_key_iters()
         if len(iterators) > 1:
-           response = defaultdict(list)
+           response = {}
            key_names = [i.result_key for i in iterators]
+           for key in key_names:
+               response[key] = []
            for vals in zip_longest(*iterators):
                for k, val in zip(key_names, vals):
-                   response[k].append(val)
+                   if val is not None:
+                       response[k].append(val)
         else:
             response = list(iterators[0])
         return response


Test patch:
diff --git a/tests/unit/test_paginate.py b/tests/unit/test_paginate.py
--- a/tests/unit/test_paginate.py
+++ b/tests/unit/test_paginate.py
@@ -284,7 +284,9 @@ def test_build_full_result_with_single_key(self):
         complete = pages.build_full_result()
         self.assertEqual(complete, ['User1', 'User2', 'User3'])
 
-    def test_build_full_result_with_multiple_result_keys(self):
+
+class TestMultipleResultKeys(unittest.TestCase):
+    def setUp(self):
         self.operation = mock.Mock()
         # This is something we'd see in s3 pagination.
         self.paginate_config = {
@@ -295,6 +297,7 @@ def test_build_full_result_with_multiple_result_keys(self):
         self.operation.pagination = self.paginate_config
         self.paginator = Paginator(self.operation)
 
+    def test_build_full_result_with_multiple_result_keys(self):
         responses = [
             (None, {"Users": ["User1"], "Groups": ["Group1"], "Marker": "m1"}),
             (None, {"Users": ["User2"], "Groups": ["Group2"], "Marker": "m2"}),
@@ -307,6 +310,37 @@ def test_build_full_result_with_multiple_result_keys(self):
                          {"Users": ['User1', 'User2', 'User3'],
                           "Groups": ['Group1', 'Group2', 'Group3']})
 
+    def test_build_full_result_with_different_length_result_keys(self):
+        responses = [
+            (None, {"Users": ["User1"], "Groups": ["Group1"], "Marker": "m1"}),
+            # Then we stop getting "Users" output, but we get more "Groups"
+            (None, {"Users": [], "Groups": ["Group2"], "Marker": "m2"}),
+            (None, {"Users": [], "Groups": ["Group3"], }),
+        ]
+        self.operation.call.side_effect = responses
+        pages = self.paginator.paginate(None)
+        complete = pages.build_full_result()
+        self.assertEqual(complete,
+                         {"Users": ['User1'],
+                          "Groups": ['Group1', 'Group2', 'Group3']})
+
+
+    def test_build_full_result_with_zero_length_result_key(self):
+        responses = [
+            # In this case the 'Users' key is always empty but we should
+            # have a 'Users' key in the output, it should just have an
+            # empty list for a value.
+            (None, {"Users": [], "Groups": ["Group1"], "Marker": "m1"}),
+            (None, {"Users": [], "Groups": ["Group2"], "Marker": "m2"}),
+            (None, {"Users": [], "Groups": ["Group3"], }),
+        ]
+        self.operation.call.side_effect = responses
+        pages = self.paginator.paginate(None)
+        complete = pages.build_full_result()
+        self.assertEqual(complete,
+                         {"Users": [],
+                          "Groups": ['Group1', 'Group2', 'Group3']})
+
 
 if __name__ == '__main__':
     unittest.main()