|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Defines the FlagValues class - registry of 'Flag' objects. |
|
|
|
Do NOT import this module directly. Import the flags package and use the |
|
aliases defined at the package level instead. |
|
""" |
|
|
|
import copy |
|
import itertools |
|
import logging |
|
import os |
|
import sys |
|
from typing import Generic, TypeVar |
|
from xml.dom import minidom |
|
|
|
from absl.flags import _exceptions |
|
from absl.flags import _flag |
|
from absl.flags import _helpers |
|
from absl.flags import _validators_classes |
|
|
|
|
|
_helpers.disclaim_module_ids.add(id(sys.modules[__name__])) |
|
|
|
_T = TypeVar('_T') |
|
|
|
|
|
class FlagValues: |
|
"""Registry of :class:`~absl.flags.Flag` objects. |
|
|
|
A :class:`FlagValues` can then scan command line arguments, passing flag |
|
arguments through to the 'Flag' objects that it owns. It also |
|
provides easy access to the flag values. Typically only one |
|
:class:`FlagValues` object is needed by an application: |
|
:const:`FLAGS`. |
|
|
|
This class is heavily overloaded: |
|
|
|
:class:`Flag` objects are registered via ``__setitem__``:: |
|
|
|
FLAGS['longname'] = x # register a new flag |
|
|
|
The ``.value`` attribute of the registered :class:`~absl.flags.Flag` objects |
|
can be accessed as attributes of this :class:`FlagValues` object, through |
|
``__getattr__``. Both the long and short name of the original |
|
:class:`~absl.flags.Flag` objects can be used to access its value:: |
|
|
|
FLAGS.longname # parsed flag value |
|
FLAGS.x # parsed flag value (short name) |
|
|
|
Command line arguments are scanned and passed to the registered |
|
:class:`~absl.flags.Flag` objects through the ``__call__`` method. Unparsed |
|
arguments, including ``argv[0]`` (e.g. the program name) are returned:: |
|
|
|
argv = FLAGS(sys.argv) # scan command line arguments |
|
|
|
The original registered :class:`~absl.flags.Flag` objects can be retrieved |
|
through the use of the dictionary-like operator, ``__getitem__``:: |
|
|
|
x = FLAGS['longname'] # access the registered Flag object |
|
|
|
The ``str()`` operator of a :class:`absl.flags.FlagValues` object provides |
|
help for all of the registered :class:`~absl.flags.Flag` objects. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self): |
|
|
|
|
|
|
|
|
|
self.__dict__['__flags'] = {} |
|
|
|
|
|
|
|
self.__dict__['__hiddenflags'] = set() |
|
|
|
|
|
|
|
self.__dict__['__flags_by_module'] = {} |
|
|
|
|
|
self.__dict__['__flags_by_module_id'] = {} |
|
|
|
|
|
self.__dict__['__key_flags_by_module'] = {} |
|
|
|
|
|
self.__dict__['__flags_parsed'] = False |
|
|
|
|
|
self.__dict__['__unparse_flags_called'] = False |
|
|
|
|
|
self.__dict__['__set_unknown'] = None |
|
|
|
|
|
|
|
|
|
|
|
self.__dict__['__banned_flag_names'] = frozenset(dir(FlagValues)) |
|
|
|
|
|
self.__dict__['__use_gnu_getopt'] = True |
|
|
|
|
|
self.__dict__['__use_gnu_getopt_explicitly_set'] = False |
|
|
|
|
|
|
|
self.__dict__['__is_retired_flag_func'] = None |
|
|
|
def set_gnu_getopt(self, gnu_getopt=True): |
|
"""Sets whether or not to use GNU style scanning. |
|
|
|
GNU style allows mixing of flag and non-flag arguments. See |
|
http://docs.python.org/library/getopt.html#getopt.gnu_getopt |
|
|
|
Args: |
|
gnu_getopt: bool, whether or not to use GNU style scanning. |
|
""" |
|
self.__dict__['__use_gnu_getopt'] = gnu_getopt |
|
self.__dict__['__use_gnu_getopt_explicitly_set'] = True |
|
|
|
def is_gnu_getopt(self): |
|
return self.__dict__['__use_gnu_getopt'] |
|
|
|
def _flags(self): |
|
return self.__dict__['__flags'] |
|
|
|
def flags_by_module_dict(self): |
|
"""Returns the dictionary of module_name -> list of defined flags. |
|
|
|
Returns: |
|
A dictionary. Its keys are module names (strings). Its values |
|
are lists of Flag objects. |
|
""" |
|
return self.__dict__['__flags_by_module'] |
|
|
|
def flags_by_module_id_dict(self): |
|
"""Returns the dictionary of module_id -> list of defined flags. |
|
|
|
Returns: |
|
A dictionary. Its keys are module IDs (ints). Its values |
|
are lists of Flag objects. |
|
""" |
|
return self.__dict__['__flags_by_module_id'] |
|
|
|
def key_flags_by_module_dict(self): |
|
"""Returns the dictionary of module_name -> list of key flags. |
|
|
|
Returns: |
|
A dictionary. Its keys are module names (strings). Its values |
|
are lists of Flag objects. |
|
""" |
|
return self.__dict__['__key_flags_by_module'] |
|
|
|
def register_flag_by_module(self, module_name, flag): |
|
"""Records the module that defines a specific flag. |
|
|
|
We keep track of which flag is defined by which module so that we |
|
can later sort the flags by module. |
|
|
|
Args: |
|
module_name: str, the name of a Python module. |
|
flag: Flag, the Flag instance that is key to the module. |
|
""" |
|
flags_by_module = self.flags_by_module_dict() |
|
flags_by_module.setdefault(module_name, []).append(flag) |
|
|
|
def register_flag_by_module_id(self, module_id, flag): |
|
"""Records the module that defines a specific flag. |
|
|
|
Args: |
|
module_id: int, the ID of the Python module. |
|
flag: Flag, the Flag instance that is key to the module. |
|
""" |
|
flags_by_module_id = self.flags_by_module_id_dict() |
|
flags_by_module_id.setdefault(module_id, []).append(flag) |
|
|
|
def register_key_flag_for_module(self, module_name, flag): |
|
"""Specifies that a flag is a key flag for a module. |
|
|
|
Args: |
|
module_name: str, the name of a Python module. |
|
flag: Flag, the Flag instance that is key to the module. |
|
""" |
|
key_flags_by_module = self.key_flags_by_module_dict() |
|
|
|
key_flags = key_flags_by_module.setdefault(module_name, []) |
|
|
|
if flag not in key_flags: |
|
key_flags.append(flag) |
|
|
|
def _flag_is_registered(self, flag_obj): |
|
"""Checks whether a Flag object is registered under long name or short name. |
|
|
|
Args: |
|
flag_obj: Flag, the Flag instance to check for. |
|
|
|
Returns: |
|
bool, True iff flag_obj is registered under long name or short name. |
|
""" |
|
flag_dict = self._flags() |
|
|
|
name = flag_obj.name |
|
if flag_dict.get(name, None) == flag_obj: |
|
return True |
|
|
|
short_name = flag_obj.short_name |
|
if (short_name is not None and flag_dict.get(short_name, None) == flag_obj): |
|
return True |
|
return False |
|
|
|
def _cleanup_unregistered_flag_from_module_dicts(self, flag_obj): |
|
"""Cleans up unregistered flags from all module -> [flags] dictionaries. |
|
|
|
If flag_obj is registered under either its long name or short name, it |
|
won't be removed from the dictionaries. |
|
|
|
Args: |
|
flag_obj: Flag, the Flag instance to clean up for. |
|
""" |
|
if self._flag_is_registered(flag_obj): |
|
return |
|
for flags_by_module_dict in (self.flags_by_module_dict(), |
|
self.flags_by_module_id_dict(), |
|
self.key_flags_by_module_dict()): |
|
for flags_in_module in flags_by_module_dict.values(): |
|
|
|
|
|
while flag_obj in flags_in_module: |
|
flags_in_module.remove(flag_obj) |
|
|
|
def get_flags_for_module(self, module): |
|
"""Returns the list of flags defined by a module. |
|
|
|
Args: |
|
module: module|str, the module to get flags from. |
|
|
|
Returns: |
|
[Flag], a new list of Flag instances. Caller may update this list as |
|
desired: none of those changes will affect the internals of this |
|
FlagValue instance. |
|
""" |
|
if not isinstance(module, str): |
|
module = module.__name__ |
|
if module == '__main__': |
|
module = sys.argv[0] |
|
|
|
return list(self.flags_by_module_dict().get(module, [])) |
|
|
|
def get_key_flags_for_module(self, module): |
|
"""Returns the list of key flags for a module. |
|
|
|
Args: |
|
module: module|str, the module to get key flags from. |
|
|
|
Returns: |
|
[Flag], a new list of Flag instances. Caller may update this list as |
|
desired: none of those changes will affect the internals of this |
|
FlagValue instance. |
|
""" |
|
if not isinstance(module, str): |
|
module = module.__name__ |
|
if module == '__main__': |
|
module = sys.argv[0] |
|
|
|
|
|
|
|
|
|
key_flags = self.get_flags_for_module(module) |
|
|
|
|
|
for flag in self.key_flags_by_module_dict().get(module, []): |
|
if flag not in key_flags: |
|
key_flags.append(flag) |
|
return key_flags |
|
|
|
def find_module_defining_flag(self, flagname, default=None): |
|
"""Return the name of the module defining this flag, or default. |
|
|
|
Args: |
|
flagname: str, name of the flag to lookup. |
|
default: Value to return if flagname is not defined. Defaults to None. |
|
|
|
Returns: |
|
The name of the module which registered the flag with this name. |
|
If no such module exists (i.e. no flag with this name exists), |
|
we return default. |
|
""" |
|
registered_flag = self._flags().get(flagname) |
|
if registered_flag is None: |
|
return default |
|
for module, flags in self.flags_by_module_dict().items(): |
|
for flag in flags: |
|
|
|
|
|
|
|
if (flag.name == registered_flag.name and |
|
flag.short_name == registered_flag.short_name): |
|
return module |
|
return default |
|
|
|
def find_module_id_defining_flag(self, flagname, default=None): |
|
"""Return the ID of the module defining this flag, or default. |
|
|
|
Args: |
|
flagname: str, name of the flag to lookup. |
|
default: Value to return if flagname is not defined. Defaults to None. |
|
|
|
Returns: |
|
The ID of the module which registered the flag with this name. |
|
If no such module exists (i.e. no flag with this name exists), |
|
we return default. |
|
""" |
|
registered_flag = self._flags().get(flagname) |
|
if registered_flag is None: |
|
return default |
|
for module_id, flags in self.flags_by_module_id_dict().items(): |
|
for flag in flags: |
|
|
|
|
|
|
|
if (flag.name == registered_flag.name and |
|
flag.short_name == registered_flag.short_name): |
|
return module_id |
|
return default |
|
|
|
def _register_unknown_flag_setter(self, setter): |
|
"""Allow set default values for undefined flags. |
|
|
|
Args: |
|
setter: Method(name, value) to call to __setattr__ an unknown flag. Must |
|
raise NameError or ValueError for invalid name/value. |
|
""" |
|
self.__dict__['__set_unknown'] = setter |
|
|
|
def _set_unknown_flag(self, name, value): |
|
"""Returns value if setting flag |name| to |value| returned True. |
|
|
|
Args: |
|
name: str, name of the flag to set. |
|
value: Value to set. |
|
|
|
Returns: |
|
Flag value on successful call. |
|
|
|
Raises: |
|
UnrecognizedFlagError |
|
IllegalFlagValueError |
|
""" |
|
setter = self.__dict__['__set_unknown'] |
|
if setter: |
|
try: |
|
setter(name, value) |
|
return value |
|
except (TypeError, ValueError): |
|
raise _exceptions.IllegalFlagValueError( |
|
'"{1}" is not valid for --{0}'.format(name, value)) |
|
except NameError: |
|
pass |
|
raise _exceptions.UnrecognizedFlagError(name, value) |
|
|
|
def append_flag_values(self, flag_values): |
|
"""Appends flags registered in another FlagValues instance. |
|
|
|
Args: |
|
flag_values: FlagValues, the FlagValues instance from which to copy flags. |
|
""" |
|
for flag_name, flag in flag_values._flags().items(): |
|
|
|
|
|
|
|
|
|
|
|
if flag_name == flag.name: |
|
try: |
|
self[flag_name] = flag |
|
except _exceptions.DuplicateFlagError: |
|
raise _exceptions.DuplicateFlagError.from_flag( |
|
flag_name, self, other_flag_values=flag_values) |
|
|
|
def remove_flag_values(self, flag_values): |
|
"""Remove flags that were previously appended from another FlagValues. |
|
|
|
Args: |
|
flag_values: FlagValues, the FlagValues instance containing flags to |
|
remove. |
|
""" |
|
for flag_name in flag_values: |
|
self.__delattr__(flag_name) |
|
|
|
def __setitem__(self, name, flag): |
|
"""Registers a new flag variable.""" |
|
fl = self._flags() |
|
if not isinstance(flag, _flag.Flag): |
|
raise _exceptions.IllegalFlagValueError( |
|
f'Expect Flag instances, found type {type(flag)}. ' |
|
"Maybe you didn't mean to use FlagValue.__setitem__?") |
|
if not isinstance(name, str): |
|
raise _exceptions.Error('Flag name must be a string') |
|
if not name: |
|
raise _exceptions.Error('Flag name cannot be empty') |
|
if ' ' in name: |
|
raise _exceptions.Error('Flag name cannot contain a space') |
|
self._check_method_name_conflicts(name, flag) |
|
if name in fl and not flag.allow_override and not fl[name].allow_override: |
|
module, module_name = _helpers.get_calling_module_object_and_name() |
|
if (self.find_module_defining_flag(name) == module_name and |
|
id(module) != self.find_module_id_defining_flag(name)): |
|
|
|
|
|
|
|
return |
|
raise _exceptions.DuplicateFlagError.from_flag(name, self) |
|
short_name = flag.short_name |
|
|
|
|
|
flags_to_cleanup = set() |
|
if short_name is not None: |
|
if (short_name in fl and not flag.allow_override and |
|
not fl[short_name].allow_override): |
|
raise _exceptions.DuplicateFlagError.from_flag(short_name, self) |
|
if short_name in fl and fl[short_name] != flag: |
|
flags_to_cleanup.add(fl[short_name]) |
|
fl[short_name] = flag |
|
if (name not in fl |
|
or fl[name].using_default_value or not flag.using_default_value): |
|
if name in fl and fl[name] != flag: |
|
flags_to_cleanup.add(fl[name]) |
|
fl[name] = flag |
|
for f in flags_to_cleanup: |
|
self._cleanup_unregistered_flag_from_module_dicts(f) |
|
|
|
def __dir__(self): |
|
"""Returns list of names of all defined flags. |
|
|
|
Useful for TAB-completion in ipython. |
|
|
|
Returns: |
|
[str], a list of names of all defined flags. |
|
""" |
|
return sorted(self.__dict__['__flags']) |
|
|
|
def __getitem__(self, name): |
|
"""Returns the Flag object for the flag --name.""" |
|
return self._flags()[name] |
|
|
|
def _hide_flag(self, name): |
|
"""Marks the flag --name as hidden.""" |
|
self.__dict__['__hiddenflags'].add(name) |
|
|
|
def __getattr__(self, name): |
|
"""Retrieves the 'value' attribute of the flag --name.""" |
|
fl = self._flags() |
|
if name not in fl: |
|
raise AttributeError(name) |
|
if name in self.__dict__['__hiddenflags']: |
|
raise AttributeError(name) |
|
|
|
if self.__dict__['__flags_parsed'] or fl[name].present: |
|
return fl[name].value |
|
else: |
|
raise _exceptions.UnparsedFlagAccessError( |
|
'Trying to access flag --%s before flags were parsed.' % name) |
|
|
|
def __setattr__(self, name, value): |
|
"""Sets the 'value' attribute of the flag --name.""" |
|
self._set_attributes(**{name: value}) |
|
return value |
|
|
|
def _set_attributes(self, **attributes): |
|
"""Sets multiple flag values together, triggers validators afterwards.""" |
|
fl = self._flags() |
|
known_flags = set() |
|
for name, value in attributes.items(): |
|
if name in self.__dict__['__hiddenflags']: |
|
raise AttributeError(name) |
|
if name in fl: |
|
fl[name].value = value |
|
known_flags.add(name) |
|
else: |
|
self._set_unknown_flag(name, value) |
|
for name in known_flags: |
|
self._assert_validators(fl[name].validators) |
|
fl[name].using_default_value = False |
|
|
|
def validate_all_flags(self): |
|
"""Verifies whether all flags pass validation. |
|
|
|
Raises: |
|
AttributeError: Raised if validators work with a non-existing flag. |
|
IllegalFlagValueError: Raised if validation fails for at least one |
|
validator. |
|
""" |
|
all_validators = set() |
|
for flag in self._flags().values(): |
|
all_validators.update(flag.validators) |
|
self._assert_validators(all_validators) |
|
|
|
def _assert_validators(self, validators): |
|
"""Asserts if all validators in the list are satisfied. |
|
|
|
It asserts validators in the order they were created. |
|
|
|
Args: |
|
validators: Iterable(validators.Validator), validators to be verified. |
|
|
|
Raises: |
|
AttributeError: Raised if validators work with a non-existing flag. |
|
IllegalFlagValueError: Raised if validation fails for at least one |
|
validator. |
|
""" |
|
messages = [] |
|
bad_flags = set() |
|
for validator in sorted( |
|
validators, key=lambda validator: validator.insertion_index): |
|
try: |
|
if isinstance(validator, _validators_classes.SingleFlagValidator): |
|
if validator.flag_name in bad_flags: |
|
continue |
|
elif isinstance(validator, _validators_classes.MultiFlagsValidator): |
|
if bad_flags & set(validator.flag_names): |
|
continue |
|
validator.verify(self) |
|
except _exceptions.ValidationError as e: |
|
if isinstance(validator, _validators_classes.SingleFlagValidator): |
|
bad_flags.add(validator.flag_name) |
|
elif isinstance(validator, _validators_classes.MultiFlagsValidator): |
|
bad_flags.update(set(validator.flag_names)) |
|
message = validator.print_flags_with_values(self) |
|
messages.append('%s: %s' % (message, str(e))) |
|
if messages: |
|
raise _exceptions.IllegalFlagValueError('\n'.join(messages)) |
|
|
|
def __delattr__(self, flag_name): |
|
"""Deletes a previously-defined flag from a flag object. |
|
|
|
This method makes sure we can delete a flag by using |
|
|
|
del FLAGS.<flag_name> |
|
|
|
E.g., |
|
|
|
flags.DEFINE_integer('foo', 1, 'Integer flag.') |
|
del flags.FLAGS.foo |
|
|
|
If a flag is also registered by its the other name (long name or short |
|
name), the other name won't be deleted. |
|
|
|
Args: |
|
flag_name: str, the name of the flag to be deleted. |
|
|
|
Raises: |
|
AttributeError: Raised when there is no registered flag named flag_name. |
|
""" |
|
fl = self._flags() |
|
if flag_name not in fl: |
|
raise AttributeError(flag_name) |
|
|
|
flag_obj = fl[flag_name] |
|
del fl[flag_name] |
|
|
|
self._cleanup_unregistered_flag_from_module_dicts(flag_obj) |
|
|
|
def set_default(self, name, value): |
|
"""Changes the default value of the named flag object. |
|
|
|
The flag's current value is also updated if the flag is currently using |
|
the default value, i.e. not specified in the command line, and not set |
|
by FLAGS.name = value. |
|
|
|
Args: |
|
name: str, the name of the flag to modify. |
|
value: The new default value. |
|
|
|
Raises: |
|
UnrecognizedFlagError: Raised when there is no registered flag named name. |
|
IllegalFlagValueError: Raised when value is not valid. |
|
""" |
|
fl = self._flags() |
|
if name not in fl: |
|
self._set_unknown_flag(name, value) |
|
return |
|
fl[name]._set_default(value) |
|
self._assert_validators(fl[name].validators) |
|
|
|
def __contains__(self, name): |
|
"""Returns True if name is a value (flag) in the dict.""" |
|
return name in self._flags() |
|
|
|
def __len__(self): |
|
return len(self.__dict__['__flags']) |
|
|
|
def __iter__(self): |
|
return iter(self._flags()) |
|
|
|
def __call__(self, argv, known_only=False): |
|
"""Parses flags from argv; stores parsed flags into this FlagValues object. |
|
|
|
All unparsed arguments are returned. |
|
|
|
Args: |
|
argv: a tuple/list of strings. |
|
known_only: bool, if True, parse and remove known flags; return the rest |
|
untouched. Unknown flags specified by --undefok are not returned. |
|
|
|
Returns: |
|
The list of arguments not parsed as options, including argv[0]. |
|
|
|
Raises: |
|
Error: Raised on any parsing error. |
|
TypeError: Raised on passing wrong type of arguments. |
|
ValueError: Raised on flag value parsing error. |
|
""" |
|
if isinstance(argv, (str, bytes)): |
|
raise TypeError( |
|
'argv should be a tuple/list of strings, not bytes or string.') |
|
if not argv: |
|
raise ValueError( |
|
'argv cannot be an empty list, and must contain the program name as ' |
|
'the first element.') |
|
|
|
|
|
program_name = argv[0] |
|
args = self.read_flags_from_files(argv[1:], force_gnu=False) |
|
|
|
|
|
unknown_flags, unparsed_args = self._parse_args(args, known_only) |
|
|
|
|
|
|
|
for name, value in unknown_flags: |
|
suggestions = _helpers.get_flag_suggestions(name, list(self)) |
|
raise _exceptions.UnrecognizedFlagError( |
|
name, value, suggestions=suggestions) |
|
|
|
self.mark_as_parsed() |
|
self.validate_all_flags() |
|
return [program_name] + unparsed_args |
|
|
|
def __getstate__(self): |
|
raise TypeError("can't pickle FlagValues") |
|
|
|
def __copy__(self): |
|
raise TypeError('FlagValues does not support shallow copies. ' |
|
'Use absl.testing.flagsaver or copy.deepcopy instead.') |
|
|
|
def __deepcopy__(self, memo): |
|
result = object.__new__(type(self)) |
|
result.__dict__.update(copy.deepcopy(self.__dict__, memo)) |
|
return result |
|
|
|
def _set_is_retired_flag_func(self, is_retired_flag_func): |
|
"""Sets a function for checking retired flags. |
|
|
|
Do not use it. This is a private absl API used to check retired flags |
|
registered by the absl C++ flags library. |
|
|
|
Args: |
|
is_retired_flag_func: Callable(str) -> (bool, bool), a function takes flag |
|
name as parameter, returns a tuple (is_retired, type_is_bool). |
|
""" |
|
self.__dict__['__is_retired_flag_func'] = is_retired_flag_func |
|
|
|
def _parse_args(self, args, known_only): |
|
"""Helper function to do the main argument parsing. |
|
|
|
This function goes through args and does the bulk of the flag parsing. |
|
It will find the corresponding flag in our flag dictionary, and call its |
|
.parse() method on the flag value. |
|
|
|
Args: |
|
args: [str], a list of strings with the arguments to parse. |
|
known_only: bool, if True, parse and remove known flags; return the rest |
|
untouched. Unknown flags specified by --undefok are not returned. |
|
|
|
Returns: |
|
A tuple with the following: |
|
unknown_flags: List of (flag name, arg) for flags we don't know about. |
|
unparsed_args: List of arguments we did not parse. |
|
|
|
Raises: |
|
Error: Raised on any parsing error. |
|
ValueError: Raised on flag value parsing error. |
|
""" |
|
unparsed_names_and_args = [] |
|
undefok = set() |
|
retired_flag_func = self.__dict__['__is_retired_flag_func'] |
|
|
|
flag_dict = self._flags() |
|
args = iter(args) |
|
for arg in args: |
|
value = None |
|
|
|
def get_value(): |
|
|
|
try: |
|
return next(args) if value is None else value |
|
except StopIteration: |
|
raise _exceptions.Error('Missing value for flag ' + arg) |
|
|
|
if not arg.startswith('-'): |
|
|
|
unparsed_names_and_args.append((None, arg)) |
|
if self.is_gnu_getopt(): |
|
continue |
|
else: |
|
break |
|
|
|
if arg == '--': |
|
if known_only: |
|
unparsed_names_and_args.append((None, arg)) |
|
break |
|
|
|
|
|
if arg.startswith('--'): |
|
arg_without_dashes = arg[2:] |
|
else: |
|
arg_without_dashes = arg[1:] |
|
|
|
if '=' in arg_without_dashes: |
|
name, value = arg_without_dashes.split('=', 1) |
|
else: |
|
name, value = arg_without_dashes, None |
|
|
|
if not name: |
|
|
|
unparsed_names_and_args.append((None, arg)) |
|
if self.is_gnu_getopt(): |
|
continue |
|
else: |
|
break |
|
|
|
|
|
if name == 'undefok': |
|
value = get_value() |
|
undefok.update(v.strip() for v in value.split(',')) |
|
undefok.update('no' + v.strip() for v in value.split(',')) |
|
continue |
|
|
|
flag = flag_dict.get(name) |
|
if flag is not None: |
|
if flag.boolean and value is None: |
|
value = 'true' |
|
else: |
|
value = get_value() |
|
elif name.startswith('no') and len(name) > 2: |
|
|
|
noflag = flag_dict.get(name[2:]) |
|
if noflag is not None and noflag.boolean: |
|
if value is not None: |
|
raise ValueError(arg + ' does not take an argument') |
|
flag = noflag |
|
value = 'false' |
|
|
|
if retired_flag_func and flag is None: |
|
is_retired, is_bool = retired_flag_func(name) |
|
|
|
|
|
|
|
|
|
if not is_retired and name.startswith('no'): |
|
is_retired, is_bool = retired_flag_func(name[2:]) |
|
is_retired = is_retired and is_bool |
|
|
|
if is_retired: |
|
if not is_bool and value is None: |
|
|
|
|
|
get_value() |
|
logging.error( |
|
'Flag "%s" is retired and should no longer ' |
|
'be specified. See go/totw/90.', name) |
|
continue |
|
|
|
if flag is not None: |
|
|
|
flag.parse(value) |
|
flag.using_default_value = False |
|
|
|
else: |
|
unparsed_names_and_args.append((name, arg)) |
|
|
|
unknown_flags = [] |
|
unparsed_args = [] |
|
for name, arg in unparsed_names_and_args: |
|
if name is None: |
|
|
|
unparsed_args.append(arg) |
|
elif name in undefok: |
|
|
|
continue |
|
else: |
|
|
|
if known_only: |
|
unparsed_args.append(arg) |
|
else: |
|
unknown_flags.append((name, arg)) |
|
|
|
unparsed_args.extend(list(args)) |
|
return unknown_flags, unparsed_args |
|
|
|
def is_parsed(self): |
|
"""Returns whether flags were parsed.""" |
|
return self.__dict__['__flags_parsed'] |
|
|
|
def mark_as_parsed(self): |
|
"""Explicitly marks flags as parsed. |
|
|
|
Use this when the caller knows that this FlagValues has been parsed as if |
|
a ``__call__()`` invocation has happened. This is only a public method for |
|
use by things like appcommands which do additional command like parsing. |
|
""" |
|
self.__dict__['__flags_parsed'] = True |
|
|
|
def unparse_flags(self): |
|
"""Unparses all flags to the point before any FLAGS(argv) was called.""" |
|
for f in self._flags().values(): |
|
f.unparse() |
|
|
|
|
|
logging.info('unparse_flags() called; flags access will now raise errors.') |
|
self.__dict__['__flags_parsed'] = False |
|
self.__dict__['__unparse_flags_called'] = True |
|
|
|
def flag_values_dict(self): |
|
"""Returns a dictionary that maps flag names to flag values.""" |
|
return {name: flag.value for name, flag in self._flags().items()} |
|
|
|
def __str__(self): |
|
"""Returns a help string for all known flags.""" |
|
return self.get_help() |
|
|
|
def get_help(self, prefix='', include_special_flags=True): |
|
"""Returns a help string for all known flags. |
|
|
|
Args: |
|
prefix: str, per-line output prefix. |
|
include_special_flags: bool, whether to include description of |
|
SPECIAL_FLAGS, i.e. --flagfile and --undefok. |
|
|
|
Returns: |
|
str, formatted help message. |
|
""" |
|
flags_by_module = self.flags_by_module_dict() |
|
if flags_by_module: |
|
modules = sorted(flags_by_module) |
|
|
|
main_module = sys.argv[0] |
|
if main_module in modules: |
|
modules.remove(main_module) |
|
modules = [main_module] + modules |
|
return self._get_help_for_modules(modules, prefix, include_special_flags) |
|
else: |
|
output_lines = [] |
|
|
|
values = self._flags().values() |
|
if include_special_flags: |
|
values = itertools.chain( |
|
values, _helpers.SPECIAL_FLAGS._flags().values()) |
|
self._render_flag_list(values, output_lines, prefix) |
|
return '\n'.join(output_lines) |
|
|
|
def _get_help_for_modules(self, modules, prefix, include_special_flags): |
|
"""Returns the help string for a list of modules. |
|
|
|
Private to absl.flags package. |
|
|
|
Args: |
|
modules: List[str], a list of modules to get the help string for. |
|
prefix: str, a string that is prepended to each generated help line. |
|
include_special_flags: bool, whether to include description of |
|
SPECIAL_FLAGS, i.e. --flagfile and --undefok. |
|
""" |
|
output_lines = [] |
|
for module in modules: |
|
self._render_our_module_flags(module, output_lines, prefix) |
|
if include_special_flags: |
|
self._render_module_flags( |
|
'absl.flags', |
|
_helpers.SPECIAL_FLAGS._flags().values(), |
|
output_lines, |
|
prefix) |
|
return '\n'.join(output_lines) |
|
|
|
def _render_module_flags(self, module, flags, output_lines, prefix=''): |
|
"""Returns a help string for a given module.""" |
|
if not isinstance(module, str): |
|
module = module.__name__ |
|
output_lines.append('\n%s%s:' % (prefix, module)) |
|
self._render_flag_list(flags, output_lines, prefix + ' ') |
|
|
|
def _render_our_module_flags(self, module, output_lines, prefix=''): |
|
"""Returns a help string for a given module.""" |
|
flags = self.get_flags_for_module(module) |
|
if flags: |
|
self._render_module_flags(module, flags, output_lines, prefix) |
|
|
|
def _render_our_module_key_flags(self, module, output_lines, prefix=''): |
|
"""Returns a help string for the key flags of a given module. |
|
|
|
Args: |
|
module: module|str, the module to render key flags for. |
|
output_lines: [str], a list of strings. The generated help message lines |
|
will be appended to this list. |
|
prefix: str, a string that is prepended to each generated help line. |
|
""" |
|
key_flags = self.get_key_flags_for_module(module) |
|
if key_flags: |
|
self._render_module_flags(module, key_flags, output_lines, prefix) |
|
|
|
def module_help(self, module): |
|
"""Describes the key flags of a module. |
|
|
|
Args: |
|
module: module|str, the module to describe the key flags for. |
|
|
|
Returns: |
|
str, describing the key flags of a module. |
|
""" |
|
helplist = [] |
|
self._render_our_module_key_flags(module, helplist) |
|
return '\n'.join(helplist) |
|
|
|
def main_module_help(self): |
|
"""Describes the key flags of the main module. |
|
|
|
Returns: |
|
str, describing the key flags of the main module. |
|
""" |
|
return self.module_help(sys.argv[0]) |
|
|
|
def _render_flag_list(self, flaglist, output_lines, prefix=' '): |
|
fl = self._flags() |
|
special_fl = _helpers.SPECIAL_FLAGS._flags() |
|
flaglist = [(flag.name, flag) for flag in flaglist] |
|
flaglist.sort() |
|
flagset = {} |
|
for (name, flag) in flaglist: |
|
|
|
|
|
|
|
if fl.get(name, None) != flag and special_fl.get(name, None) != flag: |
|
|
|
continue |
|
|
|
if flag in flagset: |
|
continue |
|
flagset[flag] = 1 |
|
flaghelp = '' |
|
if flag.short_name: |
|
flaghelp += '-%s,' % flag.short_name |
|
if flag.boolean: |
|
flaghelp += '--[no]%s:' % flag.name |
|
else: |
|
flaghelp += '--%s:' % flag.name |
|
flaghelp += ' ' |
|
if flag.help: |
|
flaghelp += flag.help |
|
flaghelp = _helpers.text_wrap( |
|
flaghelp, indent=prefix + ' ', firstline_indent=prefix) |
|
if flag.default_as_str: |
|
flaghelp += '\n' |
|
flaghelp += _helpers.text_wrap( |
|
'(default: %s)' % flag.default_as_str, indent=prefix + ' ') |
|
if flag.parser.syntactic_help: |
|
flaghelp += '\n' |
|
flaghelp += _helpers.text_wrap( |
|
'(%s)' % flag.parser.syntactic_help, indent=prefix + ' ') |
|
output_lines.append(flaghelp) |
|
|
|
def get_flag_value(self, name, default): |
|
"""Returns the value of a flag (if not None) or a default value. |
|
|
|
Args: |
|
name: str, the name of a flag. |
|
default: Default value to use if the flag value is None. |
|
|
|
Returns: |
|
Requested flag value or default. |
|
""" |
|
|
|
value = self.__getattr__(name) |
|
if value is not None: |
|
return value |
|
else: |
|
return default |
|
|
|
def _is_flag_file_directive(self, flag_string): |
|
"""Checks whether flag_string contain a --flagfile=<foo> directive.""" |
|
if isinstance(flag_string, str): |
|
if flag_string.startswith('--flagfile='): |
|
return 1 |
|
elif flag_string == '--flagfile': |
|
return 1 |
|
elif flag_string.startswith('-flagfile='): |
|
return 1 |
|
elif flag_string == '-flagfile': |
|
return 1 |
|
else: |
|
return 0 |
|
return 0 |
|
|
|
def _extract_filename(self, flagfile_str): |
|
"""Returns filename from a flagfile_str of form -[-]flagfile=filename. |
|
|
|
The cases of --flagfile foo and -flagfile foo shouldn't be hitting |
|
this function, as they are dealt with in the level above this |
|
function. |
|
|
|
Args: |
|
flagfile_str: str, the flagfile string. |
|
|
|
Returns: |
|
str, the filename from a flagfile_str of form -[-]flagfile=filename. |
|
|
|
Raises: |
|
Error: Raised when illegal --flagfile is provided. |
|
""" |
|
if flagfile_str.startswith('--flagfile='): |
|
return os.path.expanduser((flagfile_str[(len('--flagfile=')):]).strip()) |
|
elif flagfile_str.startswith('-flagfile='): |
|
return os.path.expanduser((flagfile_str[(len('-flagfile=')):]).strip()) |
|
else: |
|
raise _exceptions.Error('Hit illegal --flagfile type: %s' % flagfile_str) |
|
|
|
def _get_flag_file_lines(self, filename, parsed_file_stack=None): |
|
"""Returns the useful (!=comments, etc) lines from a file with flags. |
|
|
|
Args: |
|
filename: str, the name of the flag file. |
|
parsed_file_stack: [str], a list of the names of the files that we have |
|
recursively encountered at the current depth. MUTATED BY THIS FUNCTION |
|
(but the original value is preserved upon successfully returning from |
|
function call). |
|
|
|
Returns: |
|
List of strings. See the note below. |
|
|
|
NOTE(springer): This function checks for a nested --flagfile=<foo> |
|
tag and handles the lower file recursively. It returns a list of |
|
all the lines that _could_ contain command flags. This is |
|
EVERYTHING except whitespace lines and comments (lines starting |
|
with '#' or '//'). |
|
""" |
|
|
|
if not filename: |
|
return [] |
|
if parsed_file_stack is None: |
|
parsed_file_stack = [] |
|
|
|
|
|
if filename in parsed_file_stack: |
|
sys.stderr.write('Warning: Hit circular flagfile dependency. Ignoring' |
|
' flagfile: %s\n' % (filename,)) |
|
return [] |
|
else: |
|
parsed_file_stack.append(filename) |
|
|
|
line_list = [] |
|
flag_line_list = [] |
|
try: |
|
file_obj = open(filename, 'r') |
|
except IOError as e_msg: |
|
raise _exceptions.CantOpenFlagFileError( |
|
'ERROR:: Unable to open flagfile: %s' % e_msg) |
|
|
|
with file_obj: |
|
line_list = file_obj.readlines() |
|
|
|
|
|
for line in line_list: |
|
if line.isspace(): |
|
pass |
|
|
|
elif line.startswith('#') or line.startswith('//'): |
|
pass |
|
|
|
|
|
elif self._is_flag_file_directive(line): |
|
sub_filename = self._extract_filename(line) |
|
included_flags = self._get_flag_file_lines( |
|
sub_filename, parsed_file_stack=parsed_file_stack) |
|
flag_line_list.extend(included_flags) |
|
else: |
|
|
|
|
|
|
|
flag_line_list.append(line.strip()) |
|
|
|
parsed_file_stack.pop() |
|
return flag_line_list |
|
|
|
def read_flags_from_files(self, argv, force_gnu=True): |
|
"""Processes command line args, but also allow args to be read from file. |
|
|
|
Args: |
|
argv: [str], a list of strings, usually sys.argv[1:], which may contain |
|
one or more flagfile directives of the form --flagfile="./filename". |
|
Note that the name of the program (sys.argv[0]) should be omitted. |
|
force_gnu: bool, if False, --flagfile parsing obeys the |
|
FLAGS.is_gnu_getopt() value. If True, ignore the value and always follow |
|
gnu_getopt semantics. |
|
|
|
Returns: |
|
A new list which has the original list combined with what we read |
|
from any flagfile(s). |
|
|
|
Raises: |
|
IllegalFlagValueError: Raised when --flagfile is provided with no |
|
argument. |
|
|
|
This function is called by FLAGS(argv). |
|
It scans the input list for a flag that looks like: |
|
--flagfile=<somefile>. Then it opens <somefile>, reads all valid key |
|
and value pairs and inserts them into the input list in exactly the |
|
place where the --flagfile arg is found. |
|
|
|
Note that your application's flags are still defined the usual way |
|
using absl.flags DEFINE_flag() type functions. |
|
|
|
Notes (assuming we're getting a commandline of some sort as our input): |
|
|
|
* For duplicate flags, the last one we hit should "win". |
|
* Since flags that appear later win, a flagfile's settings can be "weak" |
|
if the --flagfile comes at the beginning of the argument sequence, |
|
and it can be "strong" if the --flagfile comes at the end. |
|
* A further "--flagfile=<otherfile.cfg>" CAN be nested in a flagfile. |
|
It will be expanded in exactly the spot where it is found. |
|
* In a flagfile, a line beginning with # or // is a comment. |
|
* Entirely blank lines _should_ be ignored. |
|
""" |
|
rest_of_args = argv |
|
new_argv = [] |
|
while rest_of_args: |
|
current_arg = rest_of_args[0] |
|
rest_of_args = rest_of_args[1:] |
|
if self._is_flag_file_directive(current_arg): |
|
|
|
|
|
if current_arg == '--flagfile' or current_arg == '-flagfile': |
|
if not rest_of_args: |
|
raise _exceptions.IllegalFlagValueError( |
|
'--flagfile with no argument') |
|
flag_filename = os.path.expanduser(rest_of_args[0]) |
|
rest_of_args = rest_of_args[1:] |
|
else: |
|
|
|
flag_filename = self._extract_filename(current_arg) |
|
new_argv.extend(self._get_flag_file_lines(flag_filename)) |
|
else: |
|
new_argv.append(current_arg) |
|
|
|
if current_arg == '--': |
|
break |
|
|
|
if not current_arg.startswith('-'): |
|
if not force_gnu and not self.__dict__['__use_gnu_getopt']: |
|
break |
|
else: |
|
if ('=' not in current_arg and rest_of_args and |
|
not rest_of_args[0].startswith('-')): |
|
|
|
|
|
fl = self._flags() |
|
name = current_arg.lstrip('-') |
|
if name in fl and not fl[name].boolean: |
|
current_arg = rest_of_args[0] |
|
rest_of_args = rest_of_args[1:] |
|
new_argv.append(current_arg) |
|
|
|
if rest_of_args: |
|
new_argv.extend(rest_of_args) |
|
|
|
return new_argv |
|
|
|
def flags_into_string(self): |
|
"""Returns a string with the flags assignments from this FlagValues object. |
|
|
|
This function ignores flags whose value is None. Each flag |
|
assignment is separated by a newline. |
|
|
|
NOTE: MUST mirror the behavior of the C++ CommandlineFlagsIntoString |
|
from https://github.com/gflags/gflags. |
|
|
|
Returns: |
|
str, the string with the flags assignments from this FlagValues object. |
|
The flags are ordered by (module_name, flag_name). |
|
""" |
|
module_flags = sorted(self.flags_by_module_dict().items()) |
|
s = '' |
|
for unused_module_name, flags in module_flags: |
|
flags = sorted(flags, key=lambda f: f.name) |
|
for flag in flags: |
|
if flag.value is not None: |
|
s += flag.serialize() + '\n' |
|
return s |
|
|
|
def append_flags_into_file(self, filename): |
|
"""Appends all flags assignments from this FlagInfo object to a file. |
|
|
|
Output will be in the format of a flagfile. |
|
|
|
NOTE: MUST mirror the behavior of the C++ AppendFlagsIntoFile |
|
from https://github.com/gflags/gflags. |
|
|
|
Args: |
|
filename: str, name of the file. |
|
""" |
|
with open(filename, 'a') as out_file: |
|
out_file.write(self.flags_into_string()) |
|
|
|
def write_help_in_xml_format(self, outfile=None): |
|
"""Outputs flag documentation in XML format. |
|
|
|
NOTE: We use element names that are consistent with those used by |
|
the C++ command-line flag library, from |
|
https://github.com/gflags/gflags. |
|
We also use a few new elements (e.g., <key>), but we do not |
|
interfere / overlap with existing XML elements used by the C++ |
|
library. Please maintain this consistency. |
|
|
|
Args: |
|
outfile: File object we write to. Default None means sys.stdout. |
|
""" |
|
doc = minidom.Document() |
|
all_flag = doc.createElement('AllFlags') |
|
doc.appendChild(all_flag) |
|
|
|
all_flag.appendChild( |
|
_helpers.create_xml_dom_element(doc, 'program', |
|
os.path.basename(sys.argv[0]))) |
|
|
|
usage_doc = sys.modules['__main__'].__doc__ |
|
if not usage_doc: |
|
usage_doc = '\nUSAGE: %s [flags]\n' % sys.argv[0] |
|
else: |
|
usage_doc = usage_doc.replace('%s', sys.argv[0]) |
|
all_flag.appendChild( |
|
_helpers.create_xml_dom_element(doc, 'usage', usage_doc)) |
|
|
|
|
|
key_flags = self.get_key_flags_for_module(sys.argv[0]) |
|
|
|
|
|
flags_by_module = self.flags_by_module_dict() |
|
all_module_names = list(flags_by_module.keys()) |
|
all_module_names.sort() |
|
for module_name in all_module_names: |
|
flag_list = [(f.name, f) for f in flags_by_module[module_name]] |
|
flag_list.sort() |
|
for unused_flag_name, flag in flag_list: |
|
is_key = flag in key_flags |
|
all_flag.appendChild( |
|
flag._create_xml_dom_element( |
|
doc, |
|
module_name, |
|
is_key=is_key)) |
|
|
|
outfile = outfile or sys.stdout |
|
outfile.write( |
|
doc.toprettyxml(indent=' ', encoding='utf-8').decode('utf-8')) |
|
outfile.flush() |
|
|
|
def _check_method_name_conflicts(self, name, flag): |
|
if flag.allow_using_method_names: |
|
return |
|
short_name = flag.short_name |
|
flag_names = {name} if short_name is None else {name, short_name} |
|
for flag_name in flag_names: |
|
if flag_name in self.__dict__['__banned_flag_names']: |
|
raise _exceptions.FlagNameConflictsWithMethodError( |
|
'Cannot define a flag named "{name}". It conflicts with a method ' |
|
'on class "{class_name}". To allow defining it, use ' |
|
'allow_using_method_names and access the flag value with ' |
|
"FLAGS['{name}'].value. FLAGS.{name} returns the method, " |
|
'not the flag value.'.format( |
|
name=flag_name, class_name=type(self).__name__)) |
|
|
|
|
|
FLAGS = FlagValues() |
|
|
|
|
|
class FlagHolder(Generic[_T]): |
|
"""Holds a defined flag. |
|
|
|
This facilitates a cleaner api around global state. Instead of:: |
|
|
|
flags.DEFINE_integer('foo', ...) |
|
flags.DEFINE_integer('bar', ...) |
|
|
|
def method(): |
|
# prints parsed value of 'bar' flag |
|
print(flags.FLAGS.foo) |
|
# runtime error due to typo or possibly bad coding style. |
|
print(flags.FLAGS.baz) |
|
|
|
it encourages code like:: |
|
|
|
_FOO_FLAG = flags.DEFINE_integer('foo', ...) |
|
_BAR_FLAG = flags.DEFINE_integer('bar', ...) |
|
|
|
def method(): |
|
print(_FOO_FLAG.value) |
|
print(_BAR_FLAG.value) |
|
|
|
since the name of the flag appears only once in the source code. |
|
""" |
|
|
|
def __init__(self, flag_values, flag, ensure_non_none_value=False): |
|
"""Constructs a FlagHolder instance providing typesafe access to flag. |
|
|
|
Args: |
|
flag_values: The container the flag is registered to. |
|
flag: The flag object for this flag. |
|
ensure_non_none_value: Is the value of the flag allowed to be None. |
|
""" |
|
self._flagvalues = flag_values |
|
|
|
|
|
|
|
|
|
|
|
self._name = flag.name |
|
|
|
|
|
self._ensure_non_none_value = ensure_non_none_value |
|
|
|
def __eq__(self, other): |
|
raise TypeError( |
|
"unsupported operand type(s) for ==: '{0}' and '{1}' " |
|
"(did you mean to use '{0}.value' instead?)".format( |
|
type(self).__name__, type(other).__name__)) |
|
|
|
def __bool__(self): |
|
raise TypeError( |
|
"bool() not supported for instances of type '{0}' " |
|
"(did you mean to use '{0}.value' instead?)".format( |
|
type(self).__name__)) |
|
|
|
__nonzero__ = __bool__ |
|
|
|
@property |
|
def name(self): |
|
return self._name |
|
|
|
@property |
|
def value(self): |
|
"""Returns the value of the flag. |
|
|
|
If ``_ensure_non_none_value`` is ``True``, then return value is not |
|
``None``. |
|
|
|
Raises: |
|
UnparsedFlagAccessError: if flag parsing has not finished. |
|
IllegalFlagValueError: if value is None unexpectedly. |
|
""" |
|
val = getattr(self._flagvalues, self._name) |
|
if self._ensure_non_none_value and val is None: |
|
raise _exceptions.IllegalFlagValueError( |
|
'Unexpected None value for flag %s' % self._name) |
|
return val |
|
|
|
@property |
|
def default(self): |
|
"""Returns the default value of the flag.""" |
|
return self._flagvalues[self._name].default |
|
|
|
@property |
|
def present(self): |
|
"""Returns True if the flag was parsed from command-line flags.""" |
|
return bool(self._flagvalues[self._name].present) |
|
|
|
|
|
def resolve_flag_ref(flag_ref, flag_values): |
|
"""Helper to validate and resolve a flag reference argument.""" |
|
if isinstance(flag_ref, FlagHolder): |
|
new_flag_values = flag_ref._flagvalues |
|
if flag_values != FLAGS and flag_values != new_flag_values: |
|
raise ValueError( |
|
'flag_values must not be customized when operating on a FlagHolder') |
|
return flag_ref.name, new_flag_values |
|
return flag_ref, flag_values |
|
|
|
|
|
def resolve_flag_refs(flag_refs, flag_values): |
|
"""Helper to validate and resolve flag reference list arguments.""" |
|
fv = None |
|
names = [] |
|
for ref in flag_refs: |
|
if isinstance(ref, FlagHolder): |
|
newfv = ref._flagvalues |
|
name = ref.name |
|
else: |
|
newfv = flag_values |
|
name = ref |
|
if fv and fv != newfv: |
|
raise ValueError( |
|
'multiple FlagValues instances used in invocation. ' |
|
'FlagHolders must be registered to the same FlagValues instance as ' |
|
'do flag names, if provided.') |
|
fv = newfv |
|
names.append(name) |
|
return names, fv |
|
|