1# Copyright 2017 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.
14"""Defines the FlagValues class - registry of 'Flag' objects.
15
16Do NOT import this module directly. Import the flags package and use the
17aliases defined at the package level instead.
18"""
19
20import copy
21import itertools
22import logging
23import os
24import sys
25from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Text, TextIO, Generic, TypeVar, Union, Tuple
26from xml.dom import minidom
27
28from absl.flags import _exceptions
29from absl.flags import _flag
30from absl.flags import _helpers
31from absl.flags import _validators_classes
32from absl.flags._flag import Flag
33
34# Add flagvalues module to disclaimed module ids.
35_helpers.disclaim_module_ids.add(id(sys.modules[__name__]))
36
37_T = TypeVar('_T')
38
39
40class FlagValues:
41  """Registry of :class:`~absl.flags.Flag` objects.
42
43  A :class:`FlagValues` can then scan command line arguments, passing flag
44  arguments through to the 'Flag' objects that it owns.  It also
45  provides easy access to the flag values.  Typically only one
46  :class:`FlagValues` object is needed by an application:
47  :const:`FLAGS`.
48
49  This class is heavily overloaded:
50
51  :class:`Flag` objects are registered via ``__setitem__``::
52
53       FLAGS['longname'] = x   # register a new flag
54
55  The ``.value`` attribute of the registered :class:`~absl.flags.Flag` objects
56  can be accessed as attributes of this :class:`FlagValues` object, through
57  ``__getattr__``.  Both the long and short name of the original
58  :class:`~absl.flags.Flag` objects can be used to access its value::
59
60       FLAGS.longname  # parsed flag value
61       FLAGS.x  # parsed flag value (short name)
62
63  Command line arguments are scanned and passed to the registered
64  :class:`~absl.flags.Flag` objects through the ``__call__`` method.  Unparsed
65  arguments, including ``argv[0]`` (e.g. the program name) are returned::
66
67       argv = FLAGS(sys.argv)  # scan command line arguments
68
69  The original registered :class:`~absl.flags.Flag` objects can be retrieved
70  through the use of the dictionary-like operator, ``__getitem__``::
71
72       x = FLAGS['longname']   # access the registered Flag object
73
74  The ``str()`` operator of a :class:`absl.flags.FlagValues` object provides
75  help for all of the registered :class:`~absl.flags.Flag` objects.
76  """
77
78  _HAS_DYNAMIC_ATTRIBUTES = True
79
80  # A note on collections.abc.Mapping:
81  # FlagValues defines __getitem__, __iter__, and __len__. It makes perfect
82  # sense to let it be a collections.abc.Mapping class. However, we are not
83  # able to do so. The mixin methods, e.g. keys, values, are not uncommon flag
84  # names. Those flag values would not be accessible via the FLAGS.xxx form.
85
86  __dict__: Dict[str, Any]
87
88  def __init__(self):
89    # Since everything in this class is so heavily overloaded, the only
90    # way of defining and using fields is to access __dict__ directly.
91
92    # Dictionary: flag name (string) -> Flag object.
93    self.__dict__['__flags'] = {}
94
95    # Set: name of hidden flag (string).
96    # Holds flags that should not be directly accessible from Python.
97    self.__dict__['__hiddenflags'] = set()
98
99    # Dictionary: module name (string) -> list of Flag objects that are defined
100    # by that module.
101    self.__dict__['__flags_by_module'] = {}
102    # Dictionary: module id (int) -> list of Flag objects that are defined by
103    # that module.
104    self.__dict__['__flags_by_module_id'] = {}
105    # Dictionary: module name (string) -> list of Flag objects that are
106    # key for that module.
107    self.__dict__['__key_flags_by_module'] = {}
108
109    # Bool: True if flags were parsed.
110    self.__dict__['__flags_parsed'] = False
111
112    # Bool: True if unparse_flags() was called.
113    self.__dict__['__unparse_flags_called'] = False
114
115    # None or Method(name, value) to call from __setattr__ for an unknown flag.
116    self.__dict__['__set_unknown'] = None
117
118    # A set of banned flag names. This is to prevent users from accidentally
119    # defining a flag that has the same name as a method on this class.
120    # Users can still allow defining the flag by passing
121    # allow_using_method_names=True in DEFINE_xxx functions.
122    self.__dict__['__banned_flag_names'] = frozenset(dir(FlagValues))
123
124    # Bool: Whether to use GNU style scanning.
125    self.__dict__['__use_gnu_getopt'] = True
126
127    # Bool: Whether use_gnu_getopt has been explicitly set by the user.
128    self.__dict__['__use_gnu_getopt_explicitly_set'] = False
129
130    # Function: Takes a flag name as parameter, returns a tuple
131    # (is_retired, type_is_bool).
132    self.__dict__['__is_retired_flag_func'] = None
133
134  def set_gnu_getopt(self, gnu_getopt: bool = True) -> None:
135    """Sets whether or not to use GNU style scanning.
136
137    GNU style allows mixing of flag and non-flag arguments. See
138    http://docs.python.org/library/getopt.html#getopt.gnu_getopt
139
140    Args:
141      gnu_getopt: bool, whether or not to use GNU style scanning.
142    """
143    self.__dict__['__use_gnu_getopt'] = gnu_getopt
144    self.__dict__['__use_gnu_getopt_explicitly_set'] = True
145
146  def is_gnu_getopt(self) -> bool:
147    return self.__dict__['__use_gnu_getopt']
148
149  def _flags(self) -> Dict[Text, Flag]:
150    return self.__dict__['__flags']
151
152  def flags_by_module_dict(self) -> Dict[Text, List[Flag]]:
153    """Returns the dictionary of module_name -> list of defined flags.
154
155    Returns:
156      A dictionary.  Its keys are module names (strings).  Its values
157      are lists of Flag objects.
158    """
159    return self.__dict__['__flags_by_module']
160
161  def flags_by_module_id_dict(self) -> Dict[int, List[Flag]]:
162    """Returns the dictionary of module_id -> list of defined flags.
163
164    Returns:
165      A dictionary.  Its keys are module IDs (ints).  Its values
166      are lists of Flag objects.
167    """
168    return self.__dict__['__flags_by_module_id']
169
170  def key_flags_by_module_dict(self) -> Dict[Text, List[Flag]]:
171    """Returns the dictionary of module_name -> list of key flags.
172
173    Returns:
174      A dictionary.  Its keys are module names (strings).  Its values
175      are lists of Flag objects.
176    """
177    return self.__dict__['__key_flags_by_module']
178
179  def register_flag_by_module(self, module_name: Text, flag: Flag) -> None:
180    """Records the module that defines a specific flag.
181
182    We keep track of which flag is defined by which module so that we
183    can later sort the flags by module.
184
185    Args:
186      module_name: str, the name of a Python module.
187      flag: Flag, the Flag instance that is key to the module.
188    """
189    flags_by_module = self.flags_by_module_dict()
190    flags_by_module.setdefault(module_name, []).append(flag)
191
192  def register_flag_by_module_id(self, module_id: int, flag: Flag) -> None:
193    """Records the module that defines a specific flag.
194
195    Args:
196      module_id: int, the ID of the Python module.
197      flag: Flag, the Flag instance that is key to the module.
198    """
199    flags_by_module_id = self.flags_by_module_id_dict()
200    flags_by_module_id.setdefault(module_id, []).append(flag)
201
202  def register_key_flag_for_module(self, module_name: Text, flag: Flag) -> None:
203    """Specifies that a flag is a key flag for a module.
204
205    Args:
206      module_name: str, the name of a Python module.
207      flag: Flag, the Flag instance that is key to the module.
208    """
209    key_flags_by_module = self.key_flags_by_module_dict()
210    # The list of key flags for the module named module_name.
211    key_flags = key_flags_by_module.setdefault(module_name, [])
212    # Add flag, but avoid duplicates.
213    if flag not in key_flags:
214      key_flags.append(flag)
215
216  def _flag_is_registered(self, flag_obj: Flag) -> bool:
217    """Checks whether a Flag object is registered under long name or short name.
218
219    Args:
220      flag_obj: Flag, the Flag instance to check for.
221
222    Returns:
223      bool, True iff flag_obj is registered under long name or short name.
224    """
225    flag_dict = self._flags()
226    # Check whether flag_obj is registered under its long name.
227    name = flag_obj.name
228    if flag_dict.get(name, None) == flag_obj:
229      return True
230    # Check whether flag_obj is registered under its short name.
231    short_name = flag_obj.short_name
232    if (short_name is not None and flag_dict.get(short_name, None) == flag_obj):
233      return True
234    return False
235
236  def _cleanup_unregistered_flag_from_module_dicts(
237      self, flag_obj: Flag
238  ) -> None:
239    """Cleans up unregistered flags from all module -> [flags] dictionaries.
240
241    If flag_obj is registered under either its long name or short name, it
242    won't be removed from the dictionaries.
243
244    Args:
245      flag_obj: Flag, the Flag instance to clean up for.
246    """
247    if self._flag_is_registered(flag_obj):
248      return
249    for flags_by_module_dict in (self.flags_by_module_dict(),
250                                 self.flags_by_module_id_dict(),
251                                 self.key_flags_by_module_dict()):
252      for flags_in_module in flags_by_module_dict.values():
253        # While (as opposed to if) takes care of multiple occurrences of a
254        # flag in the list for the same module.
255        while flag_obj in flags_in_module:
256          flags_in_module.remove(flag_obj)
257
258  def get_flags_for_module(self, module: Union[Text, Any]) -> List[Flag]:
259    """Returns the list of flags defined by a module.
260
261    Args:
262      module: module|str, the module to get flags from.
263
264    Returns:
265      [Flag], a new list of Flag instances.  Caller may update this list as
266      desired: none of those changes will affect the internals of this
267      FlagValue instance.
268    """
269    if not isinstance(module, str):
270      module = module.__name__
271    if module == '__main__':
272      module = sys.argv[0]
273
274    return list(self.flags_by_module_dict().get(module, []))
275
276  def get_key_flags_for_module(self, module: Union[Text, Any]) -> List[Flag]:
277    """Returns the list of key flags for a module.
278
279    Args:
280      module: module|str, the module to get key flags from.
281
282    Returns:
283      [Flag], a new list of Flag instances.  Caller may update this list as
284      desired: none of those changes will affect the internals of this
285      FlagValue instance.
286    """
287    if not isinstance(module, str):
288      module = module.__name__
289    if module == '__main__':
290      module = sys.argv[0]
291
292    # Any flag is a key flag for the module that defined it.  NOTE:
293    # key_flags is a fresh list: we can update it without affecting the
294    # internals of this FlagValues object.
295    key_flags = self.get_flags_for_module(module)
296
297    # Take into account flags explicitly declared as key for a module.
298    for flag in self.key_flags_by_module_dict().get(module, []):
299      if flag not in key_flags:
300        key_flags.append(flag)
301    return key_flags
302
303  # TODO(yileiyang): Restrict default to Optional[Text].
304  def find_module_defining_flag(
305      self, flagname: Text, default: Optional[_T] = None
306  ) -> Union[str, Optional[_T]]:
307    """Return the name of the module defining this flag, or default.
308
309    Args:
310      flagname: str, name of the flag to lookup.
311      default: Value to return if flagname is not defined. Defaults to None.
312
313    Returns:
314      The name of the module which registered the flag with this name.
315      If no such module exists (i.e. no flag with this name exists),
316      we return default.
317    """
318    registered_flag = self._flags().get(flagname)
319    if registered_flag is None:
320      return default
321    for module, flags in self.flags_by_module_dict().items():
322      for flag in flags:
323        # It must compare the flag with the one in _flags. This is because a
324        # flag might be overridden only for its long name (or short name),
325        # and only its short name (or long name) is considered registered.
326        if (flag.name == registered_flag.name and
327            flag.short_name == registered_flag.short_name):
328          return module
329    return default
330
331  # TODO(yileiyang): Restrict default to Optional[Text].
332  def find_module_id_defining_flag(
333      self, flagname: Text, default: Optional[_T] = None
334  ) -> Union[int, Optional[_T]]:
335    """Return the ID of the module defining this flag, or default.
336
337    Args:
338      flagname: str, name of the flag to lookup.
339      default: Value to return if flagname is not defined. Defaults to None.
340
341    Returns:
342      The ID of the module which registered the flag with this name.
343      If no such module exists (i.e. no flag with this name exists),
344      we return default.
345    """
346    registered_flag = self._flags().get(flagname)
347    if registered_flag is None:
348      return default
349    for module_id, flags in self.flags_by_module_id_dict().items():
350      for flag in flags:
351        # It must compare the flag with the one in _flags. This is because a
352        # flag might be overridden only for its long name (or short name),
353        # and only its short name (or long name) is considered registered.
354        if (flag.name == registered_flag.name and
355            flag.short_name == registered_flag.short_name):
356          return module_id
357    return default
358
359  def _register_unknown_flag_setter(
360      self, setter: Callable[[str, Any], None]
361  ) -> None:
362    """Allow set default values for undefined flags.
363
364    Args:
365      setter: Method(name, value) to call to __setattr__ an unknown flag. Must
366        raise NameError or ValueError for invalid name/value.
367    """
368    self.__dict__['__set_unknown'] = setter
369
370  def _set_unknown_flag(self, name: str, value: _T) -> _T:
371    """Returns value if setting flag |name| to |value| returned True.
372
373    Args:
374      name: str, name of the flag to set.
375      value: Value to set.
376
377    Returns:
378      Flag value on successful call.
379
380    Raises:
381      UnrecognizedFlagError
382      IllegalFlagValueError
383    """
384    setter = self.__dict__['__set_unknown']
385    if setter:
386      try:
387        setter(name, value)
388        return value
389      except (TypeError, ValueError):  # Flag value is not valid.
390        raise _exceptions.IllegalFlagValueError(
391            '"{1}" is not valid for --{0}'.format(name, value))
392      except NameError:  # Flag name is not valid.
393        pass
394    raise _exceptions.UnrecognizedFlagError(name, value)
395
396  def append_flag_values(self, flag_values: 'FlagValues') -> None:
397    """Appends flags registered in another FlagValues instance.
398
399    Args:
400      flag_values: FlagValues, the FlagValues instance from which to copy flags.
401    """
402    for flag_name, flag in flag_values._flags().items():  # pylint: disable=protected-access
403      # Each flags with short_name appears here twice (once under its
404      # normal name, and again with its short name).  To prevent
405      # problems (DuplicateFlagError) with double flag registration, we
406      # perform a check to make sure that the entry we're looking at is
407      # for its normal name.
408      if flag_name == flag.name:
409        try:
410          self[flag_name] = flag
411        except _exceptions.DuplicateFlagError:
412          raise _exceptions.DuplicateFlagError.from_flag(
413              flag_name, self, other_flag_values=flag_values)
414
415  def remove_flag_values(
416      self, flag_values: 'Union[FlagValues, Iterable[Text]]'
417  ) -> None:
418    """Remove flags that were previously appended from another FlagValues.
419
420    Args:
421      flag_values: FlagValues, the FlagValues instance containing flags to
422        remove.
423    """
424    for flag_name in flag_values:
425      self.__delattr__(flag_name)
426
427  def __setitem__(self, name: Text, flag: Flag) -> None:
428    """Registers a new flag variable."""
429    fl = self._flags()
430    if not isinstance(flag, _flag.Flag):
431      raise _exceptions.IllegalFlagValueError(
432          f'Expect Flag instances, found type {type(flag)}. '
433          "Maybe you didn't mean to use FlagValue.__setitem__?")
434    if not isinstance(name, str):
435      raise _exceptions.Error('Flag name must be a string')
436    if not name:
437      raise _exceptions.Error('Flag name cannot be empty')
438    if ' ' in name:
439      raise _exceptions.Error('Flag name cannot contain a space')
440    self._check_method_name_conflicts(name, flag)
441    if name in fl and not flag.allow_override and not fl[name].allow_override:
442      module, module_name = _helpers.get_calling_module_object_and_name()
443      if (self.find_module_defining_flag(name) == module_name and
444          id(module) != self.find_module_id_defining_flag(name)):
445        # If the flag has already been defined by a module with the same name,
446        # but a different ID, we can stop here because it indicates that the
447        # module is simply being imported a subsequent time.
448        return
449      raise _exceptions.DuplicateFlagError.from_flag(name, self)
450    # If a new flag overrides an old one, we need to cleanup the old flag's
451    # modules if it's not registered.
452    flags_to_cleanup = set()
453    short_name: str = flag.short_name  # pytype: disable=annotation-type-mismatch
454    if short_name is not None:
455      if (short_name in fl and not flag.allow_override and
456          not fl[short_name].allow_override):
457        raise _exceptions.DuplicateFlagError.from_flag(short_name, self)
458      if short_name in fl and fl[short_name] != flag:
459        flags_to_cleanup.add(fl[short_name])
460      fl[short_name] = flag
461    if (name not in fl  # new flag
462        or fl[name].using_default_value or not flag.using_default_value):
463      if name in fl and fl[name] != flag:
464        flags_to_cleanup.add(fl[name])
465      fl[name] = flag
466    for f in flags_to_cleanup:
467      self._cleanup_unregistered_flag_from_module_dicts(f)
468
469  def __dir__(self) -> List[Text]:
470    """Returns list of names of all defined flags.
471
472    Useful for TAB-completion in ipython.
473
474    Returns:
475      [str], a list of names of all defined flags.
476    """
477    return sorted(self.__dict__['__flags'])
478
479  def __getitem__(self, name: Text) -> Flag:
480    """Returns the Flag object for the flag --name."""
481    return self._flags()[name]
482
483  def _hide_flag(self, name):
484    """Marks the flag --name as hidden."""
485    self.__dict__['__hiddenflags'].add(name)
486
487  def __getattr__(self, name: Text) -> Any:
488    """Retrieves the 'value' attribute of the flag --name."""
489    fl = self._flags()
490    if name not in fl:
491      raise AttributeError(name)
492    if name in self.__dict__['__hiddenflags']:
493      raise AttributeError(name)
494
495    if self.__dict__['__flags_parsed'] or fl[name].present:
496      return fl[name].value
497    else:
498      raise _exceptions.UnparsedFlagAccessError(
499          'Trying to access flag --%s before flags were parsed.' % name)
500
501  def __setattr__(self, name: Text, value: _T) -> _T:
502    """Sets the 'value' attribute of the flag --name."""
503    self._set_attributes(**{name: value})
504    return value
505
506  def _set_attributes(self, **attributes: Any) -> None:
507    """Sets multiple flag values together, triggers validators afterwards."""
508    fl = self._flags()
509    known_flag_vals = {}
510    known_flag_used_defaults = {}
511    try:
512      for name, value in attributes.items():
513        if name in self.__dict__['__hiddenflags']:
514          raise AttributeError(name)
515        if name in fl:
516          orig = fl[name].value
517          fl[name].value = value
518          known_flag_vals[name] = orig
519        else:
520          self._set_unknown_flag(name, value)
521      for name in known_flag_vals:
522        self._assert_validators(fl[name].validators)
523        known_flag_used_defaults[name] = fl[name].using_default_value
524        fl[name].using_default_value = False
525    except:
526      for name, orig in known_flag_vals.items():
527        fl[name].value = orig
528      for name, orig in known_flag_used_defaults.items():
529        fl[name].using_default_value = orig
530      # NOTE: We do not attempt to undo unknown flag side effects because we
531      # cannot reliably undo the user-configured behavior.
532      raise
533
534  def validate_all_flags(self) -> None:
535    """Verifies whether all flags pass validation.
536
537    Raises:
538      AttributeError: Raised if validators work with a non-existing flag.
539      IllegalFlagValueError: Raised if validation fails for at least one
540          validator.
541    """
542    all_validators = set()
543    for flag in self._flags().values():
544      all_validators.update(flag.validators)
545    self._assert_validators(all_validators)
546
547  def _assert_validators(
548      self, validators: Iterable[_validators_classes.Validator]
549  ) -> None:
550    """Asserts if all validators in the list are satisfied.
551
552    It asserts validators in the order they were created.
553
554    Args:
555      validators: Iterable(validators.Validator), validators to be verified.
556
557    Raises:
558      AttributeError: Raised if validators work with a non-existing flag.
559      IllegalFlagValueError: Raised if validation fails for at least one
560          validator.
561    """
562    messages = []
563    bad_flags = set()
564    for validator in sorted(
565        validators, key=lambda validator: validator.insertion_index):
566      try:
567        if isinstance(validator, _validators_classes.SingleFlagValidator):
568          if validator.flag_name in bad_flags:
569            continue
570        elif isinstance(validator, _validators_classes.MultiFlagsValidator):
571          if bad_flags & set(validator.flag_names):
572            continue
573        validator.verify(self)
574      except _exceptions.ValidationError as e:
575        if isinstance(validator, _validators_classes.SingleFlagValidator):
576          bad_flags.add(validator.flag_name)
577        elif isinstance(validator, _validators_classes.MultiFlagsValidator):
578          bad_flags.update(set(validator.flag_names))
579        message = validator.print_flags_with_values(self)
580        messages.append('%s: %s' % (message, str(e)))
581    if messages:
582      raise _exceptions.IllegalFlagValueError('\n'.join(messages))
583
584  def __delattr__(self, flag_name: Text) -> None:
585    """Deletes a previously-defined flag from a flag object.
586
587    This method makes sure we can delete a flag by using
588
589      del FLAGS.<flag_name>
590
591    E.g.,
592
593      flags.DEFINE_integer('foo', 1, 'Integer flag.')
594      del flags.FLAGS.foo
595
596    If a flag is also registered by its the other name (long name or short
597    name), the other name won't be deleted.
598
599    Args:
600      flag_name: str, the name of the flag to be deleted.
601
602    Raises:
603      AttributeError: Raised when there is no registered flag named flag_name.
604    """
605    fl = self._flags()
606    if flag_name not in fl:
607      raise AttributeError(flag_name)
608
609    flag_obj = fl[flag_name]
610    del fl[flag_name]
611
612    self._cleanup_unregistered_flag_from_module_dicts(flag_obj)
613
614  def set_default(self, name: Text, value: Any) -> None:
615    """Changes the default value of the named flag object.
616
617    The flag's current value is also updated if the flag is currently using
618    the default value, i.e. not specified in the command line, and not set
619    by FLAGS.name = value.
620
621    Args:
622      name: str, the name of the flag to modify.
623      value: The new default value.
624
625    Raises:
626      UnrecognizedFlagError: Raised when there is no registered flag named name.
627      IllegalFlagValueError: Raised when value is not valid.
628    """
629    fl = self._flags()
630    if name not in fl:
631      self._set_unknown_flag(name, value)
632      return
633    fl[name]._set_default(value)  # pylint: disable=protected-access
634    self._assert_validators(fl[name].validators)
635
636  def __contains__(self, name: Text) -> bool:
637    """Returns True if name is a value (flag) in the dict."""
638    return name in self._flags()
639
640  def __len__(self) -> int:
641    return len(self.__dict__['__flags'])
642
643  def __iter__(self) -> Iterator[Text]:
644    return iter(self._flags())
645
646  def __call__(
647      self, argv: Sequence[Text], known_only: bool = False
648  ) -> List[Text]:
649    """Parses flags from argv; stores parsed flags into this FlagValues object.
650
651    All unparsed arguments are returned.
652
653    Args:
654       argv: a tuple/list of strings.
655       known_only: bool, if True, parse and remove known flags; return the rest
656         untouched. Unknown flags specified by --undefok are not returned.
657
658    Returns:
659       The list of arguments not parsed as options, including argv[0].
660
661    Raises:
662       Error: Raised on any parsing error.
663       TypeError: Raised on passing wrong type of arguments.
664       ValueError: Raised on flag value parsing error.
665    """
666    if isinstance(argv, (str, bytes)):
667      raise TypeError(
668          'argv should be a tuple/list of strings, not bytes or string.')
669    if not argv:
670      raise ValueError(
671          'argv cannot be an empty list, and must contain the program name as '
672          'the first element.')
673
674    # This pre parses the argv list for --flagfile=<> options.
675    program_name = argv[0]
676    args = self.read_flags_from_files(argv[1:], force_gnu=False)
677
678    # Parse the arguments.
679    unknown_flags, unparsed_args = self._parse_args(args, known_only)
680
681    # Handle unknown flags by raising UnrecognizedFlagError.
682    # Note some users depend on us raising this particular error.
683    for name, value in unknown_flags:
684      suggestions = _helpers.get_flag_suggestions(name, list(self))
685      raise _exceptions.UnrecognizedFlagError(
686          name, value, suggestions=suggestions)
687
688    self.mark_as_parsed()
689    self.validate_all_flags()
690    return [program_name] + unparsed_args
691
692  def __getstate__(self) -> Any:
693    raise TypeError("can't pickle FlagValues")
694
695  def __copy__(self) -> Any:
696    raise TypeError('FlagValues does not support shallow copies. '
697                    'Use absl.testing.flagsaver or copy.deepcopy instead.')
698
699  def __deepcopy__(self, memo) -> Any:
700    result = object.__new__(type(self))
701    result.__dict__.update(copy.deepcopy(self.__dict__, memo))
702    return result
703
704  def _set_is_retired_flag_func(self, is_retired_flag_func):
705    """Sets a function for checking retired flags.
706
707    Do not use it. This is a private absl API used to check retired flags
708    registered by the absl C++ flags library.
709
710    Args:
711      is_retired_flag_func: Callable(str) -> (bool, bool), a function takes flag
712        name as parameter, returns a tuple (is_retired, type_is_bool).
713    """
714    self.__dict__['__is_retired_flag_func'] = is_retired_flag_func
715
716  def _parse_args(
717      self, args: List[str], known_only: bool
718  ) -> Tuple[List[Tuple[Optional[str], Any]], List[str]]:
719    """Helper function to do the main argument parsing.
720
721    This function goes through args and does the bulk of the flag parsing.
722    It will find the corresponding flag in our flag dictionary, and call its
723    .parse() method on the flag value.
724
725    Args:
726      args: [str], a list of strings with the arguments to parse.
727      known_only: bool, if True, parse and remove known flags; return the rest
728        untouched. Unknown flags specified by --undefok are not returned.
729
730    Returns:
731      A tuple with the following:
732          unknown_flags: List of (flag name, arg) for flags we don't know about.
733          unparsed_args: List of arguments we did not parse.
734
735    Raises:
736       Error: Raised on any parsing error.
737       ValueError: Raised on flag value parsing error.
738    """
739    unparsed_names_and_args = []  # A list of (flag name or None, arg).
740    undefok = set()
741    retired_flag_func = self.__dict__['__is_retired_flag_func']
742
743    flag_dict = self._flags()
744    args = iter(args)
745    for arg in args:
746      value = None
747
748      def get_value():
749        # pylint: disable=cell-var-from-loop
750        try:
751          return next(args) if value is None else value
752        except StopIteration:
753          raise _exceptions.Error('Missing value for flag ' + arg)  # pylint: disable=undefined-loop-variable
754
755      if not arg.startswith('-'):
756        # A non-argument: default is break, GNU is skip.
757        unparsed_names_and_args.append((None, arg))
758        if self.is_gnu_getopt():
759          continue
760        else:
761          break
762
763      if arg == '--':
764        if known_only:
765          unparsed_names_and_args.append((None, arg))
766        break
767
768      # At this point, arg must start with '-'.
769      if arg.startswith('--'):
770        arg_without_dashes = arg[2:]
771      else:
772        arg_without_dashes = arg[1:]
773
774      if '=' in arg_without_dashes:
775        name, value = arg_without_dashes.split('=', 1)
776      else:
777        name, value = arg_without_dashes, None
778
779      if not name:
780        # The argument is all dashes (including one dash).
781        unparsed_names_and_args.append((None, arg))
782        if self.is_gnu_getopt():
783          continue
784        else:
785          break
786
787      # --undefok is a special case.
788      if name == 'undefok':
789        value = get_value()
790        undefok.update(v.strip() for v in value.split(','))
791        undefok.update('no' + v.strip() for v in value.split(','))
792        continue
793
794      flag = flag_dict.get(name)
795      if flag is not None:
796        if flag.boolean and value is None:
797          value = 'true'
798        else:
799          value = get_value()
800      elif name.startswith('no') and len(name) > 2:
801        # Boolean flags can take the form of --noflag, with no value.
802        noflag = flag_dict.get(name[2:])
803        if noflag is not None and noflag.boolean:
804          if value is not None:
805            raise ValueError(arg + ' does not take an argument')
806          flag = noflag
807          value = 'false'
808
809      if retired_flag_func and flag is None:
810        is_retired, is_bool = retired_flag_func(name)
811
812        # If we didn't recognize that flag, but it starts with
813        # "no" then maybe it was a boolean flag specified in the
814        # --nofoo form.
815        if not is_retired and name.startswith('no'):
816          is_retired, is_bool = retired_flag_func(name[2:])
817          is_retired = is_retired and is_bool
818
819        if is_retired:
820          if not is_bool and value is None:
821            # This happens when a non-bool retired flag is specified
822            # in format of "--flag value".
823            get_value()
824          logging.error(
825              'Flag "%s" is retired and should no longer be specified. See '
826              'https://abseil.io/tips/90.',
827              name,
828          )
829          continue
830
831      if flag is not None:
832        # LINT.IfChange
833        flag.parse(value)
834        flag.using_default_value = False
835        # LINT.ThenChange(../testing/flagsaver.py:flag_override_parsing)
836      else:
837        unparsed_names_and_args.append((name, arg))
838
839    unknown_flags = []
840    unparsed_args = []
841    for name, arg in unparsed_names_and_args:
842      if name is None:
843        # Positional arguments.
844        unparsed_args.append(arg)
845      elif name in undefok:
846        # Remove undefok flags.
847        continue
848      else:
849        # This is an unknown flag.
850        if known_only:
851          unparsed_args.append(arg)
852        else:
853          unknown_flags.append((name, arg))
854
855    unparsed_args.extend(list(args))
856    return unknown_flags, unparsed_args
857
858  def is_parsed(self) -> bool:
859    """Returns whether flags were parsed."""
860    return self.__dict__['__flags_parsed']
861
862  def mark_as_parsed(self) -> None:
863    """Explicitly marks flags as parsed.
864
865    Use this when the caller knows that this FlagValues has been parsed as if
866    a ``__call__()`` invocation has happened.  This is only a public method for
867    use by things like appcommands which do additional command like parsing.
868    """
869    self.__dict__['__flags_parsed'] = True
870
871  def unparse_flags(self) -> None:
872    """Unparses all flags to the point before any FLAGS(argv) was called."""
873    for f in self._flags().values():
874      f.unparse()
875    # We log this message before marking flags as unparsed to avoid a
876    # problem when the logging library causes flags access.
877    logging.info('unparse_flags() called; flags access will now raise errors.')
878    self.__dict__['__flags_parsed'] = False
879    self.__dict__['__unparse_flags_called'] = True
880
881  def flag_values_dict(self) -> Dict[Text, Any]:
882    """Returns a dictionary that maps flag names to flag values."""
883    return {name: flag.value for name, flag in self._flags().items()}
884
885  def __str__(self):
886    """Returns a help string for all known flags."""
887    return self.get_help()
888
889  def get_help(
890      self, prefix: Text = '', include_special_flags: bool = True
891  ) -> Text:
892    """Returns a help string for all known flags.
893
894    Args:
895      prefix: str, per-line output prefix.
896      include_special_flags: bool, whether to include description of
897        SPECIAL_FLAGS, i.e. --flagfile and --undefok.
898
899    Returns:
900      str, formatted help message.
901    """
902    flags_by_module = self.flags_by_module_dict()
903    if flags_by_module:
904      modules = sorted(flags_by_module)
905      # Print the help for the main module first, if possible.
906      main_module = sys.argv[0]
907      if main_module in modules:
908        modules.remove(main_module)
909        modules = [main_module] + modules
910      return self._get_help_for_modules(modules, prefix, include_special_flags)
911    else:
912      output_lines = []
913      # Just print one long list of flags.
914      values = self._flags().values()
915      if include_special_flags:
916        values = itertools.chain(
917            values, _helpers.SPECIAL_FLAGS._flags().values()  # pylint: disable=protected-access  # pytype: disable=attribute-error
918        )
919      self._render_flag_list(values, output_lines, prefix)
920      return '\n'.join(output_lines)
921
922  def _get_help_for_modules(self, modules, prefix, include_special_flags):
923    """Returns the help string for a list of modules.
924
925    Private to absl.flags package.
926
927    Args:
928      modules: List[str], a list of modules to get the help string for.
929      prefix: str, a string that is prepended to each generated help line.
930      include_special_flags: bool, whether to include description of
931        SPECIAL_FLAGS, i.e. --flagfile and --undefok.
932    """
933    output_lines = []
934    for module in modules:
935      self._render_our_module_flags(module, output_lines, prefix)
936    if include_special_flags:
937      self._render_module_flags(
938          'absl.flags',
939          _helpers.SPECIAL_FLAGS._flags().values(),  # pylint: disable=protected-access  # pytype: disable=attribute-error
940          output_lines,
941          prefix,
942      )
943    return '\n'.join(output_lines)
944
945  def _render_module_flags(self, module, flags, output_lines, prefix=''):
946    """Returns a help string for a given module."""
947    if not isinstance(module, str):
948      module = module.__name__
949    output_lines.append('\n%s%s:' % (prefix, module))
950    self._render_flag_list(flags, output_lines, prefix + '  ')
951
952  def _render_our_module_flags(self, module, output_lines, prefix=''):
953    """Returns a help string for a given module."""
954    flags = self.get_flags_for_module(module)
955    if flags:
956      self._render_module_flags(module, flags, output_lines, prefix)
957
958  def _render_our_module_key_flags(self, module, output_lines, prefix=''):
959    """Returns a help string for the key flags of a given module.
960
961    Args:
962      module: module|str, the module to render key flags for.
963      output_lines: [str], a list of strings.  The generated help message lines
964        will be appended to this list.
965      prefix: str, a string that is prepended to each generated help line.
966    """
967    key_flags = self.get_key_flags_for_module(module)
968    if key_flags:
969      self._render_module_flags(module, key_flags, output_lines, prefix)
970
971  def module_help(self, module: Any) -> Text:
972    """Describes the key flags of a module.
973
974    Args:
975      module: module|str, the module to describe the key flags for.
976
977    Returns:
978      str, describing the key flags of a module.
979    """
980    helplist = []
981    self._render_our_module_key_flags(module, helplist)
982    return '\n'.join(helplist)
983
984  def main_module_help(self) -> Text:
985    """Describes the key flags of the main module.
986
987    Returns:
988      str, describing the key flags of the main module.
989    """
990    return self.module_help(sys.argv[0])
991
992  def _render_flag_list(self, flaglist, output_lines, prefix='  '):
993    fl = self._flags()
994    special_fl = _helpers.SPECIAL_FLAGS._flags()  # pylint: disable=protected-access  # pytype: disable=attribute-error
995    flaglist = [(flag.name, flag) for flag in flaglist]
996    flaglist.sort()
997    flagset = {}
998    for (name, flag) in flaglist:
999      # It's possible this flag got deleted or overridden since being
1000      # registered in the per-module flaglist.  Check now against the
1001      # canonical source of current flag information, the _flags.
1002      if fl.get(name, None) != flag and special_fl.get(name, None) != flag:
1003        # a different flag is using this name now
1004        continue
1005      # only print help once
1006      if flag in flagset:
1007        continue
1008      flagset[flag] = 1
1009      flaghelp = ''
1010      if flag.short_name:
1011        flaghelp += '-%s,' % flag.short_name
1012      if flag.boolean:
1013        flaghelp += '--[no]%s:' % flag.name
1014      else:
1015        flaghelp += '--%s:' % flag.name
1016      flaghelp += ' '
1017      if flag.help:
1018        flaghelp += flag.help
1019      flaghelp = _helpers.text_wrap(
1020          flaghelp, indent=prefix + '  ', firstline_indent=prefix)
1021      if flag.default_as_str:
1022        flaghelp += '\n'
1023        flaghelp += _helpers.text_wrap(
1024            '(default: %s)' % flag.default_as_str, indent=prefix + '  ')
1025      if flag.parser.syntactic_help:
1026        flaghelp += '\n'
1027        flaghelp += _helpers.text_wrap(
1028            '(%s)' % flag.parser.syntactic_help, indent=prefix + '  ')
1029      output_lines.append(flaghelp)
1030
1031  def get_flag_value(self, name: Text, default: Any) -> Any:  # pylint: disable=invalid-name
1032    """Returns the value of a flag (if not None) or a default value.
1033
1034    Args:
1035      name: str, the name of a flag.
1036      default: Default value to use if the flag value is None.
1037
1038    Returns:
1039      Requested flag value or default.
1040    """
1041
1042    value = self.__getattr__(name)
1043    if value is not None:  # Can't do if not value, b/c value might be '0' or ""
1044      return value
1045    else:
1046      return default
1047
1048  def _is_flag_file_directive(self, flag_string):
1049    """Checks whether flag_string contain a --flagfile=<foo> directive."""
1050    if isinstance(flag_string, str):
1051      if flag_string.startswith('--flagfile='):
1052        return 1
1053      elif flag_string == '--flagfile':
1054        return 1
1055      elif flag_string.startswith('-flagfile='):
1056        return 1
1057      elif flag_string == '-flagfile':
1058        return 1
1059      else:
1060        return 0
1061    return 0
1062
1063  def _extract_filename(self, flagfile_str):
1064    """Returns filename from a flagfile_str of form -[-]flagfile=filename.
1065
1066    The cases of --flagfile foo and -flagfile foo shouldn't be hitting
1067    this function, as they are dealt with in the level above this
1068    function.
1069
1070    Args:
1071      flagfile_str: str, the flagfile string.
1072
1073    Returns:
1074      str, the filename from a flagfile_str of form -[-]flagfile=filename.
1075
1076    Raises:
1077      Error: Raised when illegal --flagfile is provided.
1078    """
1079    if flagfile_str.startswith('--flagfile='):
1080      return os.path.expanduser((flagfile_str[(len('--flagfile=')):]).strip())
1081    elif flagfile_str.startswith('-flagfile='):
1082      return os.path.expanduser((flagfile_str[(len('-flagfile=')):]).strip())
1083    else:
1084      raise _exceptions.Error('Hit illegal --flagfile type: %s' % flagfile_str)
1085
1086  def _get_flag_file_lines(self, filename, parsed_file_stack=None):
1087    """Returns the useful (!=comments, etc) lines from a file with flags.
1088
1089    Args:
1090      filename: str, the name of the flag file.
1091      parsed_file_stack: [str], a list of the names of the files that we have
1092        recursively encountered at the current depth. MUTATED BY THIS FUNCTION
1093        (but the original value is preserved upon successfully returning from
1094        function call).
1095
1096    Returns:
1097      List of strings. See the note below.
1098
1099    NOTE(springer): This function checks for a nested --flagfile=<foo>
1100    tag and handles the lower file recursively. It returns a list of
1101    all the lines that _could_ contain command flags. This is
1102    EVERYTHING except whitespace lines and comments (lines starting
1103    with '#' or '//').
1104    """
1105    # For consistency with the cpp version, ignore empty values.
1106    if not filename:
1107      return []
1108    if parsed_file_stack is None:
1109      parsed_file_stack = []
1110    # We do a little safety check for reparsing a file we've already encountered
1111    # at a previous depth.
1112    if filename in parsed_file_stack:
1113      sys.stderr.write('Warning: Hit circular flagfile dependency. Ignoring'
1114                       ' flagfile: %s\n' % (filename,))
1115      return []
1116    else:
1117      parsed_file_stack.append(filename)
1118
1119    line_list = []  # All line from flagfile.
1120    flag_line_list = []  # Subset of lines w/o comments, blanks, flagfile= tags.
1121    try:
1122      file_obj = open(filename, 'r')
1123    except IOError as e_msg:
1124      raise _exceptions.CantOpenFlagFileError(
1125          'ERROR:: Unable to open flagfile: %s' % e_msg)
1126
1127    with file_obj:
1128      line_list = file_obj.readlines()
1129
1130    # This is where we check each line in the file we just read.
1131    for line in line_list:
1132      if line.isspace():
1133        pass
1134      # Checks for comment (a line that starts with '#').
1135      elif line.startswith('#') or line.startswith('//'):
1136        pass
1137      # Checks for a nested "--flagfile=<bar>" flag in the current file.
1138      # If we find one, recursively parse down into that file.
1139      elif self._is_flag_file_directive(line):
1140        sub_filename = self._extract_filename(line)
1141        included_flags = self._get_flag_file_lines(
1142            sub_filename, parsed_file_stack=parsed_file_stack)
1143        flag_line_list.extend(included_flags)
1144      else:
1145        # Any line that's not a comment or a nested flagfile should get
1146        # copied into 2nd position.  This leaves earlier arguments
1147        # further back in the list, thus giving them higher priority.
1148        flag_line_list.append(line.strip())
1149
1150    parsed_file_stack.pop()
1151    return flag_line_list
1152
1153  def read_flags_from_files(
1154      self, argv: Sequence[Text], force_gnu: bool = True
1155  ) -> List[Text]:
1156    """Processes command line args, but also allow args to be read from file.
1157
1158    Args:
1159      argv: [str], a list of strings, usually sys.argv[1:], which may contain
1160        one or more flagfile directives of the form --flagfile="./filename".
1161        Note that the name of the program (sys.argv[0]) should be omitted.
1162      force_gnu: bool, if False, --flagfile parsing obeys the
1163        FLAGS.is_gnu_getopt() value. If True, ignore the value and always follow
1164        gnu_getopt semantics.
1165
1166    Returns:
1167      A new list which has the original list combined with what we read
1168      from any flagfile(s).
1169
1170    Raises:
1171      IllegalFlagValueError: Raised when --flagfile is provided with no
1172          argument.
1173
1174    This function is called by FLAGS(argv).
1175    It scans the input list for a flag that looks like:
1176    --flagfile=<somefile>. Then it opens <somefile>, reads all valid key
1177    and value pairs and inserts them into the input list in exactly the
1178    place where the --flagfile arg is found.
1179
1180    Note that your application's flags are still defined the usual way
1181    using absl.flags DEFINE_flag() type functions.
1182
1183    Notes (assuming we're getting a commandline of some sort as our input):
1184
1185    * For duplicate flags, the last one we hit should "win".
1186    * Since flags that appear later win, a flagfile's settings can be "weak"
1187        if the --flagfile comes at the beginning of the argument sequence,
1188        and it can be "strong" if the --flagfile comes at the end.
1189    * A further "--flagfile=<otherfile.cfg>" CAN be nested in a flagfile.
1190        It will be expanded in exactly the spot where it is found.
1191    * In a flagfile, a line beginning with # or // is a comment.
1192    * Entirely blank lines _should_ be ignored.
1193    """
1194    rest_of_args = argv
1195    new_argv = []
1196    while rest_of_args:
1197      current_arg = rest_of_args[0]
1198      rest_of_args = rest_of_args[1:]
1199      if self._is_flag_file_directive(current_arg):
1200        # This handles the case of -(-)flagfile foo.  In this case the
1201        # next arg really is part of this one.
1202        if current_arg == '--flagfile' or current_arg == '-flagfile':
1203          if not rest_of_args:
1204            raise _exceptions.IllegalFlagValueError(
1205                '--flagfile with no argument')
1206          flag_filename = os.path.expanduser(rest_of_args[0])
1207          rest_of_args = rest_of_args[1:]
1208        else:
1209          # This handles the case of (-)-flagfile=foo.
1210          flag_filename = self._extract_filename(current_arg)
1211        new_argv.extend(self._get_flag_file_lines(flag_filename))
1212      else:
1213        new_argv.append(current_arg)
1214        # Stop parsing after '--', like getopt and gnu_getopt.
1215        if current_arg == '--':
1216          break
1217        # Stop parsing after a non-flag, like getopt.
1218        if not current_arg.startswith('-'):
1219          if not force_gnu and not self.__dict__['__use_gnu_getopt']:
1220            break
1221        else:
1222          if ('=' not in current_arg and rest_of_args and
1223              not rest_of_args[0].startswith('-')):
1224            # If this is an occurrence of a legitimate --x y, skip the value
1225            # so that it won't be mistaken for a standalone arg.
1226            fl = self._flags()
1227            name = current_arg.lstrip('-')
1228            if name in fl and not fl[name].boolean:
1229              current_arg = rest_of_args[0]
1230              rest_of_args = rest_of_args[1:]
1231              new_argv.append(current_arg)
1232
1233    if rest_of_args:
1234      new_argv.extend(rest_of_args)
1235
1236    return new_argv
1237
1238  def flags_into_string(self) -> Text:
1239    """Returns a string with the flags assignments from this FlagValues object.
1240
1241    This function ignores flags whose value is None.  Each flag
1242    assignment is separated by a newline.
1243
1244    NOTE: MUST mirror the behavior of the C++ CommandlineFlagsIntoString
1245    from https://github.com/gflags/gflags.
1246
1247    Returns:
1248      str, the string with the flags assignments from this FlagValues object.
1249      The flags are ordered by (module_name, flag_name).
1250    """
1251    module_flags = sorted(self.flags_by_module_dict().items())
1252    s = ''
1253    for unused_module_name, flags in module_flags:
1254      flags = sorted(flags, key=lambda f: f.name)
1255      for flag in flags:
1256        if flag.value is not None:
1257          s += flag.serialize() + '\n'
1258    return s
1259
1260  def append_flags_into_file(self, filename: Text) -> None:
1261    """Appends all flags assignments from this FlagInfo object to a file.
1262
1263    Output will be in the format of a flagfile.
1264
1265    NOTE: MUST mirror the behavior of the C++ AppendFlagsIntoFile
1266    from https://github.com/gflags/gflags.
1267
1268    Args:
1269      filename: str, name of the file.
1270    """
1271    with open(filename, 'a') as out_file:
1272      out_file.write(self.flags_into_string())
1273
1274  def write_help_in_xml_format(self, outfile: Optional[TextIO] = None) -> None:
1275    """Outputs flag documentation in XML format.
1276
1277    NOTE: We use element names that are consistent with those used by
1278    the C++ command-line flag library, from
1279    https://github.com/gflags/gflags.
1280    We also use a few new elements (e.g., <key>), but we do not
1281    interfere / overlap with existing XML elements used by the C++
1282    library.  Please maintain this consistency.
1283
1284    Args:
1285      outfile: File object we write to.  Default None means sys.stdout.
1286    """
1287    doc = minidom.Document()
1288    all_flag = doc.createElement('AllFlags')
1289    doc.appendChild(all_flag)
1290
1291    all_flag.appendChild(
1292        _helpers.create_xml_dom_element(doc, 'program',
1293                                        os.path.basename(sys.argv[0])))
1294
1295    usage_doc = sys.modules['__main__'].__doc__
1296    if not usage_doc:
1297      usage_doc = '\nUSAGE: %s [flags]\n' % sys.argv[0]
1298    else:
1299      usage_doc = usage_doc.replace('%s', sys.argv[0])
1300    all_flag.appendChild(
1301        _helpers.create_xml_dom_element(doc, 'usage', usage_doc))
1302
1303    # Get list of key flags for the main module.
1304    key_flags = self.get_key_flags_for_module(sys.argv[0])
1305
1306    # Sort flags by declaring module name and next by flag name.
1307    flags_by_module = self.flags_by_module_dict()
1308    all_module_names = list(flags_by_module.keys())
1309    all_module_names.sort()
1310    for module_name in all_module_names:
1311      flag_list = [(f.name, f) for f in flags_by_module[module_name]]
1312      flag_list.sort()
1313      for unused_flag_name, flag in flag_list:
1314        is_key = flag in key_flags
1315        all_flag.appendChild(
1316            flag._create_xml_dom_element(  # pylint: disable=protected-access
1317                doc,
1318                module_name,
1319                is_key=is_key))
1320
1321    outfile = outfile or sys.stdout
1322    outfile.write(
1323        doc.toprettyxml(indent='  ', encoding='utf-8').decode('utf-8'))
1324    outfile.flush()
1325
1326  def _check_method_name_conflicts(self, name: str, flag: Flag):
1327    if flag.allow_using_method_names:
1328      return
1329    short_name = flag.short_name
1330    flag_names = {name} if short_name is None else {name, short_name}
1331    for flag_name in flag_names:
1332      if flag_name in self.__dict__['__banned_flag_names']:
1333        raise _exceptions.FlagNameConflictsWithMethodError(
1334            'Cannot define a flag named "{name}". It conflicts with a method '
1335            'on class "{class_name}". To allow defining it, use '
1336            'allow_using_method_names and access the flag value with '
1337            "FLAGS['{name}'].value. FLAGS.{name} returns the method, "
1338            'not the flag value.'.format(
1339                name=flag_name, class_name=type(self).__name__))
1340
1341
1342FLAGS = FlagValues()
1343
1344
1345class FlagHolder(Generic[_T]):
1346  """Holds a defined flag.
1347
1348  This facilitates a cleaner api around global state. Instead of::
1349
1350      flags.DEFINE_integer('foo', ...)
1351      flags.DEFINE_integer('bar', ...)
1352
1353      def method():
1354        # prints parsed value of 'bar' flag
1355        print(flags.FLAGS.foo)
1356        # runtime error due to typo or possibly bad coding style.
1357        print(flags.FLAGS.baz)
1358
1359  it encourages code like::
1360
1361      _FOO_FLAG = flags.DEFINE_integer('foo', ...)
1362      _BAR_FLAG = flags.DEFINE_integer('bar', ...)
1363
1364      def method():
1365        print(_FOO_FLAG.value)
1366        print(_BAR_FLAG.value)
1367
1368  since the name of the flag appears only once in the source code.
1369  """
1370
1371  value: _T
1372
1373  def __init__(
1374      self,
1375      flag_values: FlagValues,
1376      flag: Flag[_T],
1377      ensure_non_none_value: bool = False,
1378  ):
1379    """Constructs a FlagHolder instance providing typesafe access to flag.
1380
1381    Args:
1382      flag_values: The container the flag is registered to.
1383      flag: The flag object for this flag.
1384      ensure_non_none_value: Is the value of the flag allowed to be None.
1385    """
1386    self._flagvalues = flag_values
1387    # We take the entire flag object, but only keep the name. Why?
1388    # - We want FlagHolder[T] to be generic container
1389    # - flag_values contains all flags, so has no reference to T.
1390    # - typecheckers don't like to see a generic class where none of the ctor
1391    #   arguments refer to the generic type.
1392    self._name = flag.name
1393    # We intentionally do NOT check if the default value is None.
1394    # This allows future use of this for "required flags with None default"
1395    self._ensure_non_none_value = ensure_non_none_value
1396
1397  def __eq__(self, other):
1398    raise TypeError(
1399        "unsupported operand type(s) for ==: '{0}' and '{1}' "
1400        "(did you mean to use '{0}.value' instead?)".format(
1401            type(self).__name__, type(other).__name__))
1402
1403  def __bool__(self):
1404    raise TypeError(
1405        "bool() not supported for instances of type '{0}' "
1406        "(did you mean to use '{0}.value' instead?)".format(
1407            type(self).__name__))
1408
1409  __nonzero__ = __bool__
1410
1411  @property
1412  def name(self) -> Text:
1413    return self._name
1414
1415  @property
1416  def value(self) -> _T:
1417    """Returns the value of the flag.
1418
1419    If ``_ensure_non_none_value`` is ``True``, then return value is not
1420    ``None``.
1421
1422    Raises:
1423      UnparsedFlagAccessError: if flag parsing has not finished.
1424      IllegalFlagValueError: if value is None unexpectedly.
1425    """
1426    val = getattr(self._flagvalues, self._name)
1427    if self._ensure_non_none_value and val is None:
1428      raise _exceptions.IllegalFlagValueError(
1429          'Unexpected None value for flag %s' % self._name)
1430    return val
1431
1432  @property
1433  def default(self) -> _T:
1434    """Returns the default value of the flag."""
1435    return self._flagvalues[self._name].default
1436
1437  @property
1438  def present(self) -> bool:
1439    """Returns True if the flag was parsed from command-line flags."""
1440    return bool(self._flagvalues[self._name].present)
1441
1442  def serialize(self) -> Text:
1443    """Returns a serialized representation of the flag."""
1444    return self._flagvalues[self._name].serialize()
1445
1446
1447def resolve_flag_ref(
1448    flag_ref: Union[str, FlagHolder], flag_values: FlagValues
1449) -> Tuple[str, FlagValues]:
1450  """Helper to validate and resolve a flag reference argument."""
1451  if isinstance(flag_ref, FlagHolder):
1452    new_flag_values = flag_ref._flagvalues  # pylint: disable=protected-access
1453    if flag_values != FLAGS and flag_values != new_flag_values:
1454      raise ValueError(
1455          'flag_values must not be customized when operating on a FlagHolder')
1456    return flag_ref.name, new_flag_values
1457  return flag_ref, flag_values
1458
1459
1460def resolve_flag_refs(
1461    flag_refs: Sequence[Union[str, FlagHolder]], flag_values: FlagValues
1462) -> Tuple[List[str], FlagValues]:
1463  """Helper to validate and resolve flag reference list arguments."""
1464  fv = None
1465  names = []
1466  for ref in flag_refs:
1467    if isinstance(ref, FlagHolder):
1468      newfv = ref._flagvalues  # pylint: disable=protected-access
1469      name = ref.name
1470    else:
1471      newfv = flag_values
1472      name = ref
1473    if fv and fv != newfv:
1474      raise ValueError(
1475          'multiple FlagValues instances used in invocation. '
1476          'FlagHolders must be registered to the same FlagValues instance as '
1477          'do flag names, if provided.')
1478    fv = newfv
1479    names.append(name)
1480  return names, fv
1481