| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | """This module provides argparse integration with absl.flags. |
| | |
| | ``argparse_flags.ArgumentParser`` is a drop-in replacement for |
| | :class:`argparse.ArgumentParser`. It takes care of collecting and defining absl |
| | flags in :mod:`argparse`. |
| | |
| | Here is a simple example:: |
| | |
| | # Assume the following absl.flags is defined in another module: |
| | # |
| | # from absl import flags |
| | # flags.DEFINE_string('echo', None, 'The echo message.') |
| | # |
| | parser = argparse_flags.ArgumentParser( |
| | description='A demo of absl.flags and argparse integration.') |
| | parser.add_argument('--header', help='Header message to print.') |
| | |
| | # The parser will also accept the absl flag `--echo`. |
| | # The `header` value is available as `args.header` just like a regular |
| | # argparse flag. The absl flag `--echo` continues to be available via |
| | # `absl.flags.FLAGS` if you want to access it. |
| | args = parser.parse_args() |
| | |
| | # Example usages: |
| | # ./program --echo='A message.' --header='A header' |
| | # ./program --header 'A header' --echo 'A message.' |
| | |
| | |
| | Here is another example demonstrates subparsers:: |
| | |
| | parser = argparse_flags.ArgumentParser(description='A subcommands demo.') |
| | parser.add_argument('--header', help='The header message to print.') |
| | |
| | subparsers = parser.add_subparsers(help='The command to execute.') |
| | |
| | roll_dice_parser = subparsers.add_parser( |
| | 'roll_dice', help='Roll a dice.', |
| | # By default, absl flags can also be specified after the sub-command. |
| | # To only allow them before sub-command, pass |
| | # `inherited_absl_flags=None`. |
| | inherited_absl_flags=None) |
| | roll_dice_parser.add_argument('--num_faces', type=int, default=6) |
| | roll_dice_parser.set_defaults(command=roll_dice) |
| | |
| | shuffle_parser = subparsers.add_parser('shuffle', help='Shuffle inputs.') |
| | shuffle_parser.add_argument( |
| | 'inputs', metavar='I', nargs='+', help='Inputs to shuffle.') |
| | shuffle_parser.set_defaults(command=shuffle) |
| | |
| | args = parser.parse_args(argv[1:]) |
| | args.command(args) |
| | |
| | # Example usages: |
| | # ./program --echo='A message.' roll_dice --num_faces=6 |
| | # ./program shuffle --echo='A message.' 1 2 3 4 |
| | |
| | |
| | There are several differences between :mod:`absl.flags` and |
| | :mod:`~absl.flags.argparse_flags`: |
| | |
| | 1. Flags defined with absl.flags are parsed differently when using the |
| | argparse parser. Notably: |
| | |
| | 1) absl.flags allows both single-dash and double-dash for any flag, and |
| | doesn't distinguish them; argparse_flags only allows double-dash for |
| | flag's regular name, and single-dash for flag's ``short_name``. |
| | 2) Boolean flags in absl.flags can be specified with ``--bool``, |
| | ``--nobool``, as well as ``--bool=true/false`` (though not recommended); |
| | in argparse_flags, it only allows ``--bool``, ``--nobool``. |
| | |
| | 2. Help related flag differences: |
| | |
| | 1) absl.flags does not define help flags, absl.app does that; argparse_flags |
| | defines help flags unless passed with ``add_help=False``. |
| | 2) absl.app supports ``--helpxml``; argparse_flags does not. |
| | 3) argparse_flags supports ``-h``; absl.app does not. |
| | """ |
| |
|
| | import argparse |
| | import sys |
| |
|
| | from absl import flags |
| |
|
| |
|
| | _BUILT_IN_FLAGS = frozenset({ |
| | 'help', |
| | 'helpshort', |
| | 'helpfull', |
| | 'helpxml', |
| | 'flagfile', |
| | 'undefok', |
| | }) |
| |
|
| |
|
| | class ArgumentParser(argparse.ArgumentParser): |
| | """Custom ArgumentParser class to support special absl flags.""" |
| |
|
| | def __init__(self, **kwargs): |
| | """Initializes ArgumentParser. |
| | |
| | Args: |
| | **kwargs: same as argparse.ArgumentParser, except: |
| | 1. It also accepts `inherited_absl_flags`: the absl flags to inherit. |
| | The default is the global absl.flags.FLAGS instance. Pass None to |
| | ignore absl flags. |
| | 2. The `prefix_chars` argument must be the default value '-'. |
| | |
| | Raises: |
| | ValueError: Raised when prefix_chars is not '-'. |
| | """ |
| | prefix_chars = kwargs.get('prefix_chars', '-') |
| | if prefix_chars != '-': |
| | raise ValueError( |
| | 'argparse_flags.ArgumentParser only supports "-" as the prefix ' |
| | 'character, found "{}".'.format(prefix_chars)) |
| |
|
| | |
| | self._inherited_absl_flags = kwargs.pop('inherited_absl_flags', flags.FLAGS) |
| | |
| | |
| | super(ArgumentParser, self).__init__(**kwargs) |
| |
|
| | if self.add_help: |
| | |
| | |
| | self.add_argument( |
| | |
| | '--helpshort', action='help', |
| | default=argparse.SUPPRESS, help=argparse.SUPPRESS) |
| | self.add_argument( |
| | '--helpfull', action=_HelpFullAction, |
| | default=argparse.SUPPRESS, help='show full help message and exit') |
| |
|
| | if self._inherited_absl_flags: |
| | self.add_argument( |
| | '--undefok', default=argparse.SUPPRESS, help=argparse.SUPPRESS) |
| | self._define_absl_flags(self._inherited_absl_flags) |
| |
|
| | def parse_known_args(self, args=None, namespace=None): |
| | if args is None: |
| | args = sys.argv[1:] |
| | if self._inherited_absl_flags: |
| | |
| | |
| | |
| | args = self._inherited_absl_flags.read_flags_from_files( |
| | args, force_gnu=True) |
| |
|
| | undefok_missing = object() |
| | undefok = getattr(namespace, 'undefok', undefok_missing) |
| |
|
| | namespace, args = super(ArgumentParser, self).parse_known_args( |
| | args, namespace) |
| |
|
| | |
| | |
| | |
| | if undefok is not undefok_missing: |
| | namespace.undefok = undefok |
| |
|
| | if self._inherited_absl_flags: |
| | |
| | |
| | |
| | |
| | |
| | if hasattr(namespace, 'undefok'): |
| | args = _strip_undefok_args(namespace.undefok, args) |
| | |
| | |
| | del namespace.undefok |
| | self._inherited_absl_flags.mark_as_parsed() |
| | try: |
| | self._inherited_absl_flags.validate_all_flags() |
| | except flags.IllegalFlagValueError as e: |
| | self.error(str(e)) |
| |
|
| | return namespace, args |
| |
|
| | def _define_absl_flags(self, absl_flags): |
| | """Defines flags from absl_flags.""" |
| | key_flags = set(absl_flags.get_key_flags_for_module(sys.argv[0])) |
| | for name in absl_flags: |
| | if name in _BUILT_IN_FLAGS: |
| | |
| | continue |
| | flag_instance = absl_flags[name] |
| | |
| | |
| | if name == flag_instance.name: |
| | |
| | |
| | suppress = flag_instance not in key_flags |
| | self._define_absl_flag(flag_instance, suppress) |
| |
|
| | def _define_absl_flag(self, flag_instance, suppress): |
| | """Defines a flag from the flag_instance.""" |
| | flag_name = flag_instance.name |
| | short_name = flag_instance.short_name |
| | argument_names = ['--' + flag_name] |
| | if short_name: |
| | argument_names.insert(0, '-' + short_name) |
| | if suppress: |
| | helptext = argparse.SUPPRESS |
| | else: |
| | |
| | helptext = flag_instance.help.replace('%', '%%') |
| | if flag_instance.boolean: |
| | |
| | argument_names.append('--no' + flag_name) |
| | self.add_argument( |
| | *argument_names, action=_BooleanFlagAction, help=helptext, |
| | metavar=flag_instance.name.upper(), |
| | flag_instance=flag_instance) |
| | else: |
| | self.add_argument( |
| | *argument_names, action=_FlagAction, help=helptext, |
| | metavar=flag_instance.name.upper(), |
| | flag_instance=flag_instance) |
| |
|
| |
|
| | class _FlagAction(argparse.Action): |
| | """Action class for Abseil non-boolean flags.""" |
| |
|
| | def __init__( |
| | self, |
| | option_strings, |
| | dest, |
| | help, |
| | metavar, |
| | flag_instance, |
| | default=argparse.SUPPRESS): |
| | """Initializes _FlagAction. |
| | |
| | Args: |
| | option_strings: See argparse.Action. |
| | dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS. |
| | help: See argparse.Action. |
| | metavar: See argparse.Action. |
| | flag_instance: absl.flags.Flag, the absl flag instance. |
| | default: Ignored. The flag always uses dest=argparse.SUPPRESS so it |
| | doesn't affect the parsing result. |
| | """ |
| | del dest |
| | self._flag_instance = flag_instance |
| | super(_FlagAction, self).__init__( |
| | option_strings=option_strings, |
| | dest=argparse.SUPPRESS, |
| | help=help, |
| | metavar=metavar) |
| |
|
| | def __call__(self, parser, namespace, values, option_string=None): |
| | """See https://docs.python.org/3/library/argparse.html#action-classes.""" |
| | self._flag_instance.parse(values) |
| | self._flag_instance.using_default_value = False |
| |
|
| |
|
| | class _BooleanFlagAction(argparse.Action): |
| | """Action class for Abseil boolean flags.""" |
| |
|
| | def __init__( |
| | self, |
| | option_strings, |
| | dest, |
| | help, |
| | metavar, |
| | flag_instance, |
| | default=argparse.SUPPRESS): |
| | """Initializes _BooleanFlagAction. |
| | |
| | Args: |
| | option_strings: See argparse.Action. |
| | dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS. |
| | help: See argparse.Action. |
| | metavar: See argparse.Action. |
| | flag_instance: absl.flags.Flag, the absl flag instance. |
| | default: Ignored. The flag always uses dest=argparse.SUPPRESS so it |
| | doesn't affect the parsing result. |
| | """ |
| | del dest, default |
| | self._flag_instance = flag_instance |
| | flag_names = [self._flag_instance.name] |
| | if self._flag_instance.short_name: |
| | flag_names.append(self._flag_instance.short_name) |
| | self._flag_names = frozenset(flag_names) |
| | super(_BooleanFlagAction, self).__init__( |
| | option_strings=option_strings, |
| | dest=argparse.SUPPRESS, |
| | nargs=0, |
| | help=help, |
| | metavar=metavar) |
| |
|
| | def __call__(self, parser, namespace, values, option_string=None): |
| | """See https://docs.python.org/3/library/argparse.html#action-classes.""" |
| | if not isinstance(values, list) or values: |
| | raise ValueError('values must be an empty list.') |
| | if option_string.startswith('--'): |
| | option = option_string[2:] |
| | else: |
| | option = option_string[1:] |
| | if option in self._flag_names: |
| | self._flag_instance.parse('true') |
| | else: |
| | if not option.startswith('no') or option[2:] not in self._flag_names: |
| | raise ValueError('invalid option_string: ' + option_string) |
| | self._flag_instance.parse('false') |
| | self._flag_instance.using_default_value = False |
| |
|
| |
|
| | class _HelpFullAction(argparse.Action): |
| | """Action class for --helpfull flag.""" |
| |
|
| | def __init__(self, option_strings, dest, default, help): |
| | """Initializes _HelpFullAction. |
| | |
| | Args: |
| | option_strings: See argparse.Action. |
| | dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS. |
| | default: Ignored. |
| | help: See argparse.Action. |
| | """ |
| | del dest, default |
| | super(_HelpFullAction, self).__init__( |
| | option_strings=option_strings, |
| | dest=argparse.SUPPRESS, |
| | default=argparse.SUPPRESS, |
| | nargs=0, |
| | help=help) |
| |
|
| | def __call__(self, parser, namespace, values, option_string=None): |
| | """See https://docs.python.org/3/library/argparse.html#action-classes.""" |
| | |
| | |
| | |
| | |
| | parser.print_help() |
| |
|
| | absl_flags = parser._inherited_absl_flags |
| | if absl_flags: |
| | modules = sorted(absl_flags.flags_by_module_dict()) |
| | main_module = sys.argv[0] |
| | if main_module in modules: |
| | |
| | modules.remove(main_module) |
| | print(absl_flags._get_help_for_modules( |
| | modules, prefix='', include_special_flags=True)) |
| | parser.exit() |
| |
|
| |
|
| | def _strip_undefok_args(undefok, args): |
| | """Returns a new list of args after removing flags in --undefok.""" |
| | if undefok: |
| | undefok_names = set(name.strip() for name in undefok.split(',')) |
| | undefok_names |= set('no' + name for name in undefok_names) |
| | |
| | args = [arg for arg in args if not _is_undefok(arg, undefok_names)] |
| | return args |
| |
|
| |
|
| | def _is_undefok(arg, undefok_names): |
| | """Returns whether we can ignore arg based on a set of undefok flag names.""" |
| | if not arg.startswith('-'): |
| | return False |
| | if arg.startswith('--'): |
| | arg_without_dash = arg[2:] |
| | else: |
| | arg_without_dash = arg[1:] |
| | if '=' in arg_without_dash: |
| | name, _ = arg_without_dash.split('=', 1) |
| | else: |
| | name = arg_without_dash |
| | if name in undefok_names: |
| | return True |
| | return False |
| |
|