We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add compat for legacy operation methods on waiters
While pulling in https://github.com/boto/botocore/pull/437,
I noticed that we aren't handling the 'error' matcher properly
because of a CLI customization that will raise an error on
non 200 responses.  This handles that case so, for example, the
command
`aws rds wait db-instance-deleted --db-instance-identifier foo`
will work as expected.  Previously, the error was being propogated
and the CLI command would exit with a non-zero RC and a client error
message.

cc @kyleknap @danielgtaylor 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `botocore/paginate.py`
Content:
```
1 # Copyright (c) 2013 Amazon.com, Inc. or its affiliates.  All Rights Reserved
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining a
4 # copy of this software and associated documentation files (the
5 # "Software"), to deal in the Software without restriction, including
6 # without limitation the rights to use, copy, modify, merge, publish, dis-
7 # tribute, sublicense, and/or sell copies of the Software, and to permit
8 # persons to whom the Software is furnished to do so, subject to the fol-
9 # lowing conditions:
10 #
11 # The above copyright notice and this permission notice shall be included
12 # in all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 # IN THE SOFTWARE.
21 #
22 from itertools import tee
23 try:
24     from itertools import zip_longest
25 except ImportError:
26     # Python2.x is izip_longest.
27     from itertools import izip_longest as zip_longest
28 
29 try:
30     zip
31 except NameError:
32     # Python2.x is izip.
33     from itertools import izip as zip
34 
35 import jmespath
36 from botocore.exceptions import PaginationError
37 
38 
39 class Paginator(object):
40     def __init__(self, operation):
41         self._operation = operation
42         self._pagination_cfg = operation.pagination
43         self._output_token = self._get_output_tokens(self._pagination_cfg)
44         self._input_token = self._get_input_tokens(self._pagination_cfg)
45         self._more_results = self._get_more_results_token(self._pagination_cfg)
46         self._result_keys = self._get_result_keys(self._pagination_cfg)
47 
48     @property
49     def result_keys(self):
50         return self._result_keys
51 
52     def _get_output_tokens(self, config):
53         output = []
54         output_token = config['output_token']
55         if not isinstance(output_token, list):
56             output_token = [output_token]
57         for config in output_token:
58             output.append(jmespath.compile(config))
59         return output
60 
61     def _get_input_tokens(self, config):
62         input_token = self._pagination_cfg['py_input_token']
63         if not isinstance(input_token, list):
64             input_token = [input_token]
65         return input_token
66 
67     def _get_more_results_token(self, config):
68         more_results = config.get('more_key')
69         if more_results is not None:
70             return jmespath.compile(more_results)
71 
72     def _get_result_keys(self, config):
73         result_key = config.get('result_key')
74         if result_key is not None:
75             if not isinstance(result_key, list):
76                 result_key = [result_key]
77             return result_key
78 
79     def paginate(self, endpoint, **kwargs):
80         """Paginate responses to an operation.
81 
82         The responses to some operations are too large for a single response.
83         When this happens, the service will indicate that there are more
84         results in its response.  This method handles the details of how
85         to detect when this happens and how to retrieve more results.
86 
87         This method returns an iterator.  Each element in the iterator
88         is the result of an ``Operation.call`` call, so each element is
89         a tuple of (``http_response``, ``parsed_result``).
90 
91         """
92         page_params = self._extract_paging_params(kwargs)
93         return PageIterator(self._operation, self._input_token,
94                             self._output_token, self._more_results,
95                             self._result_keys, page_params['max_items'],
96                             page_params['starting_token'],
97                             endpoint, kwargs)
98 
99     def _extract_paging_params(self, kwargs):
100         return {
101             'max_items': kwargs.pop('max_items', None),
102             'starting_token': kwargs.pop('starting_token', None),
103         }
104 
105 
106 class PageIterator(object):
107     def __init__(self, operation, input_token, output_token, more_results,
108                  result_key, max_items, starting_token, endpoint, op_kwargs):
109         self._operation = operation
110         self._input_token = input_token
111         self._output_token = output_token
112         self._more_results = more_results
113         self._result_key = result_key
114         self._max_items = max_items
115         self._starting_token = starting_token
116         self._endpoint = endpoint
117         self._op_kwargs = op_kwargs
118         self._resume_token = None
119 
120     @property
121     def resume_token(self):
122         """Token to specify to resume pagination."""
123         return self._resume_token
124 
125     @resume_token.setter
126     def resume_token(self, value):
127         if isinstance(value, list):
128             self._resume_token = '___'.join([str(v) for v in value])
129 
130     def __iter__(self):
131         current_kwargs = self._op_kwargs
132         endpoint = self._endpoint
133         previous_next_token = None
134         next_token = [None for _ in range(len(self._input_token))]
135         # The number of items from result_key we've seen so far.
136         total_items = 0
137         first_request = True
138         primary_result_key = self._result_key[0]
139         starting_truncation = 0
140         self._inject_starting_token(current_kwargs)
141         while True:
142             http_response, parsed = self._operation.call(endpoint,
143                                                          **current_kwargs)
144             if first_request:
145                 # The first request is handled differently.  We could
146                 # possibly have a resume/starting token that tells us where
147                 # to index into the retrieved page.
148                 if self._starting_token is not None:
149                     starting_truncation = self._handle_first_request(
150                         parsed, primary_result_key, starting_truncation)
151                 first_request = False
152             num_current_response = len(parsed.get(primary_result_key, []))
153             truncate_amount = 0
154             if self._max_items is not None:
155                 truncate_amount = (total_items + num_current_response) \
156                     - self._max_items
157             if truncate_amount > 0:
158                 self._truncate_response(parsed, primary_result_key,
159                                         truncate_amount, starting_truncation,
160                                         next_token)
161                 yield http_response, parsed
162                 break
163             else:
164                 yield http_response, parsed
165                 total_items += num_current_response
166                 next_token = self._get_next_token(parsed)
167                 if all(t is None for t in next_token):
168                     break
169                 if self._max_items is not None and \
170                         total_items == self._max_items:
171                     # We're on a page boundary so we can set the current
172                     # next token to be the resume token.
173                     self.resume_token = next_token
174                     break
175                 if previous_next_token is not None and \
176                         previous_next_token == next_token:
177                     message = ("The same next token was received "
178                                "twice: %s" % next_token)
179                     raise PaginationError(message=message)
180                 self._inject_token_into_kwargs(current_kwargs, next_token)
181                 previous_next_token = next_token
182 
183     def _inject_starting_token(self, op_kwargs):
184         # If the user has specified a starting token we need to
185         # inject that into the operation's kwargs.
186         if self._starting_token is not None:
187             # Don't need to do anything special if there is no starting
188             # token specified.
189             next_token = self._parse_starting_token()[0]
190             self._inject_token_into_kwargs(op_kwargs, next_token)
191 
192     def _inject_token_into_kwargs(self, op_kwargs, next_token):
193         for name, token in zip(self._input_token, next_token):
194             if token is None or token == 'None':
195                 continue
196             op_kwargs[name] = token
197 
198     def _handle_first_request(self, parsed, primary_result_key,
199                               starting_truncation):
200         # First we need to slice into the array and only return
201         # the truncated amount.
202         starting_truncation = self._parse_starting_token()[1]
203         parsed[primary_result_key] = parsed[
204             primary_result_key][starting_truncation:]
205         # We also need to truncate any secondary result keys
206         # because they were not truncated in the previous last
207         # response.
208         for token in self._result_key:
209             if token == primary_result_key:
210                 continue
211             parsed[token] = []
212         return starting_truncation
213 
214     def _truncate_response(self, parsed, primary_result_key, truncate_amount,
215                            starting_truncation, next_token):
216         original = parsed.get(primary_result_key, [])
217         amount_to_keep = len(original) - truncate_amount
218         truncated = original[:amount_to_keep]
219         parsed[primary_result_key] = truncated
220         # The issue here is that even though we know how much we've truncated
221         # we need to account for this globally including any starting
222         # left truncation. For example:
223         # Raw response: [0,1,2,3]
224         # Starting index: 1
225         # Max items: 1
226         # Starting left truncation: [1, 2, 3]
227         # End right truncation for max items: [1]
228         # However, even though we only kept 1, this is post
229         # left truncation so the next starting index should be 2, not 1
230         # (left_truncation + amount_to_keep).
231         next_token.append(str(amount_to_keep + starting_truncation))
232         self.resume_token = next_token
233 
234     def _get_next_token(self, parsed):
235         if self._more_results is not None:
236             if not self._more_results.search(parsed):
237                 return [None]
238         next_tokens = []
239         for token in self._output_token:
240             next_tokens.append(token.search(parsed))
241         return next_tokens
242 
243     def result_key_iters(self):
244         teed_results = tee(self, len(self._result_key))
245         return [ResultKeyIterator(i, result_key) for i, result_key
246                 in zip(teed_results, self._result_key)]
247 
248     def build_full_result(self):
249         iterators = self.result_key_iters()
250         response = {}
251         key_names = [i.result_key for i in iterators]
252         for key in key_names:
253             response[key] = []
254         for vals in zip_longest(*iterators):
255             for k, val in zip(key_names, vals):
256                 if val is not None:
257                     response[k].append(val)
258         if self.resume_token is not None:
259             response['NextToken'] = self.resume_token
260         return response
261 
262     def _parse_starting_token(self):
263         if self._starting_token is None:
264             return None
265         parts = self._starting_token.split('___')
266         next_token = []
267         index = 0
268         if len(parts) == len(self._input_token) + 1:
269             try:
270                 index = int(parts.pop())
271             except ValueError:
272                 raise ValueError("Bad starting token: %s" %
273                                  self._starting_token)
274         for part in parts:
275             if part == 'None':
276                 next_token.append(None)
277             else:
278                 next_token.append(part)
279         return next_token, index
280 
281 
282 class ResultKeyIterator(object):
283     """Iterates over the results of paginated responses."""
284     def __init__(self, pages_iterator, result_key):
285         self._pages_iterator = pages_iterator
286         self.result_key = result_key
287 
288     def __iter__(self):
289         for _, page in self._pages_iterator:
290             for result in page.get(self.result_key, []):
291                 yield result
292
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/botocore/paginate.py b/botocore/paginate.py
--- a/botocore/paginate.py
+++ b/botocore/paginate.py
@@ -105,18 +105,22 @@
 
 class PageIterator(object):
     def __init__(self, operation, input_token, output_token, more_results,
-                 result_key, max_items, starting_token, endpoint, op_kwargs):
+                 result_keys, max_items, starting_token, endpoint, op_kwargs):
         self._operation = operation
         self._input_token = input_token
         self._output_token = output_token
         self._more_results = more_results
-        self._result_key = result_key
+        self._result_keys = result_keys
         self._max_items = max_items
         self._starting_token = starting_token
         self._endpoint = endpoint
         self._op_kwargs = op_kwargs
         self._resume_token = None
 
+    @property
+    def result_keys(self):
+        return self._result_keys
+
     @property
     def resume_token(self):
         """Token to specify to resume pagination."""
@@ -135,7 +139,7 @@
         # The number of items from result_key we've seen so far.
         total_items = 0
         first_request = True
-        primary_result_key = self._result_key[0]
+        primary_result_key = self.result_keys[0]
         starting_truncation = 0
         self._inject_starting_token(current_kwargs)
         while True:
@@ -205,7 +209,7 @@
         # We also need to truncate any secondary result keys
         # because they were not truncated in the previous last
         # response.
-        for token in self._result_key:
+        for token in self.result_keys:
             if token == primary_result_key:
                 continue
             parsed[token] = []
@@ -241,9 +245,9 @@
         return next_tokens
 
     def result_key_iters(self):
-        teed_results = tee(self, len(self._result_key))
+        teed_results = tee(self, len(self.result_keys))
         return [ResultKeyIterator(i, result_key) for i, result_key
-                in zip(teed_results, self._result_key)]
+                in zip(teed_results, self.result_keys)]
 
     def build_full_result(self):
         iterators = self.result_key_iters()

{"golden_diff": "diff --git a/botocore/paginate.py b/botocore/paginate.py\n--- a/botocore/paginate.py\n+++ b/botocore/paginate.py\n@@ -105,18 +105,22 @@\n \n class PageIterator(object):\n     def __init__(self, operation, input_token, output_token, more_results,\n-                 result_key, max_items, starting_token, endpoint, op_kwargs):\n+                 result_keys, max_items, starting_token, endpoint, op_kwargs):\n         self._operation = operation\n         self._input_token = input_token\n         self._output_token = output_token\n         self._more_results = more_results\n-        self._result_key = result_key\n+        self._result_keys = result_keys\n         self._max_items = max_items\n         self._starting_token = starting_token\n         self._endpoint = endpoint\n         self._op_kwargs = op_kwargs\n         self._resume_token = None\n \n+    @property\n+    def result_keys(self):\n+        return self._result_keys\n+\n     @property\n     def resume_token(self):\n         \"\"\"Token to specify to resume pagination.\"\"\"\n@@ -135,7 +139,7 @@\n         # The number of items from result_key we've seen so far.\n         total_items = 0\n         first_request = True\n-        primary_result_key = self._result_key[0]\n+        primary_result_key = self.result_keys[0]\n         starting_truncation = 0\n         self._inject_starting_token(current_kwargs)\n         while True:\n@@ -205,7 +209,7 @@\n         # We also need to truncate any secondary result keys\n         # because they were not truncated in the previous last\n         # response.\n-        for token in self._result_key:\n+        for token in self.result_keys:\n             if token == primary_result_key:\n                 continue\n             parsed[token] = []\n@@ -241,9 +245,9 @@\n         return next_tokens\n \n     def result_key_iters(self):\n-        teed_results = tee(self, len(self._result_key))\n+        teed_results = tee(self, len(self.result_keys))\n         return [ResultKeyIterator(i, result_key) for i, result_key\n-                in zip(teed_results, self._result_key)]\n+                in zip(teed_results, self.result_keys)]\n \n     def build_full_result(self):\n         iterators = self.result_key_iters()\n", "issue": "Add compat for legacy operation methods on waiters\nWhile pulling in https://github.com/boto/botocore/pull/437,\nI noticed that we aren't handling the 'error' matcher properly\nbecause of a CLI customization that will raise an error on\nnon 200 responses.  This handles that case so, for example, the\ncommand\n`aws rds wait db-instance-deleted --db-instance-identifier foo`\nwill work as expected.  Previously, the error was being propogated\nand the CLI command would exit with a non-zero RC and a client error\nmessage.\n\ncc @kyleknap @danielgtaylor \n\n", "before_files": [{"content": "# Copyright (c) 2013 Amazon.com, Inc. or its affiliates.  All Rights Reserved\n#\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish, dis-\n# tribute, sublicense, and/or sell copies of the Software, and to permit\n# persons to whom the Software is furnished to do so, subject to the fol-\n# lowing conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-\n# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\n# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n# IN THE SOFTWARE.\n#\nfrom itertools import tee\ntry:\n    from itertools import zip_longest\nexcept ImportError:\n    # Python2.x is izip_longest.\n    from itertools import izip_longest as zip_longest\n\ntry:\n    zip\nexcept NameError:\n    # Python2.x is izip.\n    from itertools import izip as zip\n\nimport jmespath\nfrom botocore.exceptions import PaginationError\n\n\nclass Paginator(object):\n    def __init__(self, operation):\n        self._operation = operation\n        self._pagination_cfg = operation.pagination\n        self._output_token = self._get_output_tokens(self._pagination_cfg)\n        self._input_token = self._get_input_tokens(self._pagination_cfg)\n        self._more_results = self._get_more_results_token(self._pagination_cfg)\n        self._result_keys = self._get_result_keys(self._pagination_cfg)\n\n    @property\n    def result_keys(self):\n        return self._result_keys\n\n    def _get_output_tokens(self, config):\n        output = []\n        output_token = config['output_token']\n        if not isinstance(output_token, list):\n            output_token = [output_token]\n        for config in output_token:\n            output.append(jmespath.compile(config))\n        return output\n\n    def _get_input_tokens(self, config):\n        input_token = self._pagination_cfg['py_input_token']\n        if not isinstance(input_token, list):\n            input_token = [input_token]\n        return input_token\n\n    def _get_more_results_token(self, config):\n        more_results = config.get('more_key')\n        if more_results is not None:\n            return jmespath.compile(more_results)\n\n    def _get_result_keys(self, config):\n        result_key = config.get('result_key')\n        if result_key is not None:\n            if not isinstance(result_key, list):\n                result_key = [result_key]\n            return result_key\n\n    def paginate(self, endpoint, **kwargs):\n        \"\"\"Paginate responses to an operation.\n\n        The responses to some operations are too large for a single response.\n        When this happens, the service will indicate that there are more\n        results in its response.  This method handles the details of how\n        to detect when this happens and how to retrieve more results.\n\n        This method returns an iterator.  Each element in the iterator\n        is the result of an ``Operation.call`` call, so each element is\n        a tuple of (``http_response``, ``parsed_result``).\n\n        \"\"\"\n        page_params = self._extract_paging_params(kwargs)\n        return PageIterator(self._operation, self._input_token,\n                            self._output_token, self._more_results,\n                            self._result_keys, page_params['max_items'],\n                            page_params['starting_token'],\n                            endpoint, kwargs)\n\n    def _extract_paging_params(self, kwargs):\n        return {\n            'max_items': kwargs.pop('max_items', None),\n            'starting_token': kwargs.pop('starting_token', None),\n        }\n\n\nclass PageIterator(object):\n    def __init__(self, operation, input_token, output_token, more_results,\n                 result_key, max_items, starting_token, endpoint, op_kwargs):\n        self._operation = operation\n        self._input_token = input_token\n        self._output_token = output_token\n        self._more_results = more_results\n        self._result_key = result_key\n        self._max_items = max_items\n        self._starting_token = starting_token\n        self._endpoint = endpoint\n        self._op_kwargs = op_kwargs\n        self._resume_token = None\n\n    @property\n    def resume_token(self):\n        \"\"\"Token to specify to resume pagination.\"\"\"\n        return self._resume_token\n\n    @resume_token.setter\n    def resume_token(self, value):\n        if isinstance(value, list):\n            self._resume_token = '___'.join([str(v) for v in value])\n\n    def __iter__(self):\n        current_kwargs = self._op_kwargs\n        endpoint = self._endpoint\n        previous_next_token = None\n        next_token = [None for _ in range(len(self._input_token))]\n        # The number of items from result_key we've seen so far.\n        total_items = 0\n        first_request = True\n        primary_result_key = self._result_key[0]\n        starting_truncation = 0\n        self._inject_starting_token(current_kwargs)\n        while True:\n            http_response, parsed = self._operation.call(endpoint,\n                                                         **current_kwargs)\n            if first_request:\n                # The first request is handled differently.  We could\n                # possibly have a resume/starting token that tells us where\n                # to index into the retrieved page.\n                if self._starting_token is not None:\n                    starting_truncation = self._handle_first_request(\n                        parsed, primary_result_key, starting_truncation)\n                first_request = False\n            num_current_response = len(parsed.get(primary_result_key, []))\n            truncate_amount = 0\n            if self._max_items is not None:\n                truncate_amount = (total_items + num_current_response) \\\n                    - self._max_items\n            if truncate_amount > 0:\n                self._truncate_response(parsed, primary_result_key,\n                                        truncate_amount, starting_truncation,\n                                        next_token)\n                yield http_response, parsed\n                break\n            else:\n                yield http_response, parsed\n                total_items += num_current_response\n                next_token = self._get_next_token(parsed)\n                if all(t is None for t in next_token):\n                    break\n                if self._max_items is not None and \\\n                        total_items == self._max_items:\n                    # We're on a page boundary so we can set the current\n                    # next token to be the resume token.\n                    self.resume_token = next_token\n                    break\n                if previous_next_token is not None and \\\n                        previous_next_token == next_token:\n                    message = (\"The same next token was received \"\n                               \"twice: %s\" % next_token)\n                    raise PaginationError(message=message)\n                self._inject_token_into_kwargs(current_kwargs, next_token)\n                previous_next_token = next_token\n\n    def _inject_starting_token(self, op_kwargs):\n        # If the user has specified a starting token we need to\n        # inject that into the operation's kwargs.\n        if self._starting_token is not None:\n            # Don't need to do anything special if there is no starting\n            # token specified.\n            next_token = self._parse_starting_token()[0]\n            self._inject_token_into_kwargs(op_kwargs, next_token)\n\n    def _inject_token_into_kwargs(self, op_kwargs, next_token):\n        for name, token in zip(self._input_token, next_token):\n            if token is None or token == 'None':\n                continue\n            op_kwargs[name] = token\n\n    def _handle_first_request(self, parsed, primary_result_key,\n                              starting_truncation):\n        # First we need to slice into the array and only return\n        # the truncated amount.\n        starting_truncation = self._parse_starting_token()[1]\n        parsed[primary_result_key] = parsed[\n            primary_result_key][starting_truncation:]\n        # We also need to truncate any secondary result keys\n        # because they were not truncated in the previous last\n        # response.\n        for token in self._result_key:\n            if token == primary_result_key:\n                continue\n            parsed[token] = []\n        return starting_truncation\n\n    def _truncate_response(self, parsed, primary_result_key, truncate_amount,\n                           starting_truncation, next_token):\n        original = parsed.get(primary_result_key, [])\n        amount_to_keep = len(original) - truncate_amount\n        truncated = original[:amount_to_keep]\n        parsed[primary_result_key] = truncated\n        # The issue here is that even though we know how much we've truncated\n        # we need to account for this globally including any starting\n        # left truncation. For example:\n        # Raw response: [0,1,2,3]\n        # Starting index: 1\n        # Max items: 1\n        # Starting left truncation: [1, 2, 3]\n        # End right truncation for max items: [1]\n        # However, even though we only kept 1, this is post\n        # left truncation so the next starting index should be 2, not 1\n        # (left_truncation + amount_to_keep).\n        next_token.append(str(amount_to_keep + starting_truncation))\n        self.resume_token = next_token\n\n    def _get_next_token(self, parsed):\n        if self._more_results is not None:\n            if not self._more_results.search(parsed):\n                return [None]\n        next_tokens = []\n        for token in self._output_token:\n            next_tokens.append(token.search(parsed))\n        return next_tokens\n\n    def result_key_iters(self):\n        teed_results = tee(self, len(self._result_key))\n        return [ResultKeyIterator(i, result_key) for i, result_key\n                in zip(teed_results, self._result_key)]\n\n    def build_full_result(self):\n        iterators = self.result_key_iters()\n        response = {}\n        key_names = [i.result_key for i in iterators]\n        for key in key_names:\n            response[key] = []\n        for vals in zip_longest(*iterators):\n            for k, val in zip(key_names, vals):\n                if val is not None:\n                    response[k].append(val)\n        if self.resume_token is not None:\n            response['NextToken'] = self.resume_token\n        return response\n\n    def _parse_starting_token(self):\n        if self._starting_token is None:\n            return None\n        parts = self._starting_token.split('___')\n        next_token = []\n        index = 0\n        if len(parts) == len(self._input_token) + 1:\n            try:\n                index = int(parts.pop())\n            except ValueError:\n                raise ValueError(\"Bad starting token: %s\" %\n                                 self._starting_token)\n        for part in parts:\n            if part == 'None':\n                next_token.append(None)\n            else:\n                next_token.append(part)\n        return next_token, index\n\n\nclass ResultKeyIterator(object):\n    \"\"\"Iterates over the results of paginated responses.\"\"\"\n    def __init__(self, pages_iterator, result_key):\n        self._pages_iterator = pages_iterator\n        self.result_key = result_key\n\n    def __iter__(self):\n        for _, page in self._pages_iterator:\n            for result in page.get(self.result_key, []):\n                yield result\n", "path": "botocore/paginate.py"}], "after_files": [{"content": "# Copyright (c) 2013 Amazon.com, Inc. or its affiliates.  All Rights Reserved\n#\n# Permission is hereby granted, free of charge, to any person obtaining a\n# copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish, dis-\n# tribute, sublicense, and/or sell copies of the Software, and to permit\n# persons to whom the Software is furnished to do so, subject to the fol-\n# lowing conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-\n# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\n# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n# IN THE SOFTWARE.\n#\nfrom itertools import tee\ntry:\n    from itertools import zip_longest\nexcept ImportError:\n    # Python2.x is izip_longest.\n    from itertools import izip_longest as zip_longest\n\ntry:\n    zip\nexcept NameError:\n    # Python2.x is izip.\n    from itertools import izip as zip\n\nimport jmespath\nfrom botocore.exceptions import PaginationError\n\n\nclass Paginator(object):\n    def __init__(self, operation):\n        self._operation = operation\n        self._pagination_cfg = operation.pagination\n        self._output_token = self._get_output_tokens(self._pagination_cfg)\n        self._input_token = self._get_input_tokens(self._pagination_cfg)\n        self._more_results = self._get_more_results_token(self._pagination_cfg)\n        self._result_keys = self._get_result_keys(self._pagination_cfg)\n\n    @property\n    def result_keys(self):\n        return self._result_keys\n\n    def _get_output_tokens(self, config):\n        output = []\n        output_token = config['output_token']\n        if not isinstance(output_token, list):\n            output_token = [output_token]\n        for config in output_token:\n            output.append(jmespath.compile(config))\n        return output\n\n    def _get_input_tokens(self, config):\n        input_token = self._pagination_cfg['py_input_token']\n        if not isinstance(input_token, list):\n            input_token = [input_token]\n        return input_token\n\n    def _get_more_results_token(self, config):\n        more_results = config.get('more_key')\n        if more_results is not None:\n            return jmespath.compile(more_results)\n\n    def _get_result_keys(self, config):\n        result_key = config.get('result_key')\n        if result_key is not None:\n            if not isinstance(result_key, list):\n                result_key = [result_key]\n            return result_key\n\n    def paginate(self, endpoint, **kwargs):\n        \"\"\"Paginate responses to an operation.\n\n        The responses to some operations are too large for a single response.\n        When this happens, the service will indicate that there are more\n        results in its response.  This method handles the details of how\n        to detect when this happens and how to retrieve more results.\n\n        This method returns an iterator.  Each element in the iterator\n        is the result of an ``Operation.call`` call, so each element is\n        a tuple of (``http_response``, ``parsed_result``).\n\n        \"\"\"\n        page_params = self._extract_paging_params(kwargs)\n        return PageIterator(self._operation, self._input_token,\n                            self._output_token, self._more_results,\n                            self._result_keys, page_params['max_items'],\n                            page_params['starting_token'],\n                            endpoint, kwargs)\n\n    def _extract_paging_params(self, kwargs):\n        return {\n            'max_items': kwargs.pop('max_items', None),\n            'starting_token': kwargs.pop('starting_token', None),\n        }\n\n\nclass PageIterator(object):\n    def __init__(self, operation, input_token, output_token, more_results,\n                 result_keys, max_items, starting_token, endpoint, op_kwargs):\n        self._operation = operation\n        self._input_token = input_token\n        self._output_token = output_token\n        self._more_results = more_results\n        self._result_keys = result_keys\n        self._max_items = max_items\n        self._starting_token = starting_token\n        self._endpoint = endpoint\n        self._op_kwargs = op_kwargs\n        self._resume_token = None\n\n    @property\n    def result_keys(self):\n        return self._result_keys\n\n    @property\n    def resume_token(self):\n        \"\"\"Token to specify to resume pagination.\"\"\"\n        return self._resume_token\n\n    @resume_token.setter\n    def resume_token(self, value):\n        if isinstance(value, list):\n            self._resume_token = '___'.join([str(v) for v in value])\n\n    def __iter__(self):\n        current_kwargs = self._op_kwargs\n        endpoint = self._endpoint\n        previous_next_token = None\n        next_token = [None for _ in range(len(self._input_token))]\n        # The number of items from result_key we've seen so far.\n        total_items = 0\n        first_request = True\n        primary_result_key = self.result_keys[0]\n        starting_truncation = 0\n        self._inject_starting_token(current_kwargs)\n        while True:\n            http_response, parsed = self._operation.call(endpoint,\n                                                         **current_kwargs)\n            if first_request:\n                # The first request is handled differently.  We could\n                # possibly have a resume/starting token that tells us where\n                # to index into the retrieved page.\n                if self._starting_token is not None:\n                    starting_truncation = self._handle_first_request(\n                        parsed, primary_result_key, starting_truncation)\n                first_request = False\n            num_current_response = len(parsed.get(primary_result_key, []))\n            truncate_amount = 0\n            if self._max_items is not None:\n                truncate_amount = (total_items + num_current_response) \\\n                    - self._max_items\n            if truncate_amount > 0:\n                self._truncate_response(parsed, primary_result_key,\n                                        truncate_amount, starting_truncation,\n                                        next_token)\n                yield http_response, parsed\n                break\n            else:\n                yield http_response, parsed\n                total_items += num_current_response\n                next_token = self._get_next_token(parsed)\n                if all(t is None for t in next_token):\n                    break\n                if self._max_items is not None and \\\n                        total_items == self._max_items:\n                    # We're on a page boundary so we can set the current\n                    # next token to be the resume token.\n                    self.resume_token = next_token\n                    break\n                if previous_next_token is not None and \\\n                        previous_next_token == next_token:\n                    message = (\"The same next token was received \"\n                               \"twice: %s\" % next_token)\n                    raise PaginationError(message=message)\n                self._inject_token_into_kwargs(current_kwargs, next_token)\n                previous_next_token = next_token\n\n    def _inject_starting_token(self, op_kwargs):\n        # If the user has specified a starting token we need to\n        # inject that into the operation's kwargs.\n        if self._starting_token is not None:\n            # Don't need to do anything special if there is no starting\n            # token specified.\n            next_token = self._parse_starting_token()[0]\n            self._inject_token_into_kwargs(op_kwargs, next_token)\n\n    def _inject_token_into_kwargs(self, op_kwargs, next_token):\n        for name, token in zip(self._input_token, next_token):\n            if token is None or token == 'None':\n                continue\n            op_kwargs[name] = token\n\n    def _handle_first_request(self, parsed, primary_result_key,\n                              starting_truncation):\n        # First we need to slice into the array and only return\n        # the truncated amount.\n        starting_truncation = self._parse_starting_token()[1]\n        parsed[primary_result_key] = parsed[\n            primary_result_key][starting_truncation:]\n        # We also need to truncate any secondary result keys\n        # because they were not truncated in the previous last\n        # response.\n        for token in self.result_keys:\n            if token == primary_result_key:\n                continue\n            parsed[token] = []\n        return starting_truncation\n\n    def _truncate_response(self, parsed, primary_result_key, truncate_amount,\n                           starting_truncation, next_token):\n        original = parsed.get(primary_result_key, [])\n        amount_to_keep = len(original) - truncate_amount\n        truncated = original[:amount_to_keep]\n        parsed[primary_result_key] = truncated\n        # The issue here is that even though we know how much we've truncated\n        # we need to account for this globally including any starting\n        # left truncation. For example:\n        # Raw response: [0,1,2,3]\n        # Starting index: 1\n        # Max items: 1\n        # Starting left truncation: [1, 2, 3]\n        # End right truncation for max items: [1]\n        # However, even though we only kept 1, this is post\n        # left truncation so the next starting index should be 2, not 1\n        # (left_truncation + amount_to_keep).\n        next_token.append(str(amount_to_keep + starting_truncation))\n        self.resume_token = next_token\n\n    def _get_next_token(self, parsed):\n        if self._more_results is not None:\n            if not self._more_results.search(parsed):\n                return [None]\n        next_tokens = []\n        for token in self._output_token:\n            next_tokens.append(token.search(parsed))\n        return next_tokens\n\n    def result_key_iters(self):\n        teed_results = tee(self, len(self.result_keys))\n        return [ResultKeyIterator(i, result_key) for i, result_key\n                in zip(teed_results, self.result_keys)]\n\n    def build_full_result(self):\n        iterators = self.result_key_iters()\n        response = {}\n        key_names = [i.result_key for i in iterators]\n        for key in key_names:\n            response[key] = []\n        for vals in zip_longest(*iterators):\n            for k, val in zip(key_names, vals):\n                if val is not None:\n                    response[k].append(val)\n        if self.resume_token is not None:\n            response['NextToken'] = self.resume_token\n        return response\n\n    def _parse_starting_token(self):\n        if self._starting_token is None:\n            return None\n        parts = self._starting_token.split('___')\n        next_token = []\n        index = 0\n        if len(parts) == len(self._input_token) + 1:\n            try:\n                index = int(parts.pop())\n            except ValueError:\n                raise ValueError(\"Bad starting token: %s\" %\n                                 self._starting_token)\n        for part in parts:\n            if part == 'None':\n                next_token.append(None)\n            else:\n                next_token.append(part)\n        return next_token, index\n\n\nclass ResultKeyIterator(object):\n    \"\"\"Iterates over the results of paginated responses.\"\"\"\n    def __init__(self, pages_iterator, result_key):\n        self._pages_iterator = pages_iterator\n        self.result_key = result_key\n\n    def __iter__(self):\n        for _, page in self._pages_iterator:\n            for result in page.get(self.result_key, []):\n                yield result\n", "path": "botocore/paginate.py"}]}