|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Contains Flag class - information about single command-line flag. |
|
|
|
Do NOT import this module directly. Import the flags package and use the |
|
aliases defined at the package level instead. |
|
""" |
|
|
|
from collections import abc |
|
import copy |
|
import functools |
|
|
|
from absl.flags import _argument_parser |
|
from absl.flags import _exceptions |
|
from absl.flags import _helpers |
|
|
|
|
|
@functools.total_ordering |
|
class Flag(object): |
|
"""Information about a command-line flag. |
|
|
|
Attributes: |
|
name: the name for this flag |
|
default: the default value for this flag |
|
default_unparsed: the unparsed default value for this flag. |
|
default_as_str: default value as repr'd string, e.g., "'true'" |
|
(or None) |
|
value: the most recent parsed value of this flag set by :meth:`parse` |
|
help: a help string or None if no help is available |
|
short_name: the single letter alias for this flag (or None) |
|
boolean: if 'true', this flag does not accept arguments |
|
present: true if this flag was parsed from command line flags |
|
parser: an :class:`~absl.flags.ArgumentParser` object |
|
serializer: an ArgumentSerializer object |
|
allow_override: the flag may be redefined without raising an error, |
|
and newly defined flag overrides the old one. |
|
allow_override_cpp: use the flag from C++ if available the flag |
|
definition is replaced by the C++ flag after init |
|
allow_hide_cpp: use the Python flag despite having a C++ flag with |
|
the same name (ignore the C++ flag) |
|
using_default_value: the flag value has not been set by user |
|
allow_overwrite: the flag may be parsed more than once without |
|
raising an error, the last set value will be used |
|
allow_using_method_names: whether this flag can be defined even if |
|
it has a name that conflicts with a FlagValues method. |
|
validators: list of the flag validators. |
|
|
|
The only public method of a ``Flag`` object is :meth:`parse`, but it is |
|
typically only called by a :class:`~absl.flags.FlagValues` object. The |
|
:meth:`parse` method is a thin wrapper around the |
|
:meth:`ArgumentParser.parse()<absl.flags.ArgumentParser.parse>` method. The |
|
parsed value is saved in ``.value``, and the ``.present`` attribute is |
|
updated. If this flag was already present, an Error is raised. |
|
|
|
:meth:`parse` is also called during ``__init__`` to parse the default value |
|
and initialize the ``.value`` attribute. This enables other python modules to |
|
safely use flags even if the ``__main__`` module neglects to parse the |
|
command line arguments. The ``.present`` attribute is cleared after |
|
``__init__`` parsing. If the default value is set to ``None``, then the |
|
``__init__`` parsing step is skipped and the ``.value`` attribute is |
|
initialized to None. |
|
|
|
Note: The default value is also presented to the user in the help |
|
string, so it is important that it be a legal value for this flag. |
|
""" |
|
|
|
def __init__(self, parser, serializer, name, default, help_string, |
|
short_name=None, boolean=False, allow_override=False, |
|
allow_override_cpp=False, allow_hide_cpp=False, |
|
allow_overwrite=True, allow_using_method_names=False): |
|
self.name = name |
|
|
|
if not help_string: |
|
help_string = '(no help available)' |
|
|
|
self.help = help_string |
|
self.short_name = short_name |
|
self.boolean = boolean |
|
self.present = 0 |
|
self.parser = parser |
|
self.serializer = serializer |
|
self.allow_override = allow_override |
|
self.allow_override_cpp = allow_override_cpp |
|
self.allow_hide_cpp = allow_hide_cpp |
|
self.allow_overwrite = allow_overwrite |
|
self.allow_using_method_names = allow_using_method_names |
|
|
|
self.using_default_value = True |
|
self._value = None |
|
self.validators = [] |
|
if self.allow_hide_cpp and self.allow_override_cpp: |
|
raise _exceptions.Error( |
|
"Can't have both allow_hide_cpp (means use Python flag) and " |
|
'allow_override_cpp (means use C++ flag after InitGoogle)') |
|
|
|
self._set_default(default) |
|
|
|
@property |
|
def value(self): |
|
return self._value |
|
|
|
@value.setter |
|
def value(self, value): |
|
self._value = value |
|
|
|
def __hash__(self): |
|
return hash(id(self)) |
|
|
|
def __eq__(self, other): |
|
return self is other |
|
|
|
def __lt__(self, other): |
|
if isinstance(other, Flag): |
|
return id(self) < id(other) |
|
return NotImplemented |
|
|
|
def __bool__(self): |
|
raise TypeError('A Flag instance would always be True. ' |
|
'Did you mean to test the `.value` attribute?') |
|
|
|
def __getstate__(self): |
|
raise TypeError("can't pickle Flag objects") |
|
|
|
def __copy__(self): |
|
raise TypeError('%s does not support shallow copies. ' |
|
'Use copy.deepcopy instead.' % type(self).__name__) |
|
|
|
def __deepcopy__(self, memo): |
|
result = object.__new__(type(self)) |
|
result.__dict__ = copy.deepcopy(self.__dict__, memo) |
|
return result |
|
|
|
def _get_parsed_value_as_string(self, value): |
|
"""Returns parsed flag value as string.""" |
|
if value is None: |
|
return None |
|
if self.serializer: |
|
return repr(self.serializer.serialize(value)) |
|
if self.boolean: |
|
if value: |
|
return repr('true') |
|
else: |
|
return repr('false') |
|
return repr(str(value)) |
|
|
|
def parse(self, argument): |
|
"""Parses string and sets flag value. |
|
|
|
Args: |
|
argument: str or the correct flag value type, argument to be parsed. |
|
""" |
|
if self.present and not self.allow_overwrite: |
|
raise _exceptions.IllegalFlagValueError( |
|
'flag --%s=%s: already defined as %s' % ( |
|
self.name, argument, self.value)) |
|
self.value = self._parse(argument) |
|
self.present += 1 |
|
|
|
def _parse(self, argument): |
|
"""Internal parse function. |
|
|
|
It returns the parsed value, and does not modify class states. |
|
|
|
Args: |
|
argument: str or the correct flag value type, argument to be parsed. |
|
|
|
Returns: |
|
The parsed value. |
|
""" |
|
try: |
|
return self.parser.parse(argument) |
|
except (TypeError, ValueError) as e: |
|
raise _exceptions.IllegalFlagValueError( |
|
'flag --%s=%s: %s' % (self.name, argument, e)) |
|
|
|
def unparse(self): |
|
self.value = self.default |
|
self.using_default_value = True |
|
self.present = 0 |
|
|
|
def serialize(self): |
|
"""Serializes the flag.""" |
|
return self._serialize(self.value) |
|
|
|
def _serialize(self, value): |
|
"""Internal serialize function.""" |
|
if value is None: |
|
return '' |
|
if self.boolean: |
|
if value: |
|
return '--%s' % self.name |
|
else: |
|
return '--no%s' % self.name |
|
else: |
|
if not self.serializer: |
|
raise _exceptions.Error( |
|
'Serializer not present for flag %s' % self.name) |
|
return '--%s=%s' % (self.name, self.serializer.serialize(value)) |
|
|
|
def _set_default(self, value): |
|
"""Changes the default value (and current value too) for this Flag.""" |
|
self.default_unparsed = value |
|
if value is None: |
|
self.default = None |
|
else: |
|
self.default = self._parse_from_default(value) |
|
self.default_as_str = self._get_parsed_value_as_string(self.default) |
|
if self.using_default_value: |
|
self.value = self.default |
|
|
|
|
|
|
|
def _parse_from_default(self, value): |
|
return self._parse(value) |
|
|
|
def flag_type(self): |
|
"""Returns a str that describes the type of the flag. |
|
|
|
NOTE: we use strings, and not the types.*Type constants because |
|
our flags can have more exotic types, e.g., 'comma separated list |
|
of strings', 'whitespace separated list of strings', etc. |
|
""" |
|
return self.parser.flag_type() |
|
|
|
def _create_xml_dom_element(self, doc, module_name, is_key=False): |
|
"""Returns an XML element that contains this flag's information. |
|
|
|
This is information that is relevant to all flags (e.g., name, |
|
meaning, etc.). If you defined a flag that has some other pieces of |
|
info, then please override _ExtraXMLInfo. |
|
|
|
Please do NOT override this method. |
|
|
|
Args: |
|
doc: minidom.Document, the DOM document it should create nodes from. |
|
module_name: str,, the name of the module that defines this flag. |
|
is_key: boolean, True iff this flag is key for main module. |
|
|
|
Returns: |
|
A minidom.Element instance. |
|
""" |
|
element = doc.createElement('flag') |
|
if is_key: |
|
element.appendChild(_helpers.create_xml_dom_element(doc, 'key', 'yes')) |
|
element.appendChild(_helpers.create_xml_dom_element( |
|
doc, 'file', module_name)) |
|
|
|
element.appendChild(_helpers.create_xml_dom_element(doc, 'name', self.name)) |
|
if self.short_name: |
|
element.appendChild(_helpers.create_xml_dom_element( |
|
doc, 'short_name', self.short_name)) |
|
if self.help: |
|
element.appendChild(_helpers.create_xml_dom_element( |
|
doc, 'meaning', self.help)) |
|
|
|
|
|
|
|
if self.serializer and not isinstance(self.default, str): |
|
if self.default is not None: |
|
default_serialized = self.serializer.serialize(self.default) |
|
else: |
|
default_serialized = '' |
|
else: |
|
default_serialized = self.default |
|
element.appendChild(_helpers.create_xml_dom_element( |
|
doc, 'default', default_serialized)) |
|
value_serialized = self._serialize_value_for_xml(self.value) |
|
element.appendChild(_helpers.create_xml_dom_element( |
|
doc, 'current', value_serialized)) |
|
element.appendChild(_helpers.create_xml_dom_element( |
|
doc, 'type', self.flag_type())) |
|
|
|
for e in self._extra_xml_dom_elements(doc): |
|
element.appendChild(e) |
|
return element |
|
|
|
def _serialize_value_for_xml(self, value): |
|
"""Returns the serialized value, for use in an XML help text.""" |
|
return value |
|
|
|
def _extra_xml_dom_elements(self, doc): |
|
"""Returns extra info about this flag in XML. |
|
|
|
"Extra" means "not already included by _create_xml_dom_element above." |
|
|
|
Args: |
|
doc: minidom.Document, the DOM document it should create nodes from. |
|
|
|
Returns: |
|
A list of minidom.Element. |
|
""" |
|
|
|
|
|
return self.parser._custom_xml_dom_elements(doc) |
|
|
|
|
|
class BooleanFlag(Flag): |
|
"""Basic boolean flag. |
|
|
|
Boolean flags do not take any arguments, and their value is either |
|
``True`` (1) or ``False`` (0). The false value is specified on the command |
|
line by prepending the word ``'no'`` to either the long or the short flag |
|
name. |
|
|
|
For example, if a Boolean flag was created whose long name was |
|
``'update'`` and whose short name was ``'x'``, then this flag could be |
|
explicitly unset through either ``--noupdate`` or ``--nox``. |
|
""" |
|
|
|
def __init__(self, name, default, help, short_name=None, **args): |
|
p = _argument_parser.BooleanParser() |
|
super(BooleanFlag, self).__init__( |
|
p, None, name, default, help, short_name, 1, **args) |
|
|
|
|
|
class EnumFlag(Flag): |
|
"""Basic enum flag; its value can be any string from list of enum_values.""" |
|
|
|
def __init__(self, name, default, help, enum_values, |
|
short_name=None, case_sensitive=True, **args): |
|
p = _argument_parser.EnumParser(enum_values, case_sensitive) |
|
g = _argument_parser.ArgumentSerializer() |
|
super(EnumFlag, self).__init__( |
|
p, g, name, default, help, short_name, **args) |
|
self.help = '<%s>: %s' % ('|'.join(enum_values), self.help) |
|
|
|
def _extra_xml_dom_elements(self, doc): |
|
elements = [] |
|
for enum_value in self.parser.enum_values: |
|
elements.append(_helpers.create_xml_dom_element( |
|
doc, 'enum_value', enum_value)) |
|
return elements |
|
|
|
|
|
class EnumClassFlag(Flag): |
|
"""Basic enum flag; its value is an enum class's member.""" |
|
|
|
def __init__( |
|
self, |
|
name, |
|
default, |
|
help, |
|
enum_class, |
|
short_name=None, |
|
case_sensitive=False, |
|
**args): |
|
p = _argument_parser.EnumClassParser( |
|
enum_class, case_sensitive=case_sensitive) |
|
g = _argument_parser.EnumClassSerializer(lowercase=not case_sensitive) |
|
super(EnumClassFlag, self).__init__( |
|
p, g, name, default, help, short_name, **args) |
|
self.help = '<%s>: %s' % ('|'.join(p.member_names), self.help) |
|
|
|
def _extra_xml_dom_elements(self, doc): |
|
elements = [] |
|
for enum_value in self.parser.enum_class.__members__.keys(): |
|
elements.append(_helpers.create_xml_dom_element( |
|
doc, 'enum_value', enum_value)) |
|
return elements |
|
|
|
|
|
class MultiFlag(Flag): |
|
"""A flag that can appear multiple time on the command-line. |
|
|
|
The value of such a flag is a list that contains the individual values |
|
from all the appearances of that flag on the command-line. |
|
|
|
See the __doc__ for Flag for most behavior of this class. Only |
|
differences in behavior are described here: |
|
|
|
* The default value may be either a single value or an iterable of values. |
|
A single value is transformed into a single-item list of that value. |
|
|
|
* The value of the flag is always a list, even if the option was |
|
only supplied once, and even if the default value is a single |
|
value |
|
""" |
|
|
|
def __init__(self, *args, **kwargs): |
|
super(MultiFlag, self).__init__(*args, **kwargs) |
|
self.help += ';\n repeat this option to specify a list of values' |
|
|
|
def parse(self, arguments): |
|
"""Parses one or more arguments with the installed parser. |
|
|
|
Args: |
|
arguments: a single argument or a list of arguments (typically a |
|
list of default values); a single argument is converted |
|
internally into a list containing one item. |
|
""" |
|
new_values = self._parse(arguments) |
|
if self.present: |
|
self.value.extend(new_values) |
|
else: |
|
self.value = new_values |
|
self.present += len(new_values) |
|
|
|
def _parse(self, arguments): |
|
if (isinstance(arguments, abc.Iterable) and |
|
not isinstance(arguments, str)): |
|
arguments = list(arguments) |
|
|
|
if not isinstance(arguments, list): |
|
|
|
|
|
|
|
arguments = [arguments] |
|
|
|
return [super(MultiFlag, self)._parse(item) for item in arguments] |
|
|
|
def _serialize(self, value): |
|
"""See base class.""" |
|
if not self.serializer: |
|
raise _exceptions.Error( |
|
'Serializer not present for flag %s' % self.name) |
|
if value is None: |
|
return '' |
|
|
|
serialized_items = [ |
|
super(MultiFlag, self)._serialize(value_item) for value_item in value |
|
] |
|
|
|
return '\n'.join(serialized_items) |
|
|
|
def flag_type(self): |
|
"""See base class.""" |
|
return 'multi ' + self.parser.flag_type() |
|
|
|
def _extra_xml_dom_elements(self, doc): |
|
elements = [] |
|
if hasattr(self.parser, 'enum_values'): |
|
for enum_value in self.parser.enum_values: |
|
elements.append(_helpers.create_xml_dom_element( |
|
doc, 'enum_value', enum_value)) |
|
return elements |
|
|
|
|
|
class MultiEnumClassFlag(MultiFlag): |
|
"""A multi_enum_class flag. |
|
|
|
See the __doc__ for MultiFlag for most behaviors of this class. In addition, |
|
this class knows how to handle enum.Enum instances as values for this flag |
|
type. |
|
""" |
|
|
|
def __init__(self, |
|
name, |
|
default, |
|
help_string, |
|
enum_class, |
|
case_sensitive=False, |
|
**args): |
|
p = _argument_parser.EnumClassParser( |
|
enum_class, case_sensitive=case_sensitive) |
|
g = _argument_parser.EnumClassListSerializer( |
|
list_sep=',', lowercase=not case_sensitive) |
|
super(MultiEnumClassFlag, self).__init__( |
|
p, g, name, default, help_string, **args) |
|
self.help = ( |
|
'<%s>: %s;\n repeat this option to specify a list of values' % |
|
('|'.join(p.member_names), help_string or '(no help available)')) |
|
|
|
def _extra_xml_dom_elements(self, doc): |
|
elements = [] |
|
for enum_value in self.parser.enum_class.__members__.keys(): |
|
elements.append(_helpers.create_xml_dom_element( |
|
doc, 'enum_value', enum_value)) |
|
return elements |
|
|
|
def _serialize_value_for_xml(self, value): |
|
"""See base class.""" |
|
if value is not None: |
|
value_serialized = self.serializer.serialize(value) |
|
else: |
|
value_serialized = '' |
|
return value_serialized |
|
|