Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/awscli/customizations/paginate.py
1566 views
1
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
"""This module has customizations to unify paging parameters.
14
15
For any operation that can be paginated, we will:
16
17
* Hide the service specific pagination params. This can vary across
18
services and we're going to replace them with a consistent set of
19
arguments. The arguments will still work, but they are not
20
documented. This allows us to add a pagination config after
21
the fact and still remain backwards compatible with users that
22
were manually doing pagination.
23
* Add a ``--starting-token`` and a ``--max-items`` argument.
24
25
"""
26
import logging
27
import sys
28
from functools import partial
29
30
from awscli.customizations.utils import uni_print
31
from botocore import xform_name
32
from botocore.exceptions import DataNotFoundError, PaginationError
33
from botocore import model
34
35
from awscli.arguments import BaseCLIArgument
36
37
38
logger = logging.getLogger(__name__)
39
40
41
STARTING_TOKEN_HELP = """
42
<p>A token to specify where to start paginating. This is the
43
<code>NextToken</code> from a previously truncated response.</p>
44
<p>For usage examples, see <a
45
href="https://docs.aws.amazon.com/cli/latest/userguide/pagination.html"
46
>Pagination</a> in the <i>AWS Command Line Interface User
47
Guide</i>.</p>
48
"""
49
50
MAX_ITEMS_HELP = """
51
<p>The total number of items to return in the command's output.
52
If the total number of items available is more than the value
53
specified, a <code>NextToken</code> is provided in the command's
54
output. To resume pagination, provide the
55
<code>NextToken</code> value in the <code>starting-token</code>
56
argument of a subsequent command. <b>Do not</b> use the
57
<code>NextToken</code> response element directly outside of the
58
AWS CLI.</p>
59
<p>For usage examples, see <a
60
href="https://docs.aws.amazon.com/cli/latest/userguide/pagination.html"
61
>Pagination</a> in the <i>AWS Command Line Interface User
62
Guide</i>.</p>
63
"""
64
65
PAGE_SIZE_HELP = """
66
<p>The size of each page to get in the AWS service call. This
67
does not affect the number of items returned in the command's
68
output. Setting a smaller page size results in more calls to
69
the AWS service, retrieving fewer items in each call. This can
70
help prevent the AWS service calls from timing out.</p>
71
<p>For usage examples, see <a
72
href="https://docs.aws.amazon.com/cli/latest/userguide/pagination.html"
73
>Pagination</a> in the <i>AWS Command Line Interface User
74
Guide</i>.</p>
75
"""
76
77
78
def register_pagination(event_handlers):
79
event_handlers.register('building-argument-table', unify_paging_params)
80
event_handlers.register_last('doc-description', add_paging_description)
81
82
83
def get_paginator_config(session, service_name, operation_name):
84
try:
85
paginator_model = session.get_paginator_model(service_name)
86
except DataNotFoundError:
87
return None
88
try:
89
operation_paginator_config = paginator_model.get_paginator(
90
operation_name)
91
except ValueError:
92
return None
93
return operation_paginator_config
94
95
96
def add_paging_description(help_command, **kwargs):
97
# This customization is only applied to the description of
98
# Operations, so we must filter out all other events.
99
if not isinstance(help_command.obj, model.OperationModel):
100
return
101
service_name = help_command.obj.service_model.service_name
102
paginator_config = get_paginator_config(
103
help_command.session, service_name, help_command.obj.name)
104
if not paginator_config:
105
return
106
help_command.doc.style.new_paragraph()
107
help_command.doc.writeln(
108
('``%s`` is a paginated operation. Multiple API calls may be issued '
109
'in order to retrieve the entire data set of results. You can '
110
'disable pagination by providing the ``--no-paginate`` argument.')
111
% help_command.name)
112
# Only include result key information if it is present.
113
if paginator_config.get('result_key'):
114
queries = paginator_config['result_key']
115
if type(queries) is not list:
116
queries = [queries]
117
queries = ", ".join([('``%s``' % s) for s in queries])
118
help_command.doc.writeln(
119
('When using ``--output text`` and the ``--query`` argument on a '
120
'paginated response, the ``--query`` argument must extract data '
121
'from the results of the following query expressions: %s')
122
% queries)
123
124
125
def unify_paging_params(argument_table, operation_model, event_name,
126
session, **kwargs):
127
paginator_config = get_paginator_config(
128
session, operation_model.service_model.service_name,
129
operation_model.name)
130
if paginator_config is None:
131
# We only apply these customizations to paginated responses.
132
return
133
logger.debug("Modifying paging parameters for operation: %s",
134
operation_model.name)
135
_remove_existing_paging_arguments(argument_table, paginator_config)
136
parsed_args_event = event_name.replace('building-argument-table.',
137
'operation-args-parsed.')
138
shadowed_args = {}
139
add_paging_argument(argument_table, 'starting-token',
140
PageArgument('starting-token', STARTING_TOKEN_HELP,
141
parse_type='string',
142
serialized_name='StartingToken'),
143
shadowed_args)
144
input_members = operation_model.input_shape.members
145
type_name = 'integer'
146
if 'limit_key' in paginator_config:
147
limit_key_shape = input_members[paginator_config['limit_key']]
148
type_name = limit_key_shape.type_name
149
if type_name not in PageArgument.type_map:
150
raise TypeError(
151
('Unsupported pagination type {0} for operation {1}'
152
' and parameter {2}').format(
153
type_name, operation_model.name,
154
paginator_config['limit_key']))
155
add_paging_argument(argument_table, 'page-size',
156
PageArgument('page-size', PAGE_SIZE_HELP,
157
parse_type=type_name,
158
serialized_name='PageSize'),
159
shadowed_args)
160
161
add_paging_argument(argument_table, 'max-items',
162
PageArgument('max-items', MAX_ITEMS_HELP,
163
parse_type=type_name,
164
serialized_name='MaxItems'),
165
shadowed_args)
166
session.register(
167
parsed_args_event,
168
partial(check_should_enable_pagination,
169
list(_get_all_cli_input_tokens(paginator_config)),
170
shadowed_args, argument_table))
171
172
173
def add_paging_argument(argument_table, arg_name, argument, shadowed_args):
174
if arg_name in argument_table:
175
# If there's already an entry in the arg table for this argument,
176
# this means we're shadowing an argument for this operation. We
177
# need to store this later in case pagination is turned off because
178
# we put these arguments back.
179
# See the comment in check_should_enable_pagination() for more info.
180
shadowed_args[arg_name] = argument_table[arg_name]
181
argument_table[arg_name] = argument
182
183
184
def check_should_enable_pagination(input_tokens, shadowed_args, argument_table,
185
parsed_args, parsed_globals, **kwargs):
186
normalized_paging_args = ['start_token', 'max_items']
187
for token in input_tokens:
188
py_name = token.replace('-', '_')
189
if getattr(parsed_args, py_name) is not None and \
190
py_name not in normalized_paging_args:
191
# The user has specified a manual (undocumented) pagination arg.
192
# We need to automatically turn pagination off.
193
logger.debug("User has specified a manual pagination arg. "
194
"Automatically setting --no-paginate.")
195
parsed_globals.paginate = False
196
197
if not parsed_globals.paginate:
198
ensure_paging_params_not_set(parsed_args, shadowed_args)
199
# Because pagination is now disabled, there's a chance that
200
# we were shadowing arguments. For example, we inject a
201
# --max-items argument in unify_paging_params(). If the
202
# the operation also provides its own MaxItems (which we
203
# expose as --max-items) then our custom pagination arg
204
# was shadowing the customers arg. When we turn pagination
205
# off we need to put back the original argument which is
206
# what we're doing here.
207
for key, value in shadowed_args.items():
208
argument_table[key] = value
209
210
211
def ensure_paging_params_not_set(parsed_args, shadowed_args):
212
paging_params = ['starting_token', 'page_size', 'max_items']
213
shadowed_params = [p.replace('-', '_') for p in shadowed_args.keys()]
214
params_used = [p for p in paging_params if
215
p not in shadowed_params and getattr(parsed_args, p, None)]
216
217
if len(params_used) > 0:
218
converted_params = ', '.join(
219
["--" + p.replace('_', '-') for p in params_used])
220
raise PaginationError(
221
message="Cannot specify --no-paginate along with pagination "
222
"arguments: %s" % converted_params)
223
224
225
def _remove_existing_paging_arguments(argument_table, pagination_config):
226
for cli_name in _get_all_cli_input_tokens(pagination_config):
227
argument_table[cli_name]._UNDOCUMENTED = True
228
229
230
def _get_all_cli_input_tokens(pagination_config):
231
# Get all input tokens including the limit_key
232
# if it exists.
233
tokens = _get_input_tokens(pagination_config)
234
for token_name in tokens:
235
cli_name = xform_name(token_name, '-')
236
yield cli_name
237
if 'limit_key' in pagination_config:
238
key_name = pagination_config['limit_key']
239
cli_name = xform_name(key_name, '-')
240
yield cli_name
241
242
243
def _get_input_tokens(pagination_config):
244
tokens = pagination_config['input_token']
245
if not isinstance(tokens, list):
246
return [tokens]
247
return tokens
248
249
250
def _get_cli_name(param_objects, token_name):
251
for param in param_objects:
252
if param.name == token_name:
253
return param.cli_name.lstrip('-')
254
255
256
class PageArgument(BaseCLIArgument):
257
type_map = {
258
'string': str,
259
'integer': int,
260
'long': int,
261
}
262
263
def __init__(self, name, documentation, parse_type, serialized_name):
264
self.argument_model = model.Shape('PageArgument', {'type': 'string'})
265
self._name = name
266
self._serialized_name = serialized_name
267
self._documentation = documentation
268
self._parse_type = parse_type
269
self._required = False
270
271
def _emit_non_positive_max_items_warning(self):
272
uni_print(
273
"warning: Non-positive values for --max-items may result in undefined behavior.\n",
274
sys.stderr)
275
276
@property
277
def cli_name(self):
278
return '--' + self._name
279
280
@property
281
def cli_type_name(self):
282
return self._parse_type
283
284
@property
285
def required(self):
286
return self._required
287
288
@required.setter
289
def required(self, value):
290
self._required = value
291
292
@property
293
def documentation(self):
294
return self._documentation
295
296
def add_to_parser(self, parser):
297
parser.add_argument(self.cli_name, dest=self.py_name,
298
type=self.type_map[self._parse_type])
299
300
def add_to_params(self, parameters, value):
301
if value is not None:
302
if self._serialized_name == 'MaxItems' and int(value) <= 0:
303
self._emit_non_positive_max_items_warning()
304
pagination_config = parameters.get('PaginationConfig', {})
305
pagination_config[self._serialized_name] = value
306
parameters['PaginationConfig'] = pagination_config
307
308