Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/absl_py-2.0.0-py3.8.egg/absl/flags/argparse_flags.py: 17%
121 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:13 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:13 +0000
1# Copyright 2018 The Abseil Authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""This module provides argparse integration with absl.flags.
17``argparse_flags.ArgumentParser`` is a drop-in replacement for
18:class:`argparse.ArgumentParser`. It takes care of collecting and defining absl
19flags in :mod:`argparse`.
21Here is a simple example::
23 # Assume the following absl.flags is defined in another module:
24 #
25 # from absl import flags
26 # flags.DEFINE_string('echo', None, 'The echo message.')
27 #
28 parser = argparse_flags.ArgumentParser(
29 description='A demo of absl.flags and argparse integration.')
30 parser.add_argument('--header', help='Header message to print.')
32 # The parser will also accept the absl flag `--echo`.
33 # The `header` value is available as `args.header` just like a regular
34 # argparse flag. The absl flag `--echo` continues to be available via
35 # `absl.flags.FLAGS` if you want to access it.
36 args = parser.parse_args()
38 # Example usages:
39 # ./program --echo='A message.' --header='A header'
40 # ./program --header 'A header' --echo 'A message.'
43Here is another example demonstrates subparsers::
45 parser = argparse_flags.ArgumentParser(description='A subcommands demo.')
46 parser.add_argument('--header', help='The header message to print.')
48 subparsers = parser.add_subparsers(help='The command to execute.')
50 roll_dice_parser = subparsers.add_parser(
51 'roll_dice', help='Roll a dice.',
52 # By default, absl flags can also be specified after the sub-command.
53 # To only allow them before sub-command, pass
54 # `inherited_absl_flags=None`.
55 inherited_absl_flags=None)
56 roll_dice_parser.add_argument('--num_faces', type=int, default=6)
57 roll_dice_parser.set_defaults(command=roll_dice)
59 shuffle_parser = subparsers.add_parser('shuffle', help='Shuffle inputs.')
60 shuffle_parser.add_argument(
61 'inputs', metavar='I', nargs='+', help='Inputs to shuffle.')
62 shuffle_parser.set_defaults(command=shuffle)
64 args = parser.parse_args(argv[1:])
65 args.command(args)
67 # Example usages:
68 # ./program --echo='A message.' roll_dice --num_faces=6
69 # ./program shuffle --echo='A message.' 1 2 3 4
72There are several differences between :mod:`absl.flags` and
73:mod:`~absl.flags.argparse_flags`:
751. Flags defined with absl.flags are parsed differently when using the
76 argparse parser. Notably:
78 1) absl.flags allows both single-dash and double-dash for any flag, and
79 doesn't distinguish them; argparse_flags only allows double-dash for
80 flag's regular name, and single-dash for flag's ``short_name``.
81 2) Boolean flags in absl.flags can be specified with ``--bool``,
82 ``--nobool``, as well as ``--bool=true/false`` (though not recommended);
83 in argparse_flags, it only allows ``--bool``, ``--nobool``.
852. Help related flag differences:
87 1) absl.flags does not define help flags, absl.app does that; argparse_flags
88 defines help flags unless passed with ``add_help=False``.
89 2) absl.app supports ``--helpxml``; argparse_flags does not.
90 3) argparse_flags supports ``-h``; absl.app does not.
91"""
93import argparse
94import sys
96from absl import flags
99_BUILT_IN_FLAGS = frozenset({
100 'help',
101 'helpshort',
102 'helpfull',
103 'helpxml',
104 'flagfile',
105 'undefok',
106})
109class ArgumentParser(argparse.ArgumentParser):
110 """Custom ArgumentParser class to support special absl flags."""
112 def __init__(self, **kwargs):
113 """Initializes ArgumentParser.
115 Args:
116 **kwargs: same as argparse.ArgumentParser, except:
117 1. It also accepts `inherited_absl_flags`: the absl flags to inherit.
118 The default is the global absl.flags.FLAGS instance. Pass None to
119 ignore absl flags.
120 2. The `prefix_chars` argument must be the default value '-'.
122 Raises:
123 ValueError: Raised when prefix_chars is not '-'.
124 """
125 prefix_chars = kwargs.get('prefix_chars', '-')
126 if prefix_chars != '-':
127 raise ValueError(
128 'argparse_flags.ArgumentParser only supports "-" as the prefix '
129 'character, found "{}".'.format(prefix_chars))
131 # Remove inherited_absl_flags before calling super.
132 self._inherited_absl_flags = kwargs.pop('inherited_absl_flags', flags.FLAGS)
133 # Now call super to initialize argparse.ArgumentParser before calling
134 # add_argument in _define_absl_flags.
135 super(ArgumentParser, self).__init__(**kwargs)
137 if self.add_help:
138 # -h and --help are defined in super.
139 # Also add the --helpshort and --helpfull flags.
140 self.add_argument(
141 # Action 'help' defines a similar flag to -h/--help.
142 '--helpshort', action='help',
143 default=argparse.SUPPRESS, help=argparse.SUPPRESS)
144 self.add_argument(
145 '--helpfull', action=_HelpFullAction,
146 default=argparse.SUPPRESS, help='show full help message and exit')
148 if self._inherited_absl_flags is not None:
149 self.add_argument(
150 '--undefok', default=argparse.SUPPRESS, help=argparse.SUPPRESS)
151 self._define_absl_flags(self._inherited_absl_flags)
153 def parse_known_args(self, args=None, namespace=None):
154 if args is None:
155 args = sys.argv[1:]
156 if self._inherited_absl_flags is not None:
157 # Handle --flagfile.
158 # Explicitly specify force_gnu=True, since argparse behaves like
159 # gnu_getopt: flags can be specified after positional arguments.
160 args = self._inherited_absl_flags.read_flags_from_files(
161 args, force_gnu=True)
163 undefok_missing = object()
164 undefok = getattr(namespace, 'undefok', undefok_missing)
166 namespace, args = super(ArgumentParser, self).parse_known_args(
167 args, namespace)
169 # For Python <= 2.7.8: https://bugs.python.org/issue9351, a bug where
170 # sub-parsers don't preserve existing namespace attributes.
171 # Restore the undefok attribute if a sub-parser dropped it.
172 if undefok is not undefok_missing:
173 namespace.undefok = undefok
175 if self._inherited_absl_flags is not None:
176 # Handle --undefok. At this point, `args` only contains unknown flags,
177 # so it won't strip defined flags that are also specified with --undefok.
178 # For Python <= 2.7.8: https://bugs.python.org/issue9351, a bug where
179 # sub-parsers don't preserve existing namespace attributes. The undefok
180 # attribute might not exist because a subparser dropped it.
181 if hasattr(namespace, 'undefok'):
182 args = _strip_undefok_args(namespace.undefok, args)
183 # absl flags are not exposed in the Namespace object. See Namespace:
184 # https://docs.python.org/3/library/argparse.html#argparse.Namespace.
185 del namespace.undefok
186 self._inherited_absl_flags.mark_as_parsed()
187 try:
188 self._inherited_absl_flags.validate_all_flags()
189 except flags.IllegalFlagValueError as e:
190 self.error(str(e))
192 return namespace, args
194 def _define_absl_flags(self, absl_flags):
195 """Defines flags from absl_flags."""
196 key_flags = set(absl_flags.get_key_flags_for_module(sys.argv[0]))
197 for name in absl_flags:
198 if name in _BUILT_IN_FLAGS:
199 # Do not inherit built-in flags.
200 continue
201 flag_instance = absl_flags[name]
202 # Each flags with short_name appears in FLAGS twice, so only define
203 # when the dictionary key is equal to the regular name.
204 if name == flag_instance.name:
205 # Suppress the flag in the help short message if it's not a main
206 # module's key flag.
207 suppress = flag_instance not in key_flags
208 self._define_absl_flag(flag_instance, suppress)
210 def _define_absl_flag(self, flag_instance, suppress):
211 """Defines a flag from the flag_instance."""
212 flag_name = flag_instance.name
213 short_name = flag_instance.short_name
214 argument_names = ['--' + flag_name]
215 if short_name:
216 argument_names.insert(0, '-' + short_name)
217 if suppress:
218 helptext = argparse.SUPPRESS
219 else:
220 # argparse help string uses %-formatting. Escape the literal %'s.
221 helptext = flag_instance.help.replace('%', '%%')
222 if flag_instance.boolean:
223 # Only add the `no` form to the long name.
224 argument_names.append('--no' + flag_name)
225 self.add_argument(
226 *argument_names, action=_BooleanFlagAction, help=helptext,
227 metavar=flag_instance.name.upper(),
228 flag_instance=flag_instance)
229 else:
230 self.add_argument(
231 *argument_names, action=_FlagAction, help=helptext,
232 metavar=flag_instance.name.upper(),
233 flag_instance=flag_instance)
236class _FlagAction(argparse.Action):
237 """Action class for Abseil non-boolean flags."""
239 def __init__(
240 self,
241 option_strings,
242 dest,
243 help, # pylint: disable=redefined-builtin
244 metavar,
245 flag_instance,
246 default=argparse.SUPPRESS):
247 """Initializes _FlagAction.
249 Args:
250 option_strings: See argparse.Action.
251 dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS.
252 help: See argparse.Action.
253 metavar: See argparse.Action.
254 flag_instance: absl.flags.Flag, the absl flag instance.
255 default: Ignored. The flag always uses dest=argparse.SUPPRESS so it
256 doesn't affect the parsing result.
257 """
258 del dest
259 self._flag_instance = flag_instance
260 super(_FlagAction, self).__init__(
261 option_strings=option_strings,
262 dest=argparse.SUPPRESS,
263 help=help,
264 metavar=metavar)
266 def __call__(self, parser, namespace, values, option_string=None):
267 """See https://docs.python.org/3/library/argparse.html#action-classes."""
268 self._flag_instance.parse(values)
269 self._flag_instance.using_default_value = False
272class _BooleanFlagAction(argparse.Action):
273 """Action class for Abseil boolean flags."""
275 def __init__(
276 self,
277 option_strings,
278 dest,
279 help, # pylint: disable=redefined-builtin
280 metavar,
281 flag_instance,
282 default=argparse.SUPPRESS):
283 """Initializes _BooleanFlagAction.
285 Args:
286 option_strings: See argparse.Action.
287 dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS.
288 help: See argparse.Action.
289 metavar: See argparse.Action.
290 flag_instance: absl.flags.Flag, the absl flag instance.
291 default: Ignored. The flag always uses dest=argparse.SUPPRESS so it
292 doesn't affect the parsing result.
293 """
294 del dest, default
295 self._flag_instance = flag_instance
296 flag_names = [self._flag_instance.name]
297 if self._flag_instance.short_name:
298 flag_names.append(self._flag_instance.short_name)
299 self._flag_names = frozenset(flag_names)
300 super(_BooleanFlagAction, self).__init__(
301 option_strings=option_strings,
302 dest=argparse.SUPPRESS,
303 nargs=0, # Does not accept values, only `--bool` or `--nobool`.
304 help=help,
305 metavar=metavar)
307 def __call__(self, parser, namespace, values, option_string=None):
308 """See https://docs.python.org/3/library/argparse.html#action-classes."""
309 if not isinstance(values, list) or values:
310 raise ValueError('values must be an empty list.')
311 if option_string.startswith('--'):
312 option = option_string[2:]
313 else:
314 option = option_string[1:]
315 if option in self._flag_names:
316 self._flag_instance.parse('true')
317 else:
318 if not option.startswith('no') or option[2:] not in self._flag_names:
319 raise ValueError('invalid option_string: ' + option_string)
320 self._flag_instance.parse('false')
321 self._flag_instance.using_default_value = False
324class _HelpFullAction(argparse.Action):
325 """Action class for --helpfull flag."""
327 def __init__(self, option_strings, dest, default, help): # pylint: disable=redefined-builtin
328 """Initializes _HelpFullAction.
330 Args:
331 option_strings: See argparse.Action.
332 dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS.
333 default: Ignored.
334 help: See argparse.Action.
335 """
336 del dest, default
337 super(_HelpFullAction, self).__init__(
338 option_strings=option_strings,
339 dest=argparse.SUPPRESS,
340 default=argparse.SUPPRESS,
341 nargs=0,
342 help=help)
344 def __call__(self, parser, namespace, values, option_string=None):
345 """See https://docs.python.org/3/library/argparse.html#action-classes."""
346 # This only prints flags when help is not argparse.SUPPRESS.
347 # It includes user defined argparse flags, as well as main module's
348 # key absl flags. Other absl flags use argparse.SUPPRESS, so they aren't
349 # printed here.
350 parser.print_help()
352 absl_flags = parser._inherited_absl_flags # pylint: disable=protected-access
353 if absl_flags is not None:
354 modules = sorted(absl_flags.flags_by_module_dict())
355 main_module = sys.argv[0]
356 if main_module in modules:
357 # The main module flags are already printed in parser.print_help().
358 modules.remove(main_module)
359 print(absl_flags._get_help_for_modules( # pylint: disable=protected-access
360 modules, prefix='', include_special_flags=True))
361 parser.exit()
364def _strip_undefok_args(undefok, args):
365 """Returns a new list of args after removing flags in --undefok."""
366 if undefok:
367 undefok_names = set(name.strip() for name in undefok.split(','))
368 undefok_names |= set('no' + name for name in undefok_names)
369 # Remove undefok flags.
370 args = [arg for arg in args if not _is_undefok(arg, undefok_names)]
371 return args
374def _is_undefok(arg, undefok_names):
375 """Returns whether we can ignore arg based on a set of undefok flag names."""
376 if not arg.startswith('-'):
377 return False
378 if arg.startswith('--'):
379 arg_without_dash = arg[2:]
380 else:
381 arg_without_dash = arg[1:]
382 if '=' in arg_without_dash:
383 name, _ = arg_without_dash.split('=', 1)
384 else:
385 name = arg_without_dash
386 if name in undefok_names:
387 return True
388 return False