aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.readthedocs.yaml21
-rw-r--r--CHANGELOG.md48
-rw-r--r--METADATA16
-rw-r--r--absl/app.py18
-rw-r--r--absl/command_name.py4
-rw-r--r--absl/flags/__init__.py80
-rw-r--r--absl/flags/_argument_parser.py18
-rw-r--r--absl/flags/_defines.py195
-rw-r--r--absl/flags/_defines.pyi39
-rw-r--r--absl/flags/_exceptions.py22
-rw-r--r--absl/flags/_flag.py85
-rw-r--r--absl/flags/_flagvalues.py139
-rw-r--r--absl/flags/_helpers.py62
-rw-r--r--absl/flags/_validators.py135
-rw-r--r--absl/flags/_validators_classes.py6
-rw-r--r--absl/flags/argparse_flags.py32
-rw-r--r--absl/flags/tests/_flag_test.py4
-rw-r--r--absl/flags/tests/_helpers_test.py19
-rw-r--r--absl/flags/tests/_validators_test.py249
-rw-r--r--absl/flags/tests/argparse_flags_test_helper.py4
-rw-r--r--absl/flags/tests/flags_formatting_test.py4
-rw-r--r--absl/flags/tests/flags_test.py99
-rw-r--r--absl/flags/tests/flags_unicode_literals_test.py5
-rw-r--r--absl/flags/tests/module_bar.py4
-rw-r--r--absl/flags/tests/module_baz.py4
-rw-r--r--absl/flags/tests/module_foo.py4
-rw-r--r--absl/logging/__init__.py88
-rw-r--r--absl/logging/converter.py51
-rw-r--r--absl/logging/tests/converter_test.py4
-rw-r--r--absl/logging/tests/verbosity_flag_test.py4
-rw-r--r--absl/testing/BUILD1
-rw-r--r--absl/testing/_bazelize_command.py4
-rw-r--r--absl/testing/_pretty_print_reporter.py4
-rw-r--r--absl/testing/absltest.py239
-rw-r--r--absl/testing/flagsaver.py80
-rw-r--r--absl/testing/parameterized.py214
-rw-r--r--absl/testing/tests/absltest_fail_fast_test.py4
-rw-r--r--absl/testing/tests/absltest_fail_fast_test_helper.py4
-rw-r--r--absl/testing/tests/absltest_filtering_test.py4
-rw-r--r--absl/testing/tests/absltest_filtering_test_helper.py4
-rw-r--r--absl/testing/tests/absltest_randomization_test.py4
-rw-r--r--absl/testing/tests/absltest_randomization_testcase.py4
-rw-r--r--absl/testing/tests/absltest_sharding_test.py4
-rw-r--r--absl/testing/tests/absltest_sharding_test_helper.py4
-rw-r--r--absl/testing/tests/absltest_test.py51
-rw-r--r--absl/testing/tests/absltest_test_helper.py68
-rw-r--r--absl/testing/tests/flagsaver_test.py4
-rw-r--r--absl/testing/tests/xml_reporter_helper_test.py4
-rw-r--r--absl/testing/tests/xml_reporter_test.py10
-rw-r--r--absl/testing/xml_reporter.py10
-rw-r--r--absl/tests/app_test_helper.py4
-rw-r--r--docs/Makefile37
-rw-r--r--docs/source/conf.py60
-rw-r--r--docs/source/index.rst16
-rw-r--r--docs/source/readme_link.rst1
-rw-r--r--setup.py7
-rw-r--r--smoke_tests/sample_app.py4
-rw-r--r--smoke_tests/sample_test.py4
58 files changed, 1487 insertions, 830 deletions
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..e7b7d9b
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,21 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the version of Python and other tools you might need
+python:
+ version: "3"
+ install:
+ - method: pip
+ path: .
+ extra_requirements:
+ - m2r2
+ - sphinxcontrib-apidoc
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ builder: html
+ configuration: docs/source/conf.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c36751..ae82a55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,50 @@ The format is based on [Keep a Changelog](https://keepachangelog.com).
Nothing notable unreleased.
+## 1.3.0 (2022-10-11)
+
+### Added
+
+* (flags) Added a new `absl.flags.set_default` function that updates the flag
+ default for a provided `FlagHolder`. This parallels the
+ `absl.flags.FlagValues.set_default` interface which takes a flag name.
+* (flags) The following functions now also accept `FlagHolder` instance(s) in
+ addition to flag name(s) as their first positional argument:
+ - `flags.register_validator`
+ - `flags.validator`
+ - `flags.register_multi_flags_validator`
+ - `flags.multi_flags_validator`
+ - `flags.mark_flag_as_required`
+ - `flags.mark_flags_as_required`
+ - `flags.mark_flags_as_mutual_exclusive`
+ - `flags.mark_bool_flags_as_mutual_exclusive`
+ - `flags.declare_key_flag`
+
+### Changed
+
+* (testing) Assertions `assertRaisesWithPredicateMatch` and
+ `assertRaisesWithLiteralMatch` now capture the raised `Exception` for
+ further analysis when used as a context manager.
+* (testing) TextAndXMLTestRunner now produces time duration values with
+ millisecond precision in XML test result output.
+* (flags) Keyword access to `flag_name` arguments in the following functions
+ is deprecated. This parameter will be renamed in a future 2.0.0 release.
+ - `flags.register_validator`
+ - `flags.validator`
+ - `flags.register_multi_flags_validator`
+ - `flags.multi_flags_validator`
+ - `flags.mark_flag_as_required`
+ - `flags.mark_flags_as_required`
+ - `flags.mark_flags_as_mutual_exclusive`
+ - `flags.mark_bool_flags_as_mutual_exclusive`
+ - `flags.declare_key_flag`
+
+## 1.2.0 (2022-07-18)
+
+### Fixed
+
+* Fixed a crash in Python 3.11 when `TempFileCleanup.SUCCESS` is used.
+
## 1.1.0 (2022-06-01)
* `Flag` instances now raise an error if used in a bool context. This prevents
@@ -235,8 +279,8 @@ Nothing notable unreleased.
* (flags) An empty --flagfile value (e.g. "--flagfile=" or "--flagfile=''"
doesn't raise an error; its not just ignored. This matches Abseil C++
behavior.
-* (bazel) Building with Bazel 0.2.0 works without extra incompatibility disable
- build flags.
+* (bazel) Building with Bazel 0.2.0 works without extra incompatibility
+ disable build flags.
### Changed
diff --git a/METADATA b/METADATA
index ffbb5ad..78080bb 100644
--- a/METADATA
+++ b/METADATA
@@ -1,7 +1,9 @@
-name: "abseil-py"
-description:
- ""
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update python/absl-py
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+name: "abseil-py"
+description: ""
third_party {
url {
type: HOMEPAGE
@@ -11,7 +13,11 @@ third_party {
type: GIT
value: "https://github.com/abseil/abseil-py.git"
}
- version: "v1.1.0"
- last_upgrade_date { year: 2022 month: 6 day: 8 }
+ version: "v1.3.0"
license_type: NOTICE
+ last_upgrade_date {
+ year: 2022
+ month: 11
+ day: 2
+ }
}
diff --git a/absl/app.py b/absl/app.py
index 037f75c..43d8ca3 100644
--- a/absl/app.py
+++ b/absl/app.py
@@ -14,8 +14,8 @@
"""Generic entry point for Abseil Python applications.
-To use this module, define a 'main' function with a single 'argv' argument and
-call app.run(main). For example:
+To use this module, define a ``main`` function with a single ``argv`` argument
+and call ``app.run(main)``. For example::
def main(argv):
if len(argv) > 1:
@@ -25,10 +25,6 @@ call app.run(main). For example:
app.run(main)
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import collections
import errno
import os
@@ -340,14 +336,14 @@ _init_callbacks = collections.deque()
def call_after_init(callback):
"""Calls the given callback only once ABSL has finished initialization.
- If ABSL has already finished initialization when `call_after_init` is
+ If ABSL has already finished initialization when ``call_after_init`` is
called then the callback is executed immediately, otherwise `callback` is
- stored to be executed after `app.run` has finished initializing (aka. just
+ stored to be executed after ``app.run`` has finished initializing (aka. just
before the main function is called).
- If called after `app.run`, this is equivalent to calling `callback()` in the
- caller thread. If called before `app.run`, callbacks are run sequentially (in
- an undefined order) in the same thread as `app.run`.
+ If called after ``app.run``, this is equivalent to calling ``callback()`` in
+ the caller thread. If called before ``app.run``, callbacks are run
+ sequentially (in an undefined order) in the same thread as ``app.run``.
Args:
callback: a callable to be called once ABSL has finished initialization.
diff --git a/absl/command_name.py b/absl/command_name.py
index 3bf9fad..1996493 100644
--- a/absl/command_name.py
+++ b/absl/command_name.py
@@ -14,10 +14,6 @@
"""A tiny stand alone library to change the kernel process name on Linux."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import sys
diff --git a/absl/flags/__init__.py b/absl/flags/__init__.py
index e6014a6..6d8ba03 100644
--- a/absl/flags/__init__.py
+++ b/absl/flags/__init__.py
@@ -40,6 +40,81 @@ from absl.flags import _flagvalues
from absl.flags import _helpers
from absl.flags import _validators
+__all__ = (
+ 'DEFINE',
+ 'DEFINE_flag',
+ 'DEFINE_string',
+ 'DEFINE_boolean',
+ 'DEFINE_bool',
+ 'DEFINE_float',
+ 'DEFINE_integer',
+ 'DEFINE_enum',
+ 'DEFINE_enum_class',
+ 'DEFINE_list',
+ 'DEFINE_spaceseplist',
+ 'DEFINE_multi',
+ 'DEFINE_multi_string',
+ 'DEFINE_multi_integer',
+ 'DEFINE_multi_float',
+ 'DEFINE_multi_enum',
+ 'DEFINE_multi_enum_class',
+ 'DEFINE_alias',
+ # Flag validators.
+ 'register_validator',
+ 'validator',
+ 'register_multi_flags_validator',
+ 'multi_flags_validator',
+ 'mark_flag_as_required',
+ 'mark_flags_as_required',
+ 'mark_flags_as_mutual_exclusive',
+ 'mark_bool_flags_as_mutual_exclusive',
+ # Flag modifiers.
+ 'set_default',
+ # Key flag related functions.
+ 'declare_key_flag',
+ 'adopt_module_key_flags',
+ 'disclaim_key_flags',
+ # Module exceptions.
+ 'Error',
+ 'CantOpenFlagFileError',
+ 'DuplicateFlagError',
+ 'IllegalFlagValueError',
+ 'UnrecognizedFlagError',
+ 'UnparsedFlagAccessError',
+ 'ValidationError',
+ 'FlagNameConflictsWithMethodError',
+ # Public classes.
+ 'Flag',
+ 'BooleanFlag',
+ 'EnumFlag',
+ 'EnumClassFlag',
+ 'MultiFlag',
+ 'MultiEnumClassFlag',
+ 'FlagHolder',
+ 'FlagValues',
+ 'ArgumentParser',
+ 'BooleanParser',
+ 'EnumParser',
+ 'EnumClassParser',
+ 'ArgumentSerializer',
+ 'FloatParser',
+ 'IntegerParser',
+ 'BaseListParser',
+ 'ListParser',
+ 'ListSerializer',
+ 'EnumClassListSerializer',
+ 'CsvListSerializer',
+ 'WhitespaceSeparatedListParser',
+ 'EnumClassSerializer',
+ # Helper functions.
+ 'get_help_width',
+ 'text_wrap',
+ 'flag_dict_to_args',
+ 'doc_to_help',
+ # The global FlagValues instance.
+ 'FLAGS',
+)
+
# Initialize the FLAGS_MODULE as early as possible.
# It's only used by adopt_module_key_flags to take SPECIAL_FLAGS into account.
_helpers.FLAGS_MODULE = sys.modules[__name__]
@@ -79,6 +154,9 @@ mark_flags_as_required = _validators.mark_flags_as_required
mark_flags_as_mutual_exclusive = _validators.mark_flags_as_mutual_exclusive
mark_bool_flags_as_mutual_exclusive = _validators.mark_bool_flags_as_mutual_exclusive
+# Flag modifiers.
+set_default = _defines.set_default
+
# Key flag related functions.
declare_key_flag = _defines.declare_key_flag
adopt_module_key_flags = _defines.adopt_module_key_flags
@@ -141,5 +219,5 @@ DEFINE_string('undefok', '',
'arguments MUST use the --flag=value format.',
_helpers.SPECIAL_FLAGS) # pytype: disable=wrong-arg-types
-# The global FlagValues instance.
+#: The global FlagValues instance.
FLAGS = _flagvalues.FLAGS
diff --git a/absl/flags/_argument_parser.py b/absl/flags/_argument_parser.py
index 9c6c8c6..2c4de9b 100644
--- a/absl/flags/_argument_parser.py
+++ b/absl/flags/_argument_parser.py
@@ -93,9 +93,9 @@ class _ArgumentParserCache(type):
class ArgumentParser(metaclass=_ArgumentParserCache):
"""Base class used to parse and convert arguments.
- The parse() method checks to make sure that the string argument is a
+ The :meth:`parse` method checks to make sure that the string argument is a
legal value and convert it to a native type. If the value cannot be
- converted, it should throw a 'ValueError' exception with a human
+ converted, it should throw a ``ValueError`` exception with a human
readable explanation of why the value is illegal.
Subclasses should also define a syntactic_help string which may be
@@ -147,7 +147,7 @@ class ArgumentSerializer(object):
def serialize(self, value):
"""Returns a serialized string of the value."""
- return _helpers.str_or_unicode(value)
+ return str(value)
class NumericParser(ArgumentParser):
@@ -454,11 +454,11 @@ class ListSerializer(ArgumentSerializer):
def serialize(self, value):
"""See base class."""
- return self.list_sep.join([_helpers.str_or_unicode(x) for x in value])
+ return self.list_sep.join([str(x) for x in value])
class EnumClassListSerializer(ListSerializer):
- """A serializer for MultiEnumClass flags.
+ """A serializer for :class:`MultiEnumClass` flags.
This serializer simply joins the output of `EnumClassSerializer` using a
provided separator.
@@ -498,7 +498,7 @@ class CsvListSerializer(ArgumentSerializer):
# We need the returned value to be pure ascii or Unicodes so that
# when the xml help is generated they are usefully encodable.
- return _helpers.str_or_unicode(serialized_value)
+ return str(serialized_value)
class EnumClassSerializer(ArgumentSerializer):
@@ -514,16 +514,16 @@ class EnumClassSerializer(ArgumentSerializer):
def serialize(self, value):
"""Returns a serialized string of the Enum class value."""
- as_string = _helpers.str_or_unicode(value.name)
+ as_string = str(value.name)
return as_string.lower() if self._lowercase else as_string
class BaseListParser(ArgumentParser):
"""Base class for a parser of lists of strings.
- To extend, inherit from this class; from the subclass __init__, call
+ To extend, inherit from this class; from the subclass ``__init__``, call::
- BaseListParser.__init__(self, token, name)
+ super().__init__(token, name)
where token is a character used to tokenize, and name is a description
of the separator.
diff --git a/absl/flags/_defines.py b/absl/flags/_defines.py
index 4494c3b..dce53ea 100644
--- a/absl/flags/_defines.py
+++ b/absl/flags/_defines.py
@@ -17,10 +17,6 @@ Do NOT import this module directly. Import the flags package and use the
aliases defined at the package level instead.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import sys
import types
@@ -81,22 +77,22 @@ def DEFINE( # pylint: disable=invalid-name
NOTE: in the docstrings of all DEFINE* functions, "registers" is short
for "creates a new flag and registers it".
- Auxiliary function: clients should use the specialized DEFINE_<type>
+ Auxiliary function: clients should use the specialized ``DEFINE_<type>``
function instead.
Args:
- parser: ArgumentParser, used to parse the flag arguments.
+ parser: :class:`ArgumentParser`, used to parse the flag arguments.
name: str, the flag name.
default: The default value of the flag.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
- serializer: ArgumentSerializer, the flag serializer instance.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
+ serializer: :class:`ArgumentSerializer`, the flag serializer instance.
module_name: str, the name of the Python module declaring this flag. If not
provided, it will be computed using the stack trace of this call.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: dict, the extra keyword args that are passed to Flag __init__.
+ **args: dict, the extra keyword args that are passed to ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -111,19 +107,19 @@ def DEFINE_flag( # pylint: disable=invalid-name
flag_values=_flagvalues.FLAGS,
module_name=None,
required=False):
- """Registers a 'Flag' object with a 'FlagValues' object.
+ """Registers a :class:`Flag` object with a :class:`FlagValues` object.
- By default, the global FLAGS 'FlagValue' object is used.
+ By default, the global :const:`FLAGS` ``FlagValue`` object is used.
Typical users will use one of the more specialized DEFINE_xxx
- functions, such as DEFINE_string or DEFINE_integer. But developers
- who need to create Flag objects themselves should use this function
- to register their flags.
+ functions, such as :func:`DEFINE_string` or :func:`DEFINE_integer`. But
+ developers who need to create :class:`Flag` objects themselves should use
+ this function to register their flags.
Args:
- flag: Flag, a flag that is key to the module.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag: :class:`Flag`, a flag that is key to the module.
+ flag_values: :class:`FlagValues`, the ``FlagValues`` instance with which the
+ flag will be registered. This should almost never need to be overridden.
module_name: str, the name of the Python module declaring this flag. If not
provided, it will be computed using the stack trace of this call.
required: bool, is this a required flag. This must be used as a keyword
@@ -152,6 +148,23 @@ def DEFINE_flag( # pylint: disable=invalid-name
fv, flag, ensure_non_none_value=ensure_non_none_value)
+def set_default(flag_holder, value):
+ """Changes the default value of the provided 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:
+ flag_holder: FlagHolder, the flag to modify.
+ value: The new default value.
+
+ Raises:
+ IllegalFlagValueError: Raised when value is not valid.
+ """
+ flag_holder._flagvalues.set_default(flag_holder.name, value) # pylint: disable=protected-access
+
+
def _internal_declare_key_flags(flag_names,
flag_values=_flagvalues.FLAGS,
key_flag_values=None):
@@ -161,16 +174,15 @@ def _internal_declare_key_flags(flag_names,
adopt_module_key_flags instead.
Args:
- flag_names: [str], a list of strings that are names of already-registered
- Flag objects.
- flag_values: FlagValues, the FlagValues instance with which the flags listed
- in flag_names have registered (the value of the flag_values argument from
- the DEFINE_* calls that defined those flags). This should almost never
+ flag_names: [str], a list of names of already-registered Flag objects.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flags listed in flag_names have registered (the value of the flag_values
+ argument from the ``DEFINE_*`` calls that defined those flags). This
+ should almost never need to be overridden.
+ key_flag_values: :class:`FlagValues`, the FlagValues instance that (among
+ possibly many other things) keeps track of the key flags for each module.
+ Default ``None`` means "same as flag_values". This should almost never
need to be overridden.
- key_flag_values: FlagValues, the FlagValues instance that (among possibly
- many other things) keeps track of the key flags for each module. Default
- None means "same as flag_values". This should almost never need to be
- overridden.
Raises:
UnrecognizedFlagError: Raised when the flag is not defined.
@@ -180,8 +192,7 @@ def _internal_declare_key_flags(flag_names,
module = _helpers.get_calling_module()
for flag_name in flag_names:
- flag = flag_values[flag_name]
- key_flag_values.register_key_flag_for_module(module, flag)
+ key_flag_values.register_key_flag_for_module(module, flag_values[flag_name])
def declare_key_flag(flag_name, flag_values=_flagvalues.FLAGS):
@@ -193,20 +204,23 @@ def declare_key_flag(flag_name, flag_values=_flagvalues.FLAGS):
main module are listed (instead of all flags, as in the case of
--helpfull).
- Sample usage:
+ Sample usage::
- flags.declare_key_flag('flag_1')
+ flags.declare_key_flag('flag_1')
Args:
- flag_name: str, the name of an already declared flag. (Redeclaring flags as
- key, including flags implicitly key because they were declared in this
- module, is a no-op.)
- flag_values: FlagValues, the FlagValues instance in which the flag will be
- declared as a key flag. This should almost never need to be overridden.
+ flag_name: str | :class:`FlagHolder`, the name or holder of an already
+ declared flag. (Redeclaring flags as key, including flags implicitly key
+ because they were declared in this module, is a no-op.)
+ Positional-only parameter.
+ flag_values: :class:`FlagValues`, the FlagValues instance in which the
+ flag will be declared as a key flag. This should almost never need to be
+ overridden.
Raises:
ValueError: Raised if flag_name not defined as a Python flag.
"""
+ flag_name, flag_values = _flagvalues.resolve_flag_ref(flag_name, flag_values)
if flag_name in _helpers.SPECIAL_FLAGS:
# Take care of the special flags, e.g., --flagfile, --undefok.
# These flags are defined in SPECIAL_FLAGS, and are treated
@@ -229,8 +243,9 @@ def adopt_module_key_flags(module, flag_values=_flagvalues.FLAGS):
Args:
module: module, the module object from which all key flags will be declared
as key flags to the current module.
- flag_values: FlagValues, the FlagValues instance in which the flags will be
- declared as key flags. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance in which the
+ flags will be declared as key flags. This should almost never need to be
+ overridden.
Raises:
Error: Raised when given an argument that is a module name (a string),
@@ -316,13 +331,13 @@ def DEFINE_boolean( # pylint: disable=invalid-name,redefined-builtin
name: str, the flag name.
default: bool|str|None, the default value of the flag.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
module_name: str, the name of the Python module declaring this flag. If not
provided, it will be computed using the stack trace of this call.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: dict, the extra keyword args that are passed to Flag __init__.
+ **args: dict, the extra keyword args that are passed to ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -343,7 +358,7 @@ def DEFINE_float( # pylint: disable=invalid-name,redefined-builtin
**args):
"""Registers a flag whose value must be a float.
- If lower_bound or upper_bound are set, then this flag must be
+ If ``lower_bound`` or ``upper_bound`` are set, then this flag must be
within the given range.
Args:
@@ -352,11 +367,11 @@ def DEFINE_float( # pylint: disable=invalid-name,redefined-builtin
help: str, the help message.
lower_bound: float, min value of the flag.
upper_bound: float, max value of the flag.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: dict, the extra keyword args that are passed to DEFINE.
+ **args: dict, the extra keyword args that are passed to :func:`DEFINE`.
Returns:
a handle to defined flag.
@@ -387,7 +402,7 @@ def DEFINE_integer( # pylint: disable=invalid-name,redefined-builtin
**args):
"""Registers a flag whose value must be an integer.
- If lower_bound, or upper_bound are set, then this flag must be
+ If ``lower_bound``, or ``upper_bound`` are set, then this flag must be
within the given range.
Args:
@@ -396,11 +411,11 @@ def DEFINE_integer( # pylint: disable=invalid-name,redefined-builtin
help: str, the help message.
lower_bound: int, min value of the flag.
upper_bound: int, max value of the flag.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: dict, the extra keyword args that are passed to DEFINE.
+ **args: dict, the extra keyword args that are passed to :func:`DEFINE`.
Returns:
a handle to defined flag.
@@ -440,13 +455,13 @@ def DEFINE_enum( # pylint: disable=invalid-name,redefined-builtin
enum_values: [str], a non-empty list of strings with the possible values for
the flag.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
module_name: str, the name of the Python module declaring this flag. If not
provided, it will be computed using the stack trace of this call.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: dict, the extra keyword args that are passed to Flag __init__.
+ **args: dict, the extra keyword args that are passed to ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -473,15 +488,15 @@ def DEFINE_enum_class( # pylint: disable=invalid-name,redefined-builtin
default: Enum|str|None, the default value of the flag.
enum_class: class, the Enum class with all the possible values for the flag.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
module_name: str, the name of the Python module declaring this flag. If not
provided, it will be computed using the stack trace of this call.
case_sensitive: bool, whether to map strings to members of the enum_class
without considering case.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: dict, the extra keyword args that are passed to Flag __init__.
+ **args: dict, the extra keyword args that are passed to ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -511,12 +526,12 @@ def DEFINE_list( # pylint: disable=invalid-name,redefined-builtin
name: str, the flag name.
default: list|str|None, the default value of the flag.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -553,12 +568,12 @@ def DEFINE_spaceseplist( # pylint: disable=invalid-name,redefined-builtin
comma_compat: bool - Whether to support comma as an additional separator. If
false then only whitespace is supported. This is intended only for
backwards compatibility with flags that used to be comma-separated.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -605,14 +620,14 @@ def DEFINE_multi( # pylint: disable=invalid-name,redefined-builtin
over to create a shallow copy of the values. If it is None, it is left
as-is.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
module_name: A string, the name of the Python module declaring this flag. If
not provided, it will be computed using the stack trace of this call.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -640,14 +655,14 @@ def DEFINE_multi_string( # pylint: disable=invalid-name,redefined-builtin
Args:
name: str, the flag name.
default: Union[Iterable[Text], Text, None], the default value of the flag;
- see `DEFINE_multi`.
+ see :func:`DEFINE_multi`.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -688,12 +703,12 @@ def DEFINE_multi_integer( # pylint: disable=invalid-name,redefined-builtin
help: str, the help message.
lower_bound: int, min values of the flag.
upper_bound: int, max values of the flag.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -734,12 +749,12 @@ def DEFINE_multi_float( # pylint: disable=invalid-name,redefined-builtin
help: str, the help message.
lower_bound: float, min values of the flag.
upper_bound: float, max values of the flag.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -780,13 +795,13 @@ def DEFINE_multi_enum( # pylint: disable=invalid-name,redefined-builtin
enum_values: [str], a non-empty list of strings with the possible values for
the flag.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
case_sensitive: Whether or not the enum is to be case-sensitive.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -828,16 +843,16 @@ def DEFINE_multi_enum_class( # pylint: disable=invalid-name,redefined-builtin
within the iterable will be converted to the equivalent Enum objects.
enum_class: class, the Enum class with all the possible values for the flag.
help: str, the help message.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
module_name: A string, the name of the Python module declaring this flag. If
not provided, it will be computed using the stack trace of this call.
case_sensitive: bool, whether to map strings to members of the enum_class
without considering case.
required: bool, is this a required flag. This must be used as a keyword
argument.
- **args: Dictionary with extra keyword args that are passed to the Flag
- __init__.
+ **args: Dictionary with extra keyword args that are passed to the
+ ``Flag.__init__``.
Returns:
a handle to defined flag.
@@ -861,8 +876,8 @@ def DEFINE_alias( # pylint: disable=invalid-name
Args:
name: str, the flag name.
original_name: str, the original flag name.
- flag_values: FlagValues, the FlagValues instance with which the flag will be
- registered. This should almost never need to be overridden.
+ flag_values: :class:`FlagValues`, the FlagValues instance with which the
+ flag will be registered. This should almost never need to be overridden.
module_name: A string, the name of the module that defines this flag.
Returns:
diff --git a/absl/flags/_defines.pyi b/absl/flags/_defines.pyi
index 1f482a2..9bc8067 100644
--- a/absl/flags/_defines.pyi
+++ b/absl/flags/_defines.pyi
@@ -576,7 +576,23 @@ def DEFINE_multi_enum(
@overload
def DEFINE_multi_enum_class(
name: Text,
- default: Union[None, Iterable[_ET], _ET, Text],
+ # This is separate from `Union[None, _ET, Text]` to avoid a Pytype issue
+ # inferring the return value to FlagHolder[List[Union[_ET, enum.Enum]]]
+ # when an iterable of concrete enum subclasses are used.
+ default: Iterable[_ET],
+ enum_class: Type[_ET],
+ help: Text,
+ flag_values: _flagvalues.FlagValues = ...,
+ module_name: Optional[Text] = ...,
+ *,
+ required: Literal[True],
+ **args: Any) -> _flagvalues.FlagHolder[List[_ET]]:
+ ...
+
+@overload
+def DEFINE_multi_enum_class(
+ name: Text,
+ default: Union[None, _ET, Text],
enum_class: Type[_ET],
help: Text,
flag_values: _flagvalues.FlagValues = ...,
@@ -601,7 +617,10 @@ def DEFINE_multi_enum_class(
@overload
def DEFINE_multi_enum_class(
name: Text,
- default: Union[Iterable[_ET], _ET, Text],
+ # This is separate from `Union[None, _ET, Text]` to avoid a Pytype issue
+ # inferring the return value to FlagHolder[List[Union[_ET, enum.Enum]]]
+ # when an iterable of concrete enum subclasses are used.
+ default: Iterable[_ET],
enum_class: Type[_ET],
help: Text,
flag_values: _flagvalues.FlagValues = ...,
@@ -610,6 +629,17 @@ def DEFINE_multi_enum_class(
**args: Any) -> _flagvalues.FlagHolder[List[_ET]]:
...
+@overload
+def DEFINE_multi_enum_class(
+ name: Text,
+ default: Union[_ET, Text],
+ enum_class: Type[_ET],
+ help: Text,
+ flag_values: _flagvalues.FlagValues = ...,
+ module_name: Optional[Text] = ...,
+ required: bool = ...,
+ **args: Any) -> _flagvalues.FlagHolder[List[_ET]]:
+ ...
def DEFINE_alias(
@@ -620,8 +650,11 @@ def DEFINE_alias(
...
+def set_default(flag_holder: _flagvalues.FlagHolder[_T], value: _T) -> None:
+ ...
+
-def declare_key_flag(flag_name: Text,
+def declare_key_flag(flag_name: Union[Text, _flagvalues.FlagHolder],
flag_values: _flagvalues.FlagValues = ...) -> None:
...
diff --git a/absl/flags/_exceptions.py b/absl/flags/_exceptions.py
index 254eb9b..b569d94 100644
--- a/absl/flags/_exceptions.py
+++ b/absl/flags/_exceptions.py
@@ -18,10 +18,6 @@ Do NOT import this module directly. Import the flags package and use the
aliases defined at the package level instead.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import sys
from absl.flags import _helpers
@@ -50,13 +46,13 @@ class DuplicateFlagError(Error):
Args:
flagname: str, the name of the flag being redefined.
- flag_values: FlagValues, the FlagValues instance containing the first
- definition of flagname.
- other_flag_values: FlagValues, if it is not None, it should be the
- FlagValues object where the second definition of flagname occurs.
- If it is None, we assume that we're being called when attempting
- to create the flag a second time, and we use the module calling
- this one as the source of the second definition.
+ flag_values: :class:`FlagValues`, the FlagValues instance containing the
+ first definition of flagname.
+ other_flag_values: :class:`FlagValues`, if it is not None, it should be
+ the FlagValues object where the second definition of flagname occurs.
+ If it is None, we assume that we're being called when attempting to
+ create the flag a second time, and we use the module calling this one
+ as the source of the second definition.
Returns:
An instance of DuplicateFlagError.
@@ -101,7 +97,7 @@ class UnrecognizedFlagError(Error):
class UnparsedFlagAccessError(Error):
- """Raised when accessing the flag value from unparsed FlagValues."""
+ """Raised when accessing the flag value from unparsed :class:`FlagValues`."""
class ValidationError(Error):
@@ -109,4 +105,4 @@ class ValidationError(Error):
class FlagNameConflictsWithMethodError(Error):
- """Raised when a flag name conflicts with FlagValues methods."""
+ """Raised when a flag name conflicts with :class:`FlagValues` methods."""
diff --git a/absl/flags/_flag.py b/absl/flags/_flag.py
index d7ad944..124f137 100644
--- a/absl/flags/_flag.py
+++ b/absl/flags/_flag.py
@@ -31,42 +31,45 @@ from absl.flags import _helpers
class Flag(object):
"""Information about a command-line flag.
- 'Flag' objects define the following fields:
- .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 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 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.
-
- The only public method of a 'Flag' object is parse(), but it is
- typically only called by a 'FlagValues' object. The parse() method is
- a thin wrapper around the '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.
-
- 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
+ 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
@@ -150,7 +153,7 @@ class Flag(object):
return repr('true')
else:
return repr('false')
- return repr(_helpers.str_or_unicode(value))
+ return repr(str(value))
def parse(self, argument):
"""Parses string and sets flag value.
@@ -307,13 +310,13 @@ 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
+ ``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.
+ ``'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): # pylint: disable=redefined-builtin
diff --git a/absl/flags/_flagvalues.py b/absl/flags/_flagvalues.py
index 1b54fb3..937dc6c 100644
--- a/absl/flags/_flagvalues.py
+++ b/absl/flags/_flagvalues.py
@@ -37,36 +37,41 @@ _T = TypeVar('_T')
class FlagValues:
- """Registry of 'Flag' objects.
+ """Registry of :class:`~absl.flags.Flag` objects.
- A 'FlagValues' can then scan command line arguments, passing flag
+ 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
- 'FlagValues' object is needed by an application: flags.FLAGS
+ :class:`FlagValues` object is needed by an application:
+ :const:`FLAGS`.
This class is heavily overloaded:
- 'Flag' objects are registered via __setitem__:
+ :class:`Flag` objects are registered via ``__setitem__``::
+
FLAGS['longname'] = x # register a new flag
- The .value attribute of the registered 'Flag' objects can be accessed
- as attributes of this 'FlagValues' object, through __getattr__. Both
- the long and short name of the original 'Flag' objects can be used to
- access its value:
- FLAGS.longname # parsed flag value
- FLAGS.x # parsed flag value (short name)
+ 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::
- Command line arguments are scanned and passed to the registered '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 Flag objects can be retrieved through the use
- of the dictionary-like operator, __getitem__:
+ 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 'FlagValues' object provides help for all of
- the registered 'Flag' objects.
+ The ``str()`` operator of a :class:`absl.flags.FlagValues` object provides
+ help for all of the registered :class:`~absl.flags.Flag` objects.
"""
# A note on collections.abc.Mapping:
@@ -407,11 +412,7 @@ class FlagValues:
fl = self._flags()
if not isinstance(flag, _flag.Flag):
raise _exceptions.IllegalFlagValueError(flag)
- if str is bytes and isinstance(name, unicode):
- # When using Python 2 with unicode_literals, allow it but encode it
- # into the bytes type we require.
- name = name.encode('utf-8')
- if not isinstance(name, type('')):
+ 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')
@@ -627,7 +628,7 @@ class FlagValues:
TypeError: Raised on passing wrong type of arguments.
ValueError: Raised on flag value parsing error.
"""
- if _helpers.is_bytes_or_string(argv):
+ if isinstance(argv, (str, bytes)):
raise TypeError(
'argv should be a tuple/list of strings, not bytes or string.')
if not argv:
@@ -821,7 +822,7 @@ class FlagValues:
"""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
+ 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
@@ -1001,7 +1002,7 @@ class FlagValues:
def _is_flag_file_directive(self, flag_string):
"""Checks whether flag_string contain a --flagfile=<foo> directive."""
- if isinstance(flag_string, type('')):
+ if isinstance(flag_string, str):
if flag_string.startswith('--flagfile='):
return 1
elif flag_string == '--flagfile':
@@ -1133,14 +1134,15 @@ class FlagValues:
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"
+
+ * 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.
+ * 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.
+ * In a flagfile, a line beginning with # or // is a comment.
+ * Entirely blank lines _should_ be ignored.
"""
rest_of_args = argv
new_argv = []
@@ -1296,29 +1298,25 @@ 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)
- ```
+ 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.
"""
@@ -1364,7 +1362,8 @@ class FlagHolder(Generic[_T]):
def value(self):
"""Returns the value of the flag.
- If _ensure_non_none_value is True, then return value is not None.
+ If ``_ensure_non_none_value`` is ``True``, then return value is not
+ ``None``.
Raises:
UnparsedFlagAccessError: if flag parsing has not finished.
@@ -1385,3 +1384,35 @@ class FlagHolder(Generic[_T]):
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 # pylint: disable=protected-access
+ 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 # pylint: disable=protected-access
+ 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
diff --git a/absl/flags/_helpers.py b/absl/flags/_helpers.py
index 37ae360..ea02f2d 100644
--- a/absl/flags/_helpers.py
+++ b/absl/flags/_helpers.py
@@ -14,10 +14,6 @@
"""Internal helper functions for Abseil Python flags library."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import collections
import os
import re
@@ -36,8 +32,9 @@ except ImportError:
_DEFAULT_HELP_WIDTH = 80 # Default width of help output.
-_MIN_HELP_WIDTH = 40 # Minimal "sane" width of help output. We assume that any
- # value below 40 is unreasonable.
+# Minimal "sane" width of help output. We assume that any value below 40 is
+# unreasonable.
+_MIN_HELP_WIDTH = 40
# Define the allowed error rate in an input string to get suggestions.
#
@@ -129,32 +126,6 @@ def get_calling_module():
return get_calling_module_object_and_name().module_name
-def str_or_unicode(value):
- """Converts a value to a python string.
-
- Behavior of this function is intentionally different in Python2/3.
-
- In Python2, the given value is attempted to convert to a str (byte string).
- If it contains non-ASCII characters, it is converted to a unicode instead.
-
- In Python3, the given value is always converted to a str (unicode string).
-
- This behavior reflects the (bad) practice in Python2 to try to represent
- a string as str as long as it contains ASCII characters only.
-
- Args:
- value: An object to be converted to a string.
-
- Returns:
- A string representation of the given value. See the description above
- for its type.
- """
- try:
- return str(value)
- except UnicodeEncodeError:
- return unicode(value) # Python3 should never come here
-
-
def create_xml_dom_element(doc, name, value):
"""Returns an XML DOM element with name and text value.
@@ -168,7 +139,7 @@ def create_xml_dom_element(doc, name, value):
Returns:
An instance of minidom.Element.
"""
- s = str_or_unicode(value)
+ s = str(value)
if isinstance(value, bool):
# Display boolean values as the C++ flag library does: no caps.
s = s.lower()
@@ -321,14 +292,18 @@ def flag_dict_to_args(flag_map, multi_flags=None):
Args:
flag_map: dict, a mapping where the keys are flag names (strings).
values are treated according to their type:
- * If value is None, then only the name is emitted.
- * If value is True, then only the name is emitted.
- * If value is False, then only the name prepended with 'no' is emitted.
- * If value is a string then --name=value is emitted.
- * If value is a collection, this will emit --name=value1,value2,value3,
- unless the flag name is in multi_flags, in which case this will emit
- --name=value1 --name=value2 --name=value3.
+
+ * If value is ``None``, then only the name is emitted.
+ * If value is ``True``, then only the name is emitted.
+ * If value is ``False``, then only the name prepended with 'no' is
+ emitted.
+ * If value is a string then ``--name=value`` is emitted.
+ * If value is a collection, this will emit
+ ``--name=value1,value2,value3``, unless the flag name is in
+ ``multi_flags``, in which case this will emit
+ ``--name=value1 --name=value2 --name=value3``.
* Everything else is converted to string an passed as such.
+
multi_flags: set, names (strings) of flags that should be treated as
multi-flags.
Yields:
@@ -424,10 +399,3 @@ def doc_to_help(doc):
doc = re.sub(r'(?<=\S)\n(?=\S)', ' ', doc, flags=re.M)
return doc
-
-
-def is_bytes_or_string(maybe_string):
- if str is bytes:
- return isinstance(maybe_string, basestring)
- else:
- return isinstance(maybe_string, (str, bytes))
diff --git a/absl/flags/_validators.py b/absl/flags/_validators.py
index af66050..2161284 100644
--- a/absl/flags/_validators.py
+++ b/absl/flags/_validators.py
@@ -14,13 +14,15 @@
"""Module to enforce different constraints on flags.
-Flags validators can be registered using following functions / decorators:
+Flags validators can be registered using following functions / decorators::
+
flags.register_validator
@flags.validator
flags.register_multi_flags_validator
@flags.multi_flags_validator
-Three convenience functions are also provided for common flag constraints:
+Three convenience functions are also provided for common flag constraints::
+
flags.mark_flag_as_required
flags.mark_flags_as_required
flags.mark_flags_as_mutual_exclusive
@@ -32,10 +34,6 @@ Do NOT import this module directly. Import the flags package and use the
aliases defined at the package level instead.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import warnings
from absl.flags import _exceptions
@@ -51,24 +49,32 @@ def register_validator(flag_name,
The constraint is validated when flags are initially parsed, and after each
change of the corresponding flag's value.
+
Args:
- flag_name: str, name of the flag to be checked.
+ flag_name: str | FlagHolder, name or holder of the flag to be checked.
+ Positional-only parameter.
checker: callable, a function to validate the flag.
- input - A single positional argument: The value of the corresponding
- flag (string, boolean, etc. This value will be passed to checker
- by the library).
- output - bool, True if validator constraint is satisfied.
- If constraint is not satisfied, it should either return False or
- raise flags.ValidationError(desired_error_message).
+
+ * input - A single positional argument: The value of the corresponding
+ flag (string, boolean, etc. This value will be passed to checker
+ by the library).
+ * output - bool, True if validator constraint is satisfied.
+ If constraint is not satisfied, it should either ``return False`` or
+ ``raise flags.ValidationError(desired_error_message)``.
+
message: str, error text to be shown to the user if checker returns False.
If checker raises flags.ValidationError, message from the raised
error will be shown.
flag_values: flags.FlagValues, optional FlagValues instance to validate
against.
+
Raises:
AttributeError: Raised when flag_name is not registered as a valid flag
name.
+ ValueError: Raised when flag_values is non-default and does not match the
+ FlagValues of the provided FlagHolder instance.
"""
+ flag_name, flag_values = _flagvalues.resolve_flag_ref(flag_name, flag_values)
v = _validators_classes.SingleFlagValidator(flag_name, checker, message)
_add_validator(flag_values, v)
@@ -77,16 +83,17 @@ def validator(flag_name, message='Flag validation failed',
flag_values=_flagvalues.FLAGS):
"""A function decorator for defining a flag validator.
- Registers the decorated function as a validator for flag_name, e.g.
+ Registers the decorated function as a validator for flag_name, e.g.::
- @flags.validator('foo')
- def _CheckFoo(foo):
- ...
+ @flags.validator('foo')
+ def _CheckFoo(foo):
+ ...
- See register_validator() for the specification of checker function.
+ See :func:`register_validator` for the specification of checker function.
Args:
- flag_name: str, name of the flag to be checked.
+ flag_name: str | FlagHolder, name or holder of the flag to be checked.
+ Positional-only parameter.
message: str, error text to be shown to the user if checker returns False.
If checker raises flags.ValidationError, message from the raised
error will be shown.
@@ -117,13 +124,16 @@ def register_multi_flags_validator(flag_names,
change of the corresponding flag's value.
Args:
- flag_names: [str], a list of the flag names to be checked.
+ flag_names: [str | FlagHolder], a list of the flag names or holders to be
+ checked. Positional-only parameter.
multi_flags_checker: callable, a function to validate the flag.
- input - dict, with keys() being flag_names, and value for each key
+
+ * input - dict, with keys() being flag_names, and value for each key
being the value of the corresponding flag (string, boolean, etc).
- output - bool, True if validator constraint is satisfied.
+ * output - bool, True if validator constraint is satisfied.
If constraint is not satisfied, it should either return False or
raise flags.ValidationError.
+
message: str, error text to be shown to the user if checker returns False.
If checker raises flags.ValidationError, message from the raised
error will be shown.
@@ -132,7 +142,13 @@ def register_multi_flags_validator(flag_names,
Raises:
AttributeError: Raised when a flag is not registered as a valid flag name.
+ ValueError: Raised when multiple FlagValues are used in the same
+ invocation. This can occur when FlagHolders have different `_flagvalues`
+ or when str-type flag_names entries are present and the `flag_values`
+ argument does not match that of provided FlagHolder(s).
"""
+ flag_names, flag_values = _flagvalues.resolve_flag_refs(
+ flag_names, flag_values)
v = _validators_classes.MultiFlagsValidator(
flag_names, multi_flags_checker, message)
_add_validator(flag_values, v)
@@ -143,17 +159,18 @@ def multi_flags_validator(flag_names,
flag_values=_flagvalues.FLAGS):
"""A function decorator for defining a multi-flag validator.
- Registers the decorated function as a validator for flag_names, e.g.
+ Registers the decorated function as a validator for flag_names, e.g.::
- @flags.multi_flags_validator(['foo', 'bar'])
- def _CheckFooBar(flags_dict):
- ...
+ @flags.multi_flags_validator(['foo', 'bar'])
+ def _CheckFooBar(flags_dict):
+ ...
- See register_multi_flags_validator() for the specification of checker
+ See :func:`register_multi_flags_validator` for the specification of checker
function.
Args:
- flag_names: [str], a list of the flag names to be checked.
+ flag_names: [str | FlagHolder], a list of the flag names or holders to be
+ checked. Positional-only parameter.
message: str, error text to be shown to the user if checker returns False.
If checker raises flags.ValidationError, message from the raised
error will be shown.
@@ -181,24 +198,28 @@ def mark_flag_as_required(flag_name, flag_values=_flagvalues.FLAGS):
"""Ensures that flag is not None during program execution.
Registers a flag validator, which will follow usual validator rules.
- Important note: validator will pass for any non-None value, such as False,
- 0 (zero), '' (empty string) and so on.
+ Important note: validator will pass for any non-``None`` value, such as
+ ``False``, ``0`` (zero), ``''`` (empty string) and so on.
If your module might be imported by others, and you only wish to make the flag
- required when the module is directly executed, call this method like this:
+ required when the module is directly executed, call this method like this::
- if __name__ == '__main__':
- flags.mark_flag_as_required('your_flag_name')
- app.run()
+ if __name__ == '__main__':
+ flags.mark_flag_as_required('your_flag_name')
+ app.run()
Args:
- flag_name: str, name of the flag
- flag_values: flags.FlagValues, optional FlagValues instance where the flag
- is defined.
+ flag_name: str | FlagHolder, name or holder of the flag.
+ Positional-only parameter.
+ flag_values: flags.FlagValues, optional :class:`~absl.flags.FlagValues`
+ instance where the flag is defined.
Raises:
AttributeError: Raised when flag_name is not registered as a valid flag
name.
+ ValueError: Raised when flag_values is non-default and does not match the
+ FlagValues of the provided FlagHolder instance.
"""
+ flag_name, flag_values = _flagvalues.resolve_flag_ref(flag_name, flag_values)
if flag_values[flag_name].default is not None:
warnings.warn(
'Flag --%s has a non-None default value; therefore, '
@@ -216,14 +237,14 @@ def mark_flags_as_required(flag_names, flag_values=_flagvalues.FLAGS):
"""Ensures that flags are not None during program execution.
If your module might be imported by others, and you only wish to make the flag
- required when the module is directly executed, call this method like this:
+ required when the module is directly executed, call this method like this::
- if __name__ == '__main__':
- flags.mark_flags_as_required(['flag1', 'flag2', 'flag3'])
- app.run()
+ if __name__ == '__main__':
+ flags.mark_flags_as_required(['flag1', 'flag2', 'flag3'])
+ app.run()
Args:
- flag_names: Sequence[str], names of the flags.
+ flag_names: Sequence[str | FlagHolder], names or holders of the flags.
flag_values: flags.FlagValues, optional FlagValues instance where the flags
are defined.
Raises:
@@ -237,20 +258,29 @@ def mark_flags_as_mutual_exclusive(flag_names, required=False,
flag_values=_flagvalues.FLAGS):
"""Ensures that only one flag among flag_names is not None.
- Important note: This validator checks if flag values are None, and it does not
- distinguish between default and explicit values. Therefore, this validator
+ Important note: This validator checks if flag values are ``None``, and it does
+ not distinguish between default and explicit values. Therefore, this validator
does not make sense when applied to flags with default values other than None,
- including other false values (e.g. False, 0, '', []). That includes multi
- flags with a default value of [] instead of None.
+ including other false values (e.g. ``False``, ``0``, ``''``, ``[]``). That
+ includes multi flags with a default value of ``[]`` instead of None.
Args:
- flag_names: [str], names of the flags.
+ flag_names: [str | FlagHolder], names or holders of flags.
+ Positional-only parameter.
required: bool. If true, exactly one of the flags must have a value other
than None. Otherwise, at most one of the flags can have a value other
than None, and it is valid for all of the flags to be None.
flag_values: flags.FlagValues, optional FlagValues instance where the flags
are defined.
+
+ Raises:
+ ValueError: Raised when multiple FlagValues are used in the same
+ invocation. This can occur when FlagHolders have different `_flagvalues`
+ or when str-type flag_names entries are present and the `flag_values`
+ argument does not match that of provided FlagHolder(s).
"""
+ flag_names, flag_values = _flagvalues.resolve_flag_refs(
+ flag_names, flag_values)
for flag_name in flag_names:
if flag_values[flag_name].default is not None:
warnings.warn(
@@ -276,12 +306,21 @@ def mark_bool_flags_as_mutual_exclusive(flag_names, required=False,
"""Ensures that only one flag among flag_names is True.
Args:
- flag_names: [str], names of the flags.
+ flag_names: [str | FlagHolder], names or holders of flags.
+ Positional-only parameter.
required: bool. If true, exactly one flag must be True. Otherwise, at most
one flag can be True, and it is valid for all flags to be False.
flag_values: flags.FlagValues, optional FlagValues instance where the flags
are defined.
+
+ Raises:
+ ValueError: Raised when multiple FlagValues are used in the same
+ invocation. This can occur when FlagHolders have different `_flagvalues`
+ or when str-type flag_names entries are present and the `flag_values`
+ argument does not match that of provided FlagHolder(s).
"""
+ flag_names, flag_values = _flagvalues.resolve_flag_refs(
+ flag_names, flag_values)
for flag_name in flag_names:
if not flag_values[flag_name].boolean:
raise _exceptions.ValidationError(
diff --git a/absl/flags/_validators_classes.py b/absl/flags/_validators_classes.py
index d8996e0..59100c8 100644
--- a/absl/flags/_validators_classes.py
+++ b/absl/flags/_validators_classes.py
@@ -18,10 +18,6 @@ Do NOT import this module. DO NOT use anything from this module. They are
private APIs.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
from absl.flags import _exceptions
@@ -160,7 +156,7 @@ class MultiFlagsValidator(Validator):
Args:
flag_values: flags.FlagValues, the FlagValues instance to get flags from.
Returns:
- dict, with keys() being self.lag_names, and value for each key
+ dict, with keys() being self.flag_names, and value for each key
being the value of the corresponding flag (string, boolean, etc).
"""
return dict([key, flag_values[key].value] for key in self.flag_names)
diff --git a/absl/flags/argparse_flags.py b/absl/flags/argparse_flags.py
index 4f78f50..dd8b505 100644
--- a/absl/flags/argparse_flags.py
+++ b/absl/flags/argparse_flags.py
@@ -14,11 +14,11 @@
"""This module provides argparse integration with absl.flags.
-argparse_flags.ArgumentParser is a drop-in replacement for
-argparse.ArgumentParser. It takes care of collecting and defining absl flags
-in argparse.
+``argparse_flags.ArgumentParser`` is a drop-in replacement for
+:class:`argparse.ArgumentParser`. It takes care of collecting and defining absl
+flags in :mod:`argparse`.
-Here is a simple example:
+Here is a simple example::
# Assume the following absl.flags is defined in another module:
#
@@ -40,7 +40,7 @@ Here is a simple example:
# ./program --header 'A header' --echo 'A message.'
-Here is another example demonstrates subparsers:
+Here is another example demonstrates subparsers::
parser = argparse_flags.ArgumentParser(description='A subcommands demo.')
parser.add_argument('--header', help='The header message to print.')
@@ -69,29 +69,27 @@ Here is another example demonstrates subparsers:
# ./program shuffle --echo='A message.' 1 2 3 4
-There are several differences between absl.flags and argparse_flags:
+There are several differences between :mod:`absl.flags` and
+:mod:`~absl.flags.argparse_flags`:
1. Flags defined with absl.flags are parsed differently when using the
argparse parser. Notably:
1) absl.flags allows both single-dash and double-dash for any flag, and
doesn't distinguish them; argparse_flags only allows double-dash for
- flag's regular name, and single-dash for flag's `short_name`.
- 2) Boolean flags in absl.flags can be specified with `--bool`, `--nobool`,
- as well as `--bool=true/false` (though not recommended);
- in argparse_flags, it only allows `--bool`, `--nobool`.
+ flag's regular name, and single-dash for flag's ``short_name``.
+ 2) Boolean flags in absl.flags can be specified with ``--bool``,
+ ``--nobool``, as well as ``--bool=true/false`` (though not recommended);
+ in argparse_flags, it only allows ``--bool``, ``--nobool``.
2. Help related flag differences:
+
1) absl.flags does not define help flags, absl.app does that; argparse_flags
- defines help flags unless passed with `add_help=False`.
- 2) absl.app supports `--helpxml`; argparse_flags does not.
- 3) argparse_flags supports `-h`; absl.app does not.
+ defines help flags unless passed with ``add_help=False``.
+ 2) absl.app supports ``--helpxml``; argparse_flags does not.
+ 3) argparse_flags supports ``-h``; absl.app does not.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import argparse
import sys
diff --git a/absl/flags/tests/_flag_test.py b/absl/flags/tests/_flag_test.py
index 492f117..1625289 100644
--- a/absl/flags/tests/_flag_test.py
+++ b/absl/flags/tests/_flag_test.py
@@ -17,10 +17,6 @@
Most of the Flag classes are covered in the flags_test.py.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import copy
import enum
import pickle
diff --git a/absl/flags/tests/_helpers_test.py b/absl/flags/tests/_helpers_test.py
index 4746a79..78b9051 100644
--- a/absl/flags/tests/_helpers_test.py
+++ b/absl/flags/tests/_helpers_test.py
@@ -14,10 +14,6 @@
"""Unittests for helpers module."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import sys
from absl.flags import _helpers
@@ -154,20 +150,5 @@ class GetCallingModuleTest(absltest.TestCase):
sys.modules = orig_sys_modules
-class IsBytesOrString(absltest.TestCase):
-
- def test_bytes(self):
- self.assertTrue(_helpers.is_bytes_or_string(b'bytes'))
-
- def test_str(self):
- self.assertTrue(_helpers.is_bytes_or_string('str'))
-
- def test_unicode(self):
- self.assertTrue(_helpers.is_bytes_or_string(u'unicode'))
-
- def test_list(self):
- self.assertFalse(_helpers.is_bytes_or_string(['str']))
-
-
if __name__ == '__main__':
absltest.main()
diff --git a/absl/flags/tests/_validators_test.py b/absl/flags/tests/_validators_test.py
index f724813..9aa328e 100644
--- a/absl/flags/tests/_validators_test.py
+++ b/absl/flags/tests/_validators_test.py
@@ -18,10 +18,6 @@ This file tests that each flag validator called when it should be, and that
failed validator will throw an exception, etc.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import warnings
@@ -59,6 +55,45 @@ class SingleFlagValidatorTest(absltest.TestCase):
self.assertEqual(2, self.flag_values.test_flag)
self.assertEqual([None, 2], self.call_args)
+ def test_success_holder(self):
+ def checker(x):
+ self.call_args.append(x)
+ return True
+
+ flag_holder = _defines.DEFINE_integer(
+ 'test_flag', None, 'Usual integer flag', flag_values=self.flag_values)
+ _validators.register_validator(
+ flag_holder,
+ checker,
+ message='Errors happen',
+ flag_values=self.flag_values)
+
+ argv = ('./program',)
+ self.flag_values(argv)
+ self.assertIsNone(self.flag_values.test_flag)
+ self.flag_values.test_flag = 2
+ self.assertEqual(2, self.flag_values.test_flag)
+ self.assertEqual([None, 2], self.call_args)
+
+ def test_success_holder_infer_flagvalues(self):
+ def checker(x):
+ self.call_args.append(x)
+ return True
+
+ flag_holder = _defines.DEFINE_integer(
+ 'test_flag', None, 'Usual integer flag', flag_values=self.flag_values)
+ _validators.register_validator(
+ flag_holder,
+ checker,
+ message='Errors happen')
+
+ argv = ('./program',)
+ self.flag_values(argv)
+ self.assertIsNone(self.flag_values.test_flag)
+ self.flag_values.test_flag = 2
+ self.assertEqual(2, self.flag_values.test_flag)
+ self.assertEqual([None, 2], self.call_args)
+
def test_default_value_not_used_success(self):
def checker(x):
self.call_args.append(x)
@@ -222,6 +257,26 @@ class SingleFlagValidatorTest(absltest.TestCase):
self.assertTrue(checker(3))
self.assertEqual([None, 2, 3], self.call_args)
+ def test_mismatching_flagvalues(self):
+
+ def checker(x):
+ self.call_args.append(x)
+ return True
+
+ flag_holder = _defines.DEFINE_integer(
+ 'test_flag',
+ None,
+ 'Usual integer flag',
+ flag_values=_flagvalues.FlagValues())
+ expected = (
+ 'flag_values must not be customized when operating on a FlagHolder')
+ with self.assertRaisesWithLiteralMatch(ValueError, expected):
+ _validators.register_validator(
+ flag_holder,
+ checker,
+ message='Errors happen',
+ flag_values=self.flag_values)
+
class MultiFlagsValidatorTest(absltest.TestCase):
"""Test flags multi-flag validators."""
@@ -230,9 +285,9 @@ class MultiFlagsValidatorTest(absltest.TestCase):
super(MultiFlagsValidatorTest, self).setUp()
self.flag_values = _flagvalues.FlagValues()
self.call_args = []
- _defines.DEFINE_integer(
+ self.foo_holder = _defines.DEFINE_integer(
'foo', 1, 'Usual integer flag', flag_values=self.flag_values)
- _defines.DEFINE_integer(
+ self.bar_holder = _defines.DEFINE_integer(
'bar', 2, 'Usual integer flag', flag_values=self.flag_values)
def test_success(self):
@@ -252,6 +307,55 @@ class MultiFlagsValidatorTest(absltest.TestCase):
self.assertEqual([{'foo': 1, 'bar': 2}, {'foo': 3, 'bar': 2}],
self.call_args)
+ def test_success_holder(self):
+
+ def checker(flags_dict):
+ self.call_args.append(flags_dict)
+ return True
+
+ _validators.register_multi_flags_validator(
+ [self.foo_holder, self.bar_holder],
+ checker,
+ flag_values=self.flag_values)
+
+ argv = ('./program', '--bar=2')
+ self.flag_values(argv)
+ self.assertEqual(1, self.flag_values.foo)
+ self.assertEqual(2, self.flag_values.bar)
+ self.assertEqual([{'foo': 1, 'bar': 2}], self.call_args)
+ self.flag_values.foo = 3
+ self.assertEqual(3, self.flag_values.foo)
+ self.assertEqual([{
+ 'foo': 1,
+ 'bar': 2
+ }, {
+ 'foo': 3,
+ 'bar': 2
+ }], self.call_args)
+
+ def test_success_holder_infer_flagvalues(self):
+ def checker(flags_dict):
+ self.call_args.append(flags_dict)
+ return True
+
+ _validators.register_multi_flags_validator(
+ [self.foo_holder, self.bar_holder], checker)
+
+ argv = ('./program', '--bar=2')
+ self.flag_values(argv)
+ self.assertEqual(1, self.flag_values.foo)
+ self.assertEqual(2, self.flag_values.bar)
+ self.assertEqual([{'foo': 1, 'bar': 2}], self.call_args)
+ self.flag_values.foo = 3
+ self.assertEqual(3, self.flag_values.foo)
+ self.assertEqual([{
+ 'foo': 1,
+ 'bar': 2
+ }, {
+ 'foo': 3,
+ 'bar': 2
+ }], self.call_args)
+
def test_validator_not_called_when_other_flag_is_changed(self):
def checker(flags_dict):
self.call_args.append(flags_dict)
@@ -326,6 +430,30 @@ class MultiFlagsValidatorTest(absltest.TestCase):
self.assertEqual([{'foo': 1, 'bar': 2}, {'foo': 1, 'bar': 1}],
self.call_args)
+ def test_mismatching_flagvalues(self):
+
+ def checker(flags_dict):
+ self.call_args.append(flags_dict)
+ values = flags_dict.values()
+ # Make sure all the flags have different values.
+ return len(set(values)) == len(values)
+
+ other_holder = _defines.DEFINE_integer(
+ 'other_flag',
+ 3,
+ 'Other integer flag',
+ flag_values=_flagvalues.FlagValues())
+ expected = (
+ 'multiple FlagValues instances used in invocation. '
+ 'FlagHolders must be registered to the same FlagValues instance as '
+ 'do flag names, if provided.')
+ with self.assertRaisesWithLiteralMatch(ValueError, expected):
+ _validators.register_multi_flags_validator(
+ [self.foo_holder, self.bar_holder, other_holder],
+ checker,
+ message='Errors happen',
+ flag_values=self.flag_values)
+
class MarkFlagsAsMutualExclusiveTest(absltest.TestCase):
@@ -333,9 +461,9 @@ class MarkFlagsAsMutualExclusiveTest(absltest.TestCase):
super(MarkFlagsAsMutualExclusiveTest, self).setUp()
self.flag_values = _flagvalues.FlagValues()
- _defines.DEFINE_string(
+ self.flag_one_holder = _defines.DEFINE_string(
'flag_one', None, 'flag one', flag_values=self.flag_values)
- _defines.DEFINE_string(
+ self.flag_two_holder = _defines.DEFINE_string(
'flag_two', None, 'flag two', flag_values=self.flag_values)
_defines.DEFINE_string(
'flag_three', None, 'flag three', flag_values=self.flag_values)
@@ -362,6 +490,24 @@ class MarkFlagsAsMutualExclusiveTest(absltest.TestCase):
self.assertIsNone(self.flag_values.flag_one)
self.assertIsNone(self.flag_values.flag_two)
+ def test_no_flags_present_holder(self):
+ self._mark_flags_as_mutually_exclusive(
+ [self.flag_one_holder, self.flag_two_holder], False)
+ argv = ('./program',)
+
+ self.flag_values(argv)
+ self.assertIsNone(self.flag_values.flag_one)
+ self.assertIsNone(self.flag_values.flag_two)
+
+ def test_no_flags_present_mixed(self):
+ self._mark_flags_as_mutually_exclusive([self.flag_one_holder, 'flag_two'],
+ False)
+ argv = ('./program',)
+
+ self.flag_values(argv)
+ self.assertIsNone(self.flag_values.flag_one)
+ self.assertIsNone(self.flag_values.flag_two)
+
def test_no_flags_present_required(self):
self._mark_flags_as_mutually_exclusive(['flag_one', 'flag_two'], True)
argv = ('./program',)
@@ -498,6 +644,20 @@ class MarkFlagsAsMutualExclusiveTest(absltest.TestCase):
self.assertIn('--flag_not_none has a non-None default value',
str(caught_warnings[0].message))
+ def test_multiple_flagvalues(self):
+ other_holder = _defines.DEFINE_boolean(
+ 'other_flagvalues',
+ False,
+ 'other ',
+ flag_values=_flagvalues.FlagValues())
+ expected = (
+ 'multiple FlagValues instances used in invocation. '
+ 'FlagHolders must be registered to the same FlagValues instance as '
+ 'do flag names, if provided.')
+ with self.assertRaisesWithLiteralMatch(ValueError, expected):
+ self._mark_flags_as_mutually_exclusive(
+ [self.flag_one_holder, other_holder], False)
+
class MarkBoolFlagsAsMutualExclusiveTest(absltest.TestCase):
@@ -505,13 +665,13 @@ class MarkBoolFlagsAsMutualExclusiveTest(absltest.TestCase):
super(MarkBoolFlagsAsMutualExclusiveTest, self).setUp()
self.flag_values = _flagvalues.FlagValues()
- _defines.DEFINE_boolean(
+ self.false_1_holder = _defines.DEFINE_boolean(
'false_1', False, 'default false 1', flag_values=self.flag_values)
- _defines.DEFINE_boolean(
+ self.false_2_holder = _defines.DEFINE_boolean(
'false_2', False, 'default false 2', flag_values=self.flag_values)
- _defines.DEFINE_boolean(
+ self.true_1_holder = _defines.DEFINE_boolean(
'true_1', True, 'default true 1', flag_values=self.flag_values)
- _defines.DEFINE_integer(
+ self.non_bool_holder = _defines.DEFINE_integer(
'non_bool', None, 'non bool', flag_values=self.flag_values)
def _mark_bool_flags_as_mutually_exclusive(self, flag_names, required):
@@ -524,6 +684,20 @@ class MarkBoolFlagsAsMutualExclusiveTest(absltest.TestCase):
self.assertEqual(False, self.flag_values.false_1)
self.assertEqual(False, self.flag_values.false_2)
+ def test_no_flags_present_holder(self):
+ self._mark_bool_flags_as_mutually_exclusive(
+ [self.false_1_holder, self.false_2_holder], False)
+ self.flag_values(('./program',))
+ self.assertEqual(False, self.flag_values.false_1)
+ self.assertEqual(False, self.flag_values.false_2)
+
+ def test_no_flags_present_mixed(self):
+ self._mark_bool_flags_as_mutually_exclusive(
+ [self.false_1_holder, 'false_2'], False)
+ self.flag_values(('./program',))
+ self.assertEqual(False, self.flag_values.false_1)
+ self.assertEqual(False, self.flag_values.false_2)
+
def test_no_flags_present_required(self):
self._mark_bool_flags_as_mutually_exclusive(['false_1', 'false_2'], True)
argv = ('./program',)
@@ -558,6 +732,17 @@ class MarkBoolFlagsAsMutualExclusiveTest(absltest.TestCase):
self._mark_bool_flags_as_mutually_exclusive(['false_1', 'non_bool'],
False)
+ def test_multiple_flagvalues(self):
+ other_bool_holder = _defines.DEFINE_boolean(
+ 'other_bool', False, 'other bool', flag_values=_flagvalues.FlagValues())
+ expected = (
+ 'multiple FlagValues instances used in invocation. '
+ 'FlagHolders must be registered to the same FlagValues instance as '
+ 'do flag names, if provided.')
+ with self.assertRaisesWithLiteralMatch(ValueError, expected):
+ self._mark_bool_flags_as_mutually_exclusive(
+ [self.false_1_holder, other_bool_holder], False)
+
class MarkFlagAsRequiredTest(absltest.TestCase):
@@ -574,6 +759,22 @@ class MarkFlagAsRequiredTest(absltest.TestCase):
self.flag_values(argv)
self.assertEqual('value', self.flag_values.string_flag)
+ def test_success_holder(self):
+ holder = _defines.DEFINE_string(
+ 'string_flag', None, 'string flag', flag_values=self.flag_values)
+ _validators.mark_flag_as_required(holder, flag_values=self.flag_values)
+ argv = ('./program', '--string_flag=value')
+ self.flag_values(argv)
+ self.assertEqual('value', self.flag_values.string_flag)
+
+ def test_success_holder_infer_flagvalues(self):
+ holder = _defines.DEFINE_string(
+ 'string_flag', None, 'string flag', flag_values=self.flag_values)
+ _validators.mark_flag_as_required(holder)
+ argv = ('./program', '--string_flag=value')
+ self.flag_values(argv)
+ self.assertEqual('value', self.flag_values.string_flag)
+
def test_catch_none_as_default(self):
_defines.DEFINE_string(
'string_flag', None, 'string flag', flag_values=self.flag_values)
@@ -612,6 +813,18 @@ class MarkFlagAsRequiredTest(absltest.TestCase):
self.assertIn('--flag_not_none has a non-None default value',
str(caught_warnings[0].message))
+ def test_mismatching_flagvalues(self):
+ flag_holder = _defines.DEFINE_string(
+ 'string_flag',
+ 'value',
+ 'string flag',
+ flag_values=_flagvalues.FlagValues())
+ expected = (
+ 'flag_values must not be customized when operating on a FlagHolder')
+ with self.assertRaisesWithLiteralMatch(ValueError, expected):
+ _validators.mark_flag_as_required(
+ flag_holder, flag_values=self.flag_values)
+
class MarkFlagsAsRequiredTest(absltest.TestCase):
@@ -631,6 +844,18 @@ class MarkFlagsAsRequiredTest(absltest.TestCase):
self.assertEqual('value_1', self.flag_values.string_flag_1)
self.assertEqual('value_2', self.flag_values.string_flag_2)
+ def test_success_holders(self):
+ flag_1_holder = _defines.DEFINE_string(
+ 'string_flag_1', None, 'string flag 1', flag_values=self.flag_values)
+ flag_2_holder = _defines.DEFINE_string(
+ 'string_flag_2', None, 'string flag 2', flag_values=self.flag_values)
+ _validators.mark_flags_as_required([flag_1_holder, flag_2_holder],
+ flag_values=self.flag_values)
+ argv = ('./program', '--string_flag_1=value_1', '--string_flag_2=value_2')
+ self.flag_values(argv)
+ self.assertEqual('value_1', self.flag_values.string_flag_1)
+ self.assertEqual('value_2', self.flag_values.string_flag_2)
+
def test_catch_none_as_default(self):
_defines.DEFINE_string(
'string_flag_1', None, 'string flag 1', flag_values=self.flag_values)
diff --git a/absl/flags/tests/argparse_flags_test_helper.py b/absl/flags/tests/argparse_flags_test_helper.py
index 8cf42e6..613c896 100644
--- a/absl/flags/tests/argparse_flags_test_helper.py
+++ b/absl/flags/tests/argparse_flags_test_helper.py
@@ -14,10 +14,6 @@
"""Test helper for argparse_flags_test."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import random
diff --git a/absl/flags/tests/flags_formatting_test.py b/absl/flags/tests/flags_formatting_test.py
index bb547ce..ef55850 100644
--- a/absl/flags/tests/flags_formatting_test.py
+++ b/absl/flags/tests/flags_formatting_test.py
@@ -12,10 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
from absl import flags
from absl.flags import _helpers
from absl.testing import absltest
diff --git a/absl/flags/tests/flags_test.py b/absl/flags/tests/flags_test.py
index 8a42bc9..77ed307 100644
--- a/absl/flags/tests/flags_test.py
+++ b/absl/flags/tests/flags_test.py
@@ -2483,6 +2483,71 @@ class NonGlobalFlagsTest(absltest.TestCase):
flag_values['flag_name'] = 'flag_value'
+class SetDefaultTest(absltest.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.flag_values = flags.FlagValues()
+
+ def test_success(self):
+ int_holder = flags.DEFINE_integer(
+ 'an_int', 1, 'an int', flag_values=self.flag_values)
+
+ flags.set_default(int_holder, 2)
+ self.flag_values.mark_as_parsed()
+
+ self.assertEqual(int_holder.value, 2)
+
+ def test_update_after_parse(self):
+ int_holder = flags.DEFINE_integer(
+ 'an_int', 1, 'an int', flag_values=self.flag_values)
+
+ self.flag_values.mark_as_parsed()
+ flags.set_default(int_holder, 2)
+
+ self.assertEqual(int_holder.value, 2)
+
+ def test_overridden_by_explicit_assignment(self):
+ int_holder = flags.DEFINE_integer(
+ 'an_int', 1, 'an int', flag_values=self.flag_values)
+
+ self.flag_values.mark_as_parsed()
+ self.flag_values.an_int = 3
+ flags.set_default(int_holder, 2)
+
+ self.assertEqual(int_holder.value, 3)
+
+ def test_restores_back_to_none(self):
+ int_holder = flags.DEFINE_integer(
+ 'an_int', None, 'an int', flag_values=self.flag_values)
+
+ self.flag_values.mark_as_parsed()
+ flags.set_default(int_holder, 3)
+ flags.set_default(int_holder, None)
+
+ self.assertIsNone(int_holder.value)
+
+ def test_failure_on_invalid_type(self):
+ int_holder = flags.DEFINE_integer(
+ 'an_int', 1, 'an int', flag_values=self.flag_values)
+
+ self.flag_values.mark_as_parsed()
+
+ with self.assertRaises(flags.IllegalFlagValueError):
+ flags.set_default(int_holder, 'a')
+
+ def test_failure_on_type_protected_none_default(self):
+ int_holder = flags.DEFINE_integer(
+ 'an_int', 1, 'an int', flag_values=self.flag_values)
+
+ self.flag_values.mark_as_parsed()
+
+ flags.set_default(int_holder, None) # NOTE: should be a type failure
+
+ with self.assertRaises(flags.IllegalFlagValueError):
+ _ = int_holder.value # Will also fail on later access.
+
+
class KeyFlagsTest(absltest.TestCase):
def setUp(self):
@@ -2646,6 +2711,40 @@ class KeyFlagsTest(absltest.TestCase):
self._get_names_of_key_flags(main_module, fv),
names_of_flags_defined_by_bar + ['flagfile', 'undefok'])
+ def test_key_flags_with_flagholders(self):
+ main_module = sys.argv[0]
+
+ self.assertListEqual(
+ self._get_names_of_key_flags(main_module, self.flag_values), [])
+ self.assertListEqual(
+ self._get_names_of_defined_flags(main_module, self.flag_values), [])
+
+ int_holder = flags.DEFINE_integer(
+ 'main_module_int_fg',
+ 1,
+ 'Integer flag in the main module.',
+ flag_values=self.flag_values)
+
+ flags.declare_key_flag(int_holder, self.flag_values)
+
+ self.assertCountEqual(
+ self.flag_values.get_flags_for_module(main_module),
+ self.flag_values.get_key_flags_for_module(main_module))
+
+ bool_holder = flags.DEFINE_boolean(
+ 'main_module_bool_fg',
+ False,
+ 'Boolean flag in the main module.',
+ flag_values=self.flag_values)
+
+ flags.declare_key_flag(bool_holder) # omitted flag_values
+
+ self.assertCountEqual(
+ self.flag_values.get_flags_for_module(main_module),
+ self.flag_values.get_key_flags_for_module(main_module))
+
+ self.assertLen(self.flag_values.get_flags_for_module(main_module), 2)
+
def test_main_module_help_with_key_flags(self):
# Similar to test_main_module_help, but this time we make sure to
# declare some key flags.
diff --git a/absl/flags/tests/flags_unicode_literals_test.py b/absl/flags/tests/flags_unicode_literals_test.py
index e8ed5bf..1f72f23 100644
--- a/absl/flags/tests/flags_unicode_literals_test.py
+++ b/absl/flags/tests/flags_unicode_literals_test.py
@@ -14,11 +14,6 @@
"""Test the use of flags when from __future__ import unicode_literals is on."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
from absl import flags
from absl.testing import absltest
diff --git a/absl/flags/tests/module_bar.py b/absl/flags/tests/module_bar.py
index 8714d2e..51dffa3 100644
--- a/absl/flags/tests/module_bar.py
+++ b/absl/flags/tests/module_bar.py
@@ -18,10 +18,6 @@ The purpose of this module is to define a few flags. We want to make
sure the unit tests for flags.py involve more than one module.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
from absl import flags
from absl.flags import _helpers
diff --git a/absl/flags/tests/module_baz.py b/absl/flags/tests/module_baz.py
index 7199516..9bf03fd 100644
--- a/absl/flags/tests/module_baz.py
+++ b/absl/flags/tests/module_baz.py
@@ -18,10 +18,6 @@ The purpose of this module is to test the behavior of flags that are defined
before main() executes.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
from absl import flags
FLAGS = flags.FLAGS
diff --git a/absl/flags/tests/module_foo.py b/absl/flags/tests/module_foo.py
index a1a2573..649047c 100644
--- a/absl/flags/tests/module_foo.py
+++ b/absl/flags/tests/module_foo.py
@@ -19,10 +19,6 @@ other flags as being important. We want to make sure the unit tests
for flags.py involve more than one module.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
from absl import flags
from absl.flags import _helpers
from absl.flags.tests import module_bar
diff --git a/absl/logging/__init__.py b/absl/logging/__init__.py
index 8804490..c0ba4b0 100644
--- a/absl/logging/__init__.py
+++ b/absl/logging/__init__.py
@@ -14,7 +14,7 @@
"""Abseil Python logging module implemented on top of standard logging.
-Simple usage:
+Simple usage::
from absl import logging
@@ -34,29 +34,34 @@ Usage note: Do not pre-format the strings in your program code.
Instead, let the logging module perform argument interpolation.
This saves cycles because strings that don't need to be printed
are never formatted. Note that this module does not attempt to
-interpolate arguments when no arguments are given. In other words
+interpolate arguments when no arguments are given. In other words::
logging.info('Interesting Stuff: %s')
does not raise an exception because logging.info() has only one
argument, the message string.
-"Lazy" evaluation for debugging:
+"Lazy" evaluation for debugging
+-------------------------------
+
+If you do something like this::
-If you do something like this:
logging.debug('Thing: %s', thing.ExpensiveOp())
+
then the ExpensiveOp will be evaluated even if nothing
-is printed to the log. To avoid this, use the level_debug() function:
+is printed to the log. To avoid this, use the level_debug() function::
+
if logging.level_debug():
logging.debug('Thing: %s', thing.ExpensiveOp())
Per file level logging is supported by logging.vlog() and
-logging.vlog_is_on(). For example:
+logging.vlog_is_on(). For example::
if logging.vlog_is_on(2):
logging.vlog(2, very_expensive_debug_message())
-Notes on Unicode:
+Notes on Unicode
+----------------
The log output is encoded as UTF-8. Don't pass data in other encodings in
bytes() instances -- instead pass unicode string instances when you need to
@@ -407,9 +412,9 @@ def debug(msg, *args, **kwargs):
log(DEBUG, msg, *args, **kwargs)
-def exception(msg, *args):
+def exception(msg, *args, **kwargs):
"""Logs an exception, with traceback and message."""
- error(msg, *args, exc_info=True)
+ error(msg, *args, **kwargs, exc_info=True)
# Counter to keep track of number of log entries per token.
@@ -432,7 +437,7 @@ def _get_next_log_count_per_token(token):
def log_every_n(level, msg, n, *args):
- """Logs 'msg % args' at level 'level' once per 'n' times.
+ """Logs ``msg % args`` at level 'level' once per 'n' times.
Logs the 1st call, (N+1)st call, (2N+1)st call, etc.
Not threadsafe.
@@ -479,7 +484,7 @@ def _seconds_have_elapsed(token, num_seconds):
def log_every_n_seconds(level, msg, n_seconds, *args):
- """Logs 'msg % args' at level 'level' iff 'n_seconds' elapsed since last call.
+ """Logs ``msg % args`` at level ``level`` iff ``n_seconds`` elapsed since last call.
Logs the first call, logs subsequent calls if 'n' seconds have elapsed since
the last logging call from the same call site (file + line). Not thread-safe.
@@ -495,7 +500,7 @@ def log_every_n_seconds(level, msg, n_seconds, *args):
def log_first_n(level, msg, n, *args):
- """Logs 'msg % args' at level 'level' only first 'n' times.
+ """Logs ``msg % args`` at level ``level`` only first ``n`` times.
Not threadsafe.
@@ -510,13 +515,13 @@ def log_first_n(level, msg, n, *args):
def log_if(level, msg, condition, *args):
- """Logs 'msg % args' at level 'level' only if condition is fulfilled."""
+ """Logs ``msg % args`` at level ``level`` only if condition is fulfilled."""
if condition:
log(level, msg, *args)
def log(level, msg, *args, **kwargs):
- """Logs 'msg % args' at absl logging level 'level'.
+ """Logs ``msg % args`` at absl logging level ``level``.
If no args are given just print msg, ignoring any interpolation specifiers.
@@ -550,7 +555,7 @@ def log(level, msg, *args, **kwargs):
def vlog(level, msg, *args, **kwargs):
- """Log 'msg % args' at C++ vlog level 'level'.
+ """Log ``msg % args`` at C++ vlog level ``level``.
Args:
level: int, the C++ verbose logging level at which to log the message,
@@ -645,13 +650,13 @@ def find_log_dir_and_names(program_name=None, log_dir=None):
Args:
program_name: str|None, the filename part of the path to the program that
is running without its extension. e.g: if your program is called
- 'usr/bin/foobar.py' this method should probably be called with
- program_name='foobar' However, this is just a convention, you can
+ ``usr/bin/foobar.py`` this method should probably be called with
+ ``program_name='foobar`` However, this is just a convention, you can
pass in any string you want, and it will be used as part of the
log filename. If you don't pass in anything, the default behavior
is as described in the example. In python standard logging mode,
- the program_name will be prepended with py_ if it is the program_name
- argument is omitted.
+ the program_name will be prepended with ``py_`` if it is the
+ ``program_name`` argument is omitted.
log_dir: str|None, the desired log directory.
Returns:
@@ -753,10 +758,10 @@ def get_absl_log_prefix(record):
def skip_log_prefix(func):
- """Skips reporting the prefix of a given function or name by ABSLLogger.
+ """Skips reporting the prefix of a given function or name by :class:`~absl.logging.ABSLLogger`.
This is a convenience wrapper function / decorator for
- `ABSLLogger.register_frame_to_skip`.
+ :meth:`~absl.logging.ABSLLogger.register_frame_to_skip`.
If a callable function is provided, only that function will be skipped.
If a function name is provided, all functions with the same name in the
@@ -880,13 +885,13 @@ class PythonHandler(logging.StreamHandler):
def emit(self, record):
"""Prints a record out to some streams.
- If FLAGS.logtostderr is set, it will print to sys.stderr ONLY.
- If FLAGS.alsologtostderr is set, it will print to sys.stderr.
- If FLAGS.logtostderr is not set, it will log to the stream
- associated with the current thread.
+ 1. If ``FLAGS.logtostderr`` is set, it will print to ``sys.stderr`` ONLY.
+ 2. If ``FLAGS.alsologtostderr`` is set, it will print to ``sys.stderr``.
+ 3. If ``FLAGS.logtostderr`` is not set, it will log to the stream
+ associated with the current thread.
Args:
- record: logging.LogRecord, the record to emit.
+ record: :class:`logging.LogRecord`, the record to emit.
"""
# People occasionally call logging functions at import time before
# our flags may have even been defined yet, let alone even parsed, as we
@@ -987,7 +992,7 @@ class ABSLHandler(logging.Handler):
class PythonFormatter(logging.Formatter):
- """Formatter class used by PythonHandler."""
+ """Formatter class used by :class:`~absl.logging.PythonHandler`."""
def format(self, record):
"""Appends the message from the record to the results of the prefix.
@@ -1065,33 +1070,33 @@ class ABSLLogger(logging.getLoggerClass()):
frame = frame.f_back
def critical(self, msg, *args, **kwargs):
- """Logs 'msg % args' with severity 'CRITICAL'."""
+ """Logs ``msg % args`` with severity ``CRITICAL``."""
self.log(logging.CRITICAL, msg, *args, **kwargs)
def fatal(self, msg, *args, **kwargs):
- """Logs 'msg % args' with severity 'FATAL'."""
+ """Logs ``msg % args`` with severity ``FATAL``."""
self.log(logging.FATAL, msg, *args, **kwargs)
def error(self, msg, *args, **kwargs):
- """Logs 'msg % args' with severity 'ERROR'."""
+ """Logs ``msg % args`` with severity ``ERROR``."""
self.log(logging.ERROR, msg, *args, **kwargs)
def warn(self, msg, *args, **kwargs):
- """Logs 'msg % args' with severity 'WARN'."""
+ """Logs ``msg % args`` with severity ``WARN``."""
warnings.warn("The 'warn' method is deprecated, use 'warning' instead",
DeprecationWarning, 2)
self.log(logging.WARN, msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs):
- """Logs 'msg % args' with severity 'WARNING'."""
+ """Logs ``msg % args`` with severity ``WARNING``."""
self.log(logging.WARNING, msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
- """Logs 'msg % args' with severity 'INFO'."""
+ """Logs ``msg % args`` with severity ``INFO``."""
self.log(logging.INFO, msg, *args, **kwargs)
def debug(self, msg, *args, **kwargs):
- """Logs 'msg % args' with severity 'DEBUG'."""
+ """Logs ``msg % args`` with severity ``DEBUG``."""
self.log(logging.DEBUG, msg, *args, **kwargs)
def log(self, level, msg, *args, **kwargs):
@@ -1114,12 +1119,12 @@ class ABSLLogger(logging.getLoggerClass()):
super(ABSLLogger, self).log(level, msg, *args, **kwargs)
def handle(self, record):
- """Calls handlers without checking Logger.disabled.
+ """Calls handlers without checking ``Logger.disabled``.
- Non-root loggers are set to disabled after setup with logging.config if
- it's not explicitly specified. Historically, absl logging will not be
+ Non-root loggers are set to disabled after setup with :func:`logging.config`
+ if it's not explicitly specified. Historically, absl logging will not be
disabled by that. To maintaining this behavior, this function skips
- checking the Logger.disabled bit.
+ checking the ``Logger.disabled`` bit.
This logger can still be disabled by adding a filter that filters out
everything.
@@ -1134,8 +1139,8 @@ class ABSLLogger(logging.getLoggerClass()):
def register_frame_to_skip(cls, file_name, function_name, line_number=None):
"""Registers a function name to skip when walking the stack.
- The ABSLLogger sometimes skips method calls on the stack
- to make the log messages meaningful in their appropriate context.
+ The :class:`~absl.logging.ABSLLogger` sometimes skips method calls on the
+ stack to make the log messages meaningful in their appropriate context.
This method registers a function from a particular file as one
which should be skipped.
@@ -1193,7 +1198,8 @@ _attempted_to_remove_stderr_stream_handlers = False
def use_absl_handler():
"""Uses the ABSL logging handler for logging.
- This method is called in app.run() so the absl handler is used in absl apps.
+ This method is called in :func:`app.run()<absl.app.run>` so the absl handler
+ is used in absl apps.
"""
global _attempted_to_remove_stderr_stream_handlers
if not _attempted_to_remove_stderr_stream_handlers:
diff --git a/absl/logging/converter.py b/absl/logging/converter.py
index 53dd46d..0239ab4 100644
--- a/absl/logging/converter.py
+++ b/absl/logging/converter.py
@@ -16,25 +16,26 @@
This converter has to convert (best effort) between three different
logging level schemes:
- cpp = The C++ logging level scheme used in Abseil C++.
- absl = The absl.logging level scheme used in Abseil Python.
- standard = The python standard library logging level scheme.
-
-Here is a handy ascii chart for easy mental mapping.
-
- LEVEL | cpp | absl | standard |
- ---------+-----+--------+----------+
- DEBUG | 0 | 1 | 10 |
- INFO | 0 | 0 | 20 |
- WARNING | 1 | -1 | 30 |
- ERROR | 2 | -2 | 40 |
- CRITICAL | 3 | -3 | 50 |
- FATAL | 3 | -3 | 50 |
-
-Note: standard logging CRITICAL is mapped to absl/cpp FATAL.
-However, only CRITICAL logs from the absl logger (or absl.logging.fatal) will
-terminate the program. CRITICAL logs from non-absl loggers are treated as
-error logs with a message prefix "CRITICAL - ".
+
+ * **cpp**: The C++ logging level scheme used in Abseil C++.
+ * **absl**: The absl.logging level scheme used in Abseil Python.
+ * **standard**: The python standard library logging level scheme.
+
+Here is a handy ascii chart for easy mental mapping::
+
+ LEVEL | cpp | absl | standard |
+ ---------+-----+--------+----------+
+ DEBUG | 0 | 1 | 10 |
+ INFO | 0 | 0 | 20 |
+ WARNING | 1 | -1 | 30 |
+ ERROR | 2 | -2 | 40 |
+ CRITICAL | 3 | -3 | 50 |
+ FATAL | 3 | -3 | 50 |
+
+Note: standard logging ``CRITICAL`` is mapped to absl/cpp ``FATAL``.
+However, only ``CRITICAL`` logs from the absl logger (or absl.logging.fatal)
+will terminate the program. ``CRITICAL`` logs from non-absl loggers are treated
+as error logs with a message prefix ``"CRITICAL - "``.
Converting from standard to absl or cpp is a lossy conversion.
Converting back to standard will lose granularity. For this reason,
@@ -89,10 +90,11 @@ def get_initial_for_level(level):
"""Gets the initial that should start the log line for the given level.
It returns:
- - 'I' when: level < STANDARD_WARNING.
- - 'W' when: STANDARD_WARNING <= level < STANDARD_ERROR.
- - 'E' when: STANDARD_ERROR <= level < STANDARD_CRITICAL.
- - 'F' when: level >= STANDARD_CRITICAL.
+
+ * ``'I'`` when: ``level < STANDARD_WARNING``.
+ * ``'W'`` when: ``STANDARD_WARNING <= level < STANDARD_ERROR``.
+ * ``'E'`` when: ``STANDARD_ERROR <= level < STANDARD_CRITICAL``.
+ * ``'F'`` when: ``level >= STANDARD_CRITICAL``.
Args:
level: int, a Python standard logging level.
@@ -157,7 +159,8 @@ def string_to_standard(level):
"""Converts a string level to standard logging level value.
Args:
- level: str, case-insensitive 'debug', 'info', 'warning', 'error', 'fatal'.
+ level: str, case-insensitive ``'debug'``, ``'info'``, ``'warning'``,
+ ``'error'``, ``'fatal'``.
Returns:
The corresponding integer level for use in standard logging.
diff --git a/absl/logging/tests/converter_test.py b/absl/logging/tests/converter_test.py
index bdc893a..2c95c4f 100644
--- a/absl/logging/tests/converter_test.py
+++ b/absl/logging/tests/converter_test.py
@@ -14,10 +14,6 @@
"""Tests for converter.py."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import logging
from absl import logging as absl_logging
diff --git a/absl/logging/tests/verbosity_flag_test.py b/absl/logging/tests/verbosity_flag_test.py
index 4609e64..ea9944d 100644
--- a/absl/logging/tests/verbosity_flag_test.py
+++ b/absl/logging/tests/verbosity_flag_test.py
@@ -14,10 +14,6 @@
"""Tests -v/--verbosity flag and logging.root level's sync behavior."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import logging
assert logging.root.getEffectiveLevel() == logging.WARN, (
diff --git a/absl/testing/BUILD b/absl/testing/BUILD
index b608c8c..d428792 100644
--- a/absl/testing/BUILD
+++ b/absl/testing/BUILD
@@ -199,6 +199,7 @@ py_binary(
srcs_version = "PY3",
deps = [
":absltest",
+ "//absl:app",
"//absl/flags",
],
)
diff --git a/absl/testing/_bazelize_command.py b/absl/testing/_bazelize_command.py
index fdf6eb6..9380d27 100644
--- a/absl/testing/_bazelize_command.py
+++ b/absl/testing/_bazelize_command.py
@@ -14,10 +14,6 @@
"""Internal helper for running tests on Windows Bazel."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
from absl import flags
diff --git a/absl/testing/_pretty_print_reporter.py b/absl/testing/_pretty_print_reporter.py
index ef03934..b0dde07 100644
--- a/absl/testing/_pretty_print_reporter.py
+++ b/absl/testing/_pretty_print_reporter.py
@@ -14,10 +14,6 @@
"""TestResult implementing default output for test execution status."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import unittest
diff --git a/absl/testing/absltest.py b/absl/testing/absltest.py
index cebb1ca..9071f8f 100644
--- a/absl/testing/absltest.py
+++ b/absl/testing/absltest.py
@@ -103,10 +103,11 @@ __unittest = True # pylint: disable=invalid-name
def expectedFailureIf(condition, reason): # pylint: disable=invalid-name
"""Expects the test to fail if the run condition is True.
- Example usage:
- @expectedFailureIf(sys.version.major == 2, "Not yet working in py2")
- def test_foo(self):
- ...
+ Example usage::
+
+ @expectedFailureIf(sys.version.major == 2, "Not yet working in py2")
+ def test_foo(self):
+ ...
Args:
condition: bool, whether to expect failure or not.
@@ -531,8 +532,9 @@ class _TempFile(object):
# Literal to express more precise return types. The contained type is
# currently `Any` to avoid [bad-return-type] errors in the open_* methods.
@contextlib.contextmanager
- def _open(self, mode, encoding='utf8', errors='strict'):
- # type: (Text, Text, Text) -> Iterator[Any]
+ def _open(
+ self, mode: str, encoding: str = 'utf8', errors: str = 'strict'
+ ) -> Iterator[Any]:
with io.open(
self.full_path, mode=mode, encoding=encoding, errors=errors) as fp:
yield fp
@@ -622,7 +624,7 @@ class TestCase(unittest.TestCase):
This creates a named directory on disk that is isolated to this test, and
will be properly cleaned up by the test. This avoids several pitfalls of
creating temporary directories for test purposes, as well as makes it easier
- to setup directories and verify their contents. For example:
+ to setup directories and verify their contents. For example::
def test_foo(self):
out_dir = self.create_tempdir()
@@ -636,14 +638,14 @@ class TestCase(unittest.TestCase):
self.assertTrue(os.path.exists(expected_paths[1]))
self.assertEqual('foo', out_log.read_text())
- See also: `create_tempfile()` for creating temporary files.
+ See also: :meth:`create_tempdir` for creating temporary files.
Args:
name: Optional name of the directory. If not given, a unique
name will be generated and used.
cleanup: Optional cleanup policy on when/if to remove the directory (and
all its contents) at the end of the test. If None, then uses
- `self.tempfile_cleanup`.
+ :attr:`tempfile_cleanup`.
Returns:
A _TempDir representing the created directory; see _TempDir class docs
@@ -677,7 +679,7 @@ class TestCase(unittest.TestCase):
be properly cleaned up by the test. This avoids several pitfalls of
creating temporary files for test purposes, as well as makes it easier
to setup files, their data, read them back, and inspect them when
- a test fails. For example:
+ a test fails. For example::
def test_foo(self):
output = self.create_tempfile()
@@ -689,15 +691,15 @@ class TestCase(unittest.TestCase):
state.
NOTE: If the file already exists, it will be made writable and overwritten.
- See also: `create_tempdir()` for creating temporary directories, and
- `_TempDir.create_file` for creating files within a temporary directory.
+ See also: :meth:`create_tempdir` for creating temporary directories, and
+ ``_TempDir.create_file`` for creating files within a temporary directory.
Args:
file_path: Optional file path for the temp file. If not given, a unique
file name will be generated and used. Slashes are allowed in the name;
any missing intermediate directories will be created. NOTE: This path is
the path that will be cleaned up, including any directories in the path,
- e.g., 'foo/bar/baz.txt' will `rm -r foo`.
+ e.g., ``'foo/bar/baz.txt'`` will ``rm -r foo``.
content: Optional string or
bytes to initially write to the file. If not
specified, then an empty file is created.
@@ -709,7 +711,7 @@ class TestCase(unittest.TestCase):
`content` is text.
cleanup: Optional cleanup policy on when/if to remove the directory (and
all its contents) at the end of the test. If None, then uses
- `self.tempfile_cleanup`.
+ :attr:`tempfile_cleanup`.
Returns:
A _TempFile representing the created file; see _TempFile class docs for
@@ -733,7 +735,7 @@ class TestCase(unittest.TestCase):
(e.g. `TestCase.enter_context`), the context is exited after the test
class's tearDownClass call.
- Contexts are are exited in the reverse order of entering. They will always
+ Contexts are exited in the reverse order of entering. They will always
be exited, regardless of test failure/success.
This is useful to eliminate per-test boilerplate when context managers
@@ -786,24 +788,49 @@ class TestCase(unittest.TestCase):
elif cleanup == TempFileCleanup.ALWAYS:
self.addCleanup(_rmtree_ignore_errors, path)
elif cleanup == TempFileCleanup.SUCCESS:
- self._internal_cleanup_on_success(_rmtree_ignore_errors, path)
+ self._internal_add_cleanup_on_success(_rmtree_ignore_errors, path)
else:
raise AssertionError('Unexpected cleanup value: {}'.format(cleanup))
- def _internal_cleanup_on_success(self, function, *args, **kwargs):
- # type: (Callable[..., object], Any, Any) -> None
+ def _internal_add_cleanup_on_success(
+ self,
+ function: Callable[..., Any],
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
+ """Adds `function` as cleanup when the test case succeeds."""
+ outcome = self._outcome
+ previous_failure_count = (
+ len(outcome.result.failures)
+ + len(outcome.result.errors)
+ + len(outcome.result.unexpectedSuccesses)
+ )
def _call_cleaner_on_success(*args, **kwargs):
- if not self._ran_and_passed():
+ if not self._internal_ran_and_passed_when_called_during_cleanup(
+ previous_failure_count):
return
function(*args, **kwargs)
self.addCleanup(_call_cleaner_on_success, *args, **kwargs)
- def _ran_and_passed(self):
- # type: () -> bool
+ def _internal_ran_and_passed_when_called_during_cleanup(
+ self,
+ previous_failure_count: int,
+ ) -> bool:
+ """Returns whether test is passed. Expected to be called during cleanup."""
outcome = self._outcome
- result = self.defaultTestResult()
- self._feedErrorsToResult(result, outcome.errors) # pytype: disable=attribute-error
- return result.wasSuccessful()
+ if sys.version_info[:2] >= (3, 11):
+ current_failure_count = (
+ len(outcome.result.failures)
+ + len(outcome.result.errors)
+ + len(outcome.result.unexpectedSuccesses)
+ )
+ return current_failure_count == previous_failure_count
+ else:
+ # Before Python 3.11 https://github.com/python/cpython/pull/28180, errors
+ # were bufferred in _Outcome before calling cleanup.
+ result = self.defaultTestResult()
+ self._feedErrorsToResult(result, outcome.errors) # pytype: disable=attribute-error
+ return result.wasSuccessful()
def shortDescription(self):
# type: () -> Text
@@ -1049,10 +1076,10 @@ class TestCase(unittest.TestCase):
"""Asserts that two sequences have the same elements (in any order).
This method, unlike assertCountEqual, doesn't care about any
- duplicates in the expected and actual sequences.
+ duplicates in the expected and actual sequences::
- >> assertSameElements([1, 1, 1, 0, 0, 0], [0, 1])
- # Doesn't raise an AssertionError
+ # Doesn't raise an AssertionError
+ assertSameElements([1, 1, 1, 0, 0, 0], [0, 1])
If possible, you should use assertCountEqual instead of
assertSameElements.
@@ -1148,6 +1175,7 @@ class TestCase(unittest.TestCase):
expression (a string or re compiled object) instead of a list.
Notes:
+
1. This function uses substring matching, i.e. the matching
succeeds if *any* substring of the error message matches *any*
regex in the list. This is more convenient for the user than
@@ -1308,6 +1336,8 @@ class TestCase(unittest.TestCase):
if not issubclass(exc_type, self.expected_exception):
return False
self.test_func(exc_value)
+ if exc_value:
+ self.exception = exc_value.with_traceback(None)
return True
@typing.overload
@@ -1497,39 +1527,39 @@ class TestCase(unittest.TestCase):
"""Asserts that total ordering has been implemented correctly.
For example, say you have a class A that compares only on its attribute x.
- Comparators other than __lt__ are omitted for brevity.
+ Comparators other than ``__lt__`` are omitted for brevity::
- class A(object):
- def __init__(self, x, y):
- self.x = x
- self.y = y
+ class A(object):
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
- def __hash__(self):
- return hash(self.x)
+ def __hash__(self):
+ return hash(self.x)
- def __lt__(self, other):
- try:
- return self.x < other.x
- except AttributeError:
- return NotImplemented
+ def __lt__(self, other):
+ try:
+ return self.x < other.x
+ except AttributeError:
+ return NotImplemented
assertTotallyOrdered will check that instances can be ordered correctly.
- For example,
+ For example::
- self.assertTotallyOrdered(
- [None], # None should come before everything else.
- [1], # Integers sort earlier.
- [A(1, 'a')],
- [A(2, 'b')], # 2 is after 1.
- [A(3, 'c'), A(3, 'd')], # The second argument is irrelevant.
- [A(4, 'z')],
- ['foo']) # Strings sort last.
+ self.assertTotallyOrdered(
+ [None], # None should come before everything else.
+ [1], # Integers sort earlier.
+ [A(1, 'a')],
+ [A(2, 'b')], # 2 is after 1.
+ [A(3, 'c'), A(3, 'd')], # The second argument is irrelevant.
+ [A(4, 'z')],
+ ['foo']) # Strings sort last.
Args:
- *groups: A list of groups of elements. Each group of elements is a list
- of objects that are equal. The elements in each group must be less
- than the elements in the group after it. For example, these groups are
- totally ordered: [None], [1], [2, 2], [3].
+ *groups: A list of groups of elements. Each group of elements is a list
+ of objects that are equal. The elements in each group must be less
+ than the elements in the group after it. For example, these groups are
+ totally ordered: ``[None]``, ``[1]``, ``[2, 2]``, ``[3]``.
**kwargs: optional msg keyword argument can be passed.
"""
@@ -2014,12 +2044,14 @@ def main(*args, **kwargs):
Usually this function is called without arguments, so the
unittest.TestProgram instance will get created with the default settings,
- so it will run all test methods of all TestCase classes in the __main__
+ so it will run all test methods of all TestCase classes in the ``__main__``
module.
Args:
- *args: Positional arguments passed through to unittest.TestProgram.__init__.
- **kwargs: Keyword arguments passed through to unittest.TestProgram.__init__.
+ *args: Positional arguments passed through to
+ ``unittest.TestProgram.__init__``.
+ **kwargs: Keyword arguments passed through to
+ ``unittest.TestProgram.__init__``.
"""
print_python_version()
_run_in_app(run_tests, args, kwargs)
@@ -2036,19 +2068,6 @@ def _is_in_app_main():
return False
-class _SavedFlag(object):
- """Helper class for saving and restoring a flag value."""
-
- def __init__(self, flag):
- self.flag = flag
- self.value = flag.value
- self.present = flag.present
-
- def restore_flag(self):
- self.flag.value = self.value
- self.flag.present = self.present
-
-
def _register_sigterm_with_faulthandler():
# type: () -> None
"""Have faulthandler dump stacks on SIGTERM. Useful to diagnose timeouts."""
@@ -2105,29 +2124,30 @@ def _run_in_app(function, args, kwargs):
if _is_in_app_main():
_register_sigterm_with_faulthandler()
- # Save command-line flags so the side effects of FLAGS(sys.argv) can be
- # undone.
- flag_objects = (FLAGS[name] for name in FLAGS)
- saved_flags = dict((f.name, _SavedFlag(f)) for f in flag_objects)
-
# Change the default of alsologtostderr from False to True, so the test
# programs's stderr will contain all the log messages.
# If --alsologtostderr=false is specified in the command-line, or user
# has called FLAGS.alsologtostderr = False before, then the value is kept
# False.
FLAGS.set_default('alsologtostderr', True)
- # Remove it from saved flags so it doesn't get restored later.
- del saved_flags['alsologtostderr']
-
- # The call FLAGS(sys.argv) parses sys.argv, returns the arguments
- # without the flags, and -- as a side effect -- modifies flag values in
- # FLAGS. We don't want the side effect, because we don't want to
- # override flag changes the program did (e.g. in __main__.main)
- # after the command-line has been parsed. So we have the for loop below
- # to change back flags to their old values.
- argv = FLAGS(sys.argv)
- for saved_flag in saved_flags.values():
- saved_flag.restore_flag()
+
+ # Here we only want to get the `argv` without the flags. To avoid any
+ # side effects of parsing flags, we temporarily stub out the `parse` method
+ stored_parse_methods = {}
+ noop_parse = lambda _: None
+ for name in FLAGS:
+ # Avoid any side effects of parsing flags.
+ stored_parse_methods[name] = FLAGS[name].parse
+ # This must be a separate loop since multiple flag names (short_name=) can
+ # point to the same flag object.
+ for name in FLAGS:
+ FLAGS[name].parse = noop_parse
+ try:
+ argv = FLAGS(sys.argv)
+ finally:
+ for name in FLAGS:
+ FLAGS[name].parse = stored_parse_methods[name]
+ sys.stdout.flush()
function(argv, args, kwargs)
else:
@@ -2164,29 +2184,29 @@ def skipThisClass(reason):
implementations between a number of concrete testcase classes.
Example usage, showing how you can share some common test methods between
- subclasses. In this example, only 'BaseTest' will be marked as skipped, and
- not RealTest or SecondRealTest:
+ subclasses. In this example, only ``BaseTest`` will be marked as skipped, and
+ not RealTest or SecondRealTest::
- @absltest.skipThisClass("Shared functionality")
- class BaseTest(absltest.TestCase):
- def test_simple_functionality(self):
- self.assertEqual(self.system_under_test.method(), 1)
+ @absltest.skipThisClass("Shared functionality")
+ class BaseTest(absltest.TestCase):
+ def test_simple_functionality(self):
+ self.assertEqual(self.system_under_test.method(), 1)
- class RealTest(BaseTest):
- def setUp(self):
- super().setUp()
- self.system_under_test = MakeSystem(argument)
+ class RealTest(BaseTest):
+ def setUp(self):
+ super().setUp()
+ self.system_under_test = MakeSystem(argument)
- def test_specific_behavior(self):
- ...
+ def test_specific_behavior(self):
+ ...
- class SecondRealTest(BaseTest):
- def setUp(self):
- super().setUp()
- self.system_under_test = MakeSystem(other_arguments)
+ class SecondRealTest(BaseTest):
+ def setUp(self):
+ super().setUp()
+ self.system_under_test = MakeSystem(other_arguments)
- def test_other_behavior(self):
- ...
+ def test_other_behavior(self):
+ ...
Args:
reason: The reason we have a skip in place. For instance: 'shared test
@@ -2526,11 +2546,14 @@ def run_tests(argv, args, kwargs): # pylint: disable=line-too-long
Args:
argv: sys.argv with the command-line flags removed from the front, i.e. the
- argv with which app.run() has called __main__.main. It is passed to
- unittest.TestProgram.__init__(argv=), which does its own flag parsing. It
- is ignored if kwargs contains an argv entry.
- args: Positional arguments passed through to unittest.TestProgram.__init__.
- kwargs: Keyword arguments passed through to unittest.TestProgram.__init__.
+ argv with which :func:`app.run()<absl.app.run>` has called
+ ``__main__.main``. It is passed to
+ ``unittest.TestProgram.__init__(argv=)``, which does its own flag parsing.
+ It is ignored if kwargs contains an argv entry.
+ args: Positional arguments passed through to
+ ``unittest.TestProgram.__init__``.
+ kwargs: Keyword arguments passed through to
+ ``unittest.TestProgram.__init__``.
"""
result = _run_and_get_tests_result(
argv, args, kwargs, xml_reporter.TextAndXMLTestRunner)
diff --git a/absl/testing/flagsaver.py b/absl/testing/flagsaver.py
index 7fe95fe..37926d7 100644
--- a/absl/testing/flagsaver.py
+++ b/absl/testing/flagsaver.py
@@ -17,40 +17,40 @@
There are many ways to save and restore. Always use the most convenient method
for a given use case.
-Here are examples of each method. They all call do_stuff() while FLAGS.someflag
-is temporarily set to 'foo'.
-
- from absl.testing import flagsaver
-
- # Use a decorator which can optionally override flags via arguments.
- @flagsaver.flagsaver(someflag='foo')
- def some_func():
- do_stuff()
-
- # Use a decorator which can optionally override flags with flagholders.
- @flagsaver.flagsaver((module.FOO_FLAG, 'foo'), (other_mod.BAR_FLAG, 23))
- def some_func():
- do_stuff()
-
- # Use a decorator which does not override flags itself.
- @flagsaver.flagsaver
- def some_func():
- FLAGS.someflag = 'foo'
- do_stuff()
-
- # Use a context manager which can optionally override flags via arguments.
- with flagsaver.flagsaver(someflag='foo'):
- do_stuff()
-
- # Save and restore the flag values yourself.
- saved_flag_values = flagsaver.save_flag_values()
- try:
- FLAGS.someflag = 'foo'
- do_stuff()
- finally:
- flagsaver.restore_flag_values(saved_flag_values)
-
-We save and restore a shallow copy of each Flag object's __dict__ attribute.
+Here are examples of each method. They all call ``do_stuff()`` while
+``FLAGS.someflag`` is temporarily set to ``'foo'``::
+
+ from absl.testing import flagsaver
+
+ # Use a decorator which can optionally override flags via arguments.
+ @flagsaver.flagsaver(someflag='foo')
+ def some_func():
+ do_stuff()
+
+ # Use a decorator which can optionally override flags with flagholders.
+ @flagsaver.flagsaver((module.FOO_FLAG, 'foo'), (other_mod.BAR_FLAG, 23))
+ def some_func():
+ do_stuff()
+
+ # Use a decorator which does not override flags itself.
+ @flagsaver.flagsaver
+ def some_func():
+ FLAGS.someflag = 'foo'
+ do_stuff()
+
+ # Use a context manager which can optionally override flags via arguments.
+ with flagsaver.flagsaver(someflag='foo'):
+ do_stuff()
+
+ # Save and restore the flag values yourself.
+ saved_flag_values = flagsaver.save_flag_values()
+ try:
+ FLAGS.someflag = 'foo'
+ do_stuff()
+ finally:
+ flagsaver.restore_flag_values(saved_flag_values)
+
+We save and restore a shallow copy of each Flag object's ``__dict__`` attribute.
This preserves all attributes of the flag, such as whether or not it was
overridden from its default value.
@@ -59,10 +59,6 @@ exception will be raised. However if you *add* a flag after saving flag values,
and then restore flag values, the added flag will be deleted with no errors.
"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import functools
import inspect
@@ -106,7 +102,7 @@ def save_flag_values(flag_values=FLAGS):
be saved. This should almost never need to be overridden.
Returns:
Dictionary mapping keys to values. Keys are flag names, values are
- corresponding __dict__ members. E.g. {'key': value_dict, ...}.
+ corresponding ``__dict__`` members. E.g. ``{'key': value_dict, ...}``.
"""
return {name: _copy_flag_dict(flag_values[name]) for name in flag_values}
@@ -181,16 +177,16 @@ class _FlagOverrider(object):
def _copy_flag_dict(flag):
- """Returns a copy of the flag object's __dict__.
+ """Returns a copy of the flag object's ``__dict__``.
- It's mostly a shallow copy of the __dict__, except it also does a shallow
+ It's mostly a shallow copy of the ``__dict__``, except it also does a shallow
copy of the validator list.
Args:
flag: flags.Flag, the flag to copy.
Returns:
- A copy of the flag object's __dict__.
+ A copy of the flag object's ``__dict__``.
"""
copy = flag.__dict__.copy()
copy['_value'] = flag.value # Ensure correct restore for C++ flags.
diff --git a/absl/testing/parameterized.py b/absl/testing/parameterized.py
index ec6a529..650d6cf 100644
--- a/absl/testing/parameterized.py
+++ b/absl/testing/parameterized.py
@@ -17,16 +17,15 @@
A parameterized test is a method in a test case that is invoked with different
argument tuples.
-A simple example:
-
- class AdditionExample(parameterized.TestCase):
- @parameterized.parameters(
- (1, 2, 3),
- (4, 5, 9),
- (1, 1, 3))
- def testAddition(self, op1, op2, result):
- self.assertEqual(result, op1 + op2)
+A simple example::
+ class AdditionExample(parameterized.TestCase):
+ @parameterized.parameters(
+ (1, 2, 3),
+ (4, 5, 9),
+ (1, 1, 3))
+ def testAddition(self, op1, op2, result):
+ self.assertEqual(result, op1 + op2)
Each invocation is a separate test case and properly isolated just
like a normal test method, with its own setUp/tearDown cycle. In the
@@ -34,15 +33,15 @@ example above, there are three separate testcases, one of which will
fail due to an assertion error (1 + 1 != 3).
Parameters for individual test cases can be tuples (with positional parameters)
-or dictionaries (with named parameters):
+or dictionaries (with named parameters)::
- class AdditionExample(parameterized.TestCase):
- @parameterized.parameters(
- {'op1': 1, 'op2': 2, 'result': 3},
- {'op1': 4, 'op2': 5, 'result': 9},
- )
- def testAddition(self, op1, op2, result):
- self.assertEqual(result, op1 + op2)
+ class AdditionExample(parameterized.TestCase):
+ @parameterized.parameters(
+ {'op1': 1, 'op2': 2, 'result': 3},
+ {'op1': 4, 'op2': 5, 'result': 9},
+ )
+ def testAddition(self, op1, op2, result):
+ self.assertEqual(result, op1 + op2)
If a parameterized test fails, the error message will show the
original test name and the parameters for that test.
@@ -50,129 +49,135 @@ original test name and the parameters for that test.
The id method of the test, used internally by the unittest framework, is also
modified to show the arguments (but note that the name reported by `id()`
doesn't match the actual test name, see below). To make sure that test names
-stay the same across several invocations, object representations like
+stay the same across several invocations, object representations like::
- >>> class Foo(object):
- ... pass
- >>> repr(Foo())
- '<__main__.Foo object at 0x23d8610>'
+ >>> class Foo(object):
+ ... pass
+ >>> repr(Foo())
+ '<__main__.Foo object at 0x23d8610>'
-are turned into '<__main__.Foo>'. When selecting a subset of test cases to run
+are turned into ``__main__.Foo``. When selecting a subset of test cases to run
on the command-line, the test cases contain an index suffix for each argument
-in the order they were passed to `parameters()` (eg. testAddition0,
+in the order they were passed to :func:`parameters` (eg. testAddition0,
testAddition1, etc.) This naming scheme is subject to change; for more reliable
-and stable names, especially in test logs, use `named_parameters()` instead.
+and stable names, especially in test logs, use :func:`named_parameters` instead.
-Tests using `named_parameters()` are similar to `parameters()`, except only
-tuples or dicts of args are supported. For tuples, the first parameter arg
+Tests using :func:`named_parameters` are similar to :func:`parameters`, except
+only tuples or dicts of args are supported. For tuples, the first parameter arg
has to be a string (or an object that returns an apt name when converted via
-str()). For dicts, a value for the key 'testcase_name' must be present and must
-be a string (or an object that returns an apt name when converted via str()):
-
- class NamedExample(parameterized.TestCase):
- @parameterized.named_parameters(
- ('Normal', 'aa', 'aaa', True),
- ('EmptyPrefix', '', 'abc', True),
- ('BothEmpty', '', '', True))
- def testStartsWith(self, prefix, string, result):
- self.assertEqual(result, string.startswith(prefix))
-
- class NamedExample(parameterized.TestCase):
- @parameterized.named_parameters(
- {'testcase_name': 'Normal',
- 'result': True, 'string': 'aaa', 'prefix': 'aa'},
- {'testcase_name': 'EmptyPrefix',
- 'result': True, 'string': 'abc', 'prefix': ''},
- {'testcase_name': 'BothEmpty',
- 'result': True, 'string': '', 'prefix': ''})
- def testStartsWith(self, prefix, string, result):
- self.assertEqual(result, string.startswith(prefix))
+``str()``). For dicts, a value for the key ``testcase_name`` must be present and
+must be a string (or an object that returns an apt name when converted via
+``str()``)::
+
+ class NamedExample(parameterized.TestCase):
+ @parameterized.named_parameters(
+ ('Normal', 'aa', 'aaa', True),
+ ('EmptyPrefix', '', 'abc', True),
+ ('BothEmpty', '', '', True))
+ def testStartsWith(self, prefix, string, result):
+ self.assertEqual(result, string.startswith(prefix))
+
+ class NamedExample(parameterized.TestCase):
+ @parameterized.named_parameters(
+ {'testcase_name': 'Normal',
+ 'result': True, 'string': 'aaa', 'prefix': 'aa'},
+ {'testcase_name': 'EmptyPrefix',
+ 'result': True, 'string': 'abc', 'prefix': ''},
+ {'testcase_name': 'BothEmpty',
+ 'result': True, 'string': '', 'prefix': ''})
+ def testStartsWith(self, prefix, string, result):
+ self.assertEqual(result, string.startswith(prefix))
Named tests also have the benefit that they can be run individually
-from the command line:
+from the command line::
- $ testmodule.py NamedExample.testStartsWithNormal
- .
- --------------------------------------------------------------------
- Ran 1 test in 0.000s
+ $ testmodule.py NamedExample.testStartsWithNormal
+ .
+ --------------------------------------------------------------------
+ Ran 1 test in 0.000s
- OK
+ OK
Parameterized Classes
=====================
+
If invocation arguments are shared across test methods in a single
TestCase class, instead of decorating all test methods
-individually, the class itself can be decorated:
+individually, the class itself can be decorated::
- @parameterized.parameters(
- (1, 2, 3),
- (4, 5, 9))
- class ArithmeticTest(parameterized.TestCase):
- def testAdd(self, arg1, arg2, result):
- self.assertEqual(arg1 + arg2, result)
+ @parameterized.parameters(
+ (1, 2, 3),
+ (4, 5, 9))
+ class ArithmeticTest(parameterized.TestCase):
+ def testAdd(self, arg1, arg2, result):
+ self.assertEqual(arg1 + arg2, result)
- def testSubtract(self, arg1, arg2, result):
- self.assertEqual(result - arg1, arg2)
+ def testSubtract(self, arg1, arg2, result):
+ self.assertEqual(result - arg1, arg2)
Inputs from Iterables
=====================
+
If parameters should be shared across several test cases, or are dynamically
created from other sources, a single non-tuple iterable can be passed into
-the decorator. This iterable will be used to obtain the test cases:
+the decorator. This iterable will be used to obtain the test cases::
- class AdditionExample(parameterized.TestCase):
- @parameterized.parameters(
- c.op1, c.op2, c.result for c in testcases
- )
- def testAddition(self, op1, op2, result):
- self.assertEqual(result, op1 + op2)
+ class AdditionExample(parameterized.TestCase):
+ @parameterized.parameters(
+ c.op1, c.op2, c.result for c in testcases
+ )
+ def testAddition(self, op1, op2, result):
+ self.assertEqual(result, op1 + op2)
Single-Argument Test Methods
============================
+
If a test method takes only one argument, the single arguments must not be
-wrapped into a tuple:
+wrapped into a tuple::
- class NegativeNumberExample(parameterized.TestCase):
- @parameterized.parameters(
- -1, -3, -4, -5
- )
- def testIsNegative(self, arg):
- self.assertTrue(IsNegative(arg))
+ class NegativeNumberExample(parameterized.TestCase):
+ @parameterized.parameters(
+ -1, -3, -4, -5
+ )
+ def testIsNegative(self, arg):
+ self.assertTrue(IsNegative(arg))
List/tuple as a Single Argument
===============================
+
If a test method takes a single argument of a list/tuple, it must be wrapped
-inside a tuple:
+inside a tuple::
- class ZeroSumExample(parameterized.TestCase):
- @parameterized.parameters(
- ([-1, 0, 1], ),
- ([-2, 0, 2], ),
- )
- def testSumIsZero(self, arg):
- self.assertEqual(0, sum(arg))
+ class ZeroSumExample(parameterized.TestCase):
+ @parameterized.parameters(
+ ([-1, 0, 1], ),
+ ([-2, 0, 2], ),
+ )
+ def testSumIsZero(self, arg):
+ self.assertEqual(0, sum(arg))
Cartesian product of Parameter Values as Parametrized Test Cases
-======================================================
+================================================================
+
If required to test method over a cartesian product of parameters,
`parameterized.product` may be used to facilitate generation of parameters
-test combinations:
+test combinations::
- class TestModuloExample(parameterized.TestCase):
- @parameterized.product(
- num=[0, 20, 80],
- modulo=[2, 4],
- expected=[0]
- )
- def testModuloResult(self, num, modulo, expected):
- self.assertEqual(expected, num % modulo)
+ class TestModuloExample(parameterized.TestCase):
+ @parameterized.product(
+ num=[0, 20, 80],
+ modulo=[2, 4],
+ expected=[0]
+ )
+ def testModuloResult(self, num, modulo, expected):
+ self.assertEqual(expected, num % modulo)
This results in 6 test cases being created - one for each combination of the
parameters. It is also possible to supply sequences of keyword argument dicts
-as elements of the cartesian product:
+as elements of the cartesian product::
@parameterized.product(
(dict(num=5, modulo=3, expected=2),
@@ -187,10 +192,11 @@ data (supplied as kwarg dicts) and for each of the two data types (supplied as
a named parameter). Multiple keyword argument dicts may be supplied if required.
Async Support
-===============================
+=============
+
If a test needs to call async functions, it can inherit from both
parameterized.TestCase and another TestCase that supports async calls, such
-as [asynctest](https://github.com/Martiusweb/asynctest):
+as [asynctest](https://github.com/Martiusweb/asynctest)::
import asynctest
@@ -674,16 +680,16 @@ def CoopTestCase(other_base_class): # pylint: disable=invalid-name
This enables the TestCase to be used in combination
with other base classes that have custom metaclasses, such as
- mox.MoxTestBase.
+ ``mox.MoxTestBase``.
- Only works with metaclasses that do not override type.__new__.
+ Only works with metaclasses that do not override ``type.__new__``.
- Example:
+ Example::
- from absl.testing import parameterized
+ from absl.testing import parameterized
- class ExampleTest(parameterized.CoopTestCase(OtherTestCase)):
- ...
+ class ExampleTest(parameterized.CoopTestCase(OtherTestCase)):
+ ...
Args:
other_base_class: (class) A test case base class.
diff --git a/absl/testing/tests/absltest_fail_fast_test.py b/absl/testing/tests/absltest_fail_fast_test.py
index dc967f9..efde8a0 100644
--- a/absl/testing/tests/absltest_fail_fast_test.py
+++ b/absl/testing/tests/absltest_fail_fast_test.py
@@ -14,10 +14,6 @@
"""Tests for test fail fast protocol."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import subprocess
from absl import logging
diff --git a/absl/testing/tests/absltest_fail_fast_test_helper.py b/absl/testing/tests/absltest_fail_fast_test_helper.py
index 339a569..a6ffb62 100644
--- a/absl/testing/tests/absltest_fail_fast_test_helper.py
+++ b/absl/testing/tests/absltest_fail_fast_test_helper.py
@@ -14,10 +14,6 @@
"""A helper test program for absltest_fail_fast_test."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import sys
diff --git a/absl/testing/tests/absltest_filtering_test.py b/absl/testing/tests/absltest_filtering_test.py
index 30a81f6..3bbb219 100644
--- a/absl/testing/tests/absltest_filtering_test.py
+++ b/absl/testing/tests/absltest_filtering_test.py
@@ -13,10 +13,6 @@
# limitations under the License.
"""Tests for test filtering protocol."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import subprocess
import sys
diff --git a/absl/testing/tests/absltest_filtering_test_helper.py b/absl/testing/tests/absltest_filtering_test_helper.py
index 2b741ed..b8bc55f 100644
--- a/absl/testing/tests/absltest_filtering_test_helper.py
+++ b/absl/testing/tests/absltest_filtering_test_helper.py
@@ -14,10 +14,6 @@
"""A helper test program for absltest_filtering_test."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import sys
diff --git a/absl/testing/tests/absltest_randomization_test.py b/absl/testing/tests/absltest_randomization_test.py
index 75a3868..e73dfce 100644
--- a/absl/testing/tests/absltest_randomization_test.py
+++ b/absl/testing/tests/absltest_randomization_test.py
@@ -14,10 +14,6 @@
"""Tests for test randomization."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import random
import subprocess
diff --git a/absl/testing/tests/absltest_randomization_testcase.py b/absl/testing/tests/absltest_randomization_testcase.py
index 18b20ff..ac2cea0 100644
--- a/absl/testing/tests/absltest_randomization_testcase.py
+++ b/absl/testing/tests/absltest_randomization_testcase.py
@@ -14,10 +14,6 @@
"""Stub tests, only for use in absltest_randomization_test.py."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import sys
diff --git a/absl/testing/tests/absltest_sharding_test.py b/absl/testing/tests/absltest_sharding_test.py
index 6411971..9bbb903 100644
--- a/absl/testing/tests/absltest_sharding_test.py
+++ b/absl/testing/tests/absltest_sharding_test.py
@@ -14,10 +14,6 @@
"""Tests for test sharding protocol."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import subprocess
diff --git a/absl/testing/tests/absltest_sharding_test_helper.py b/absl/testing/tests/absltest_sharding_test_helper.py
index 7b2f20e..57a924c 100644
--- a/absl/testing/tests/absltest_sharding_test_helper.py
+++ b/absl/testing/tests/absltest_sharding_test_helper.py
@@ -14,10 +14,6 @@
"""A helper test program for absltest_sharding_test."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import sys
from absl.testing import absltest
diff --git a/absl/testing/tests/absltest_test.py b/absl/testing/tests/absltest_test.py
index 48eeca8..68067cf 100644
--- a/absl/testing/tests/absltest_test.py
+++ b/absl/testing/tests/absltest_test.py
@@ -186,6 +186,15 @@ class TestCaseTest(absltest.TestCase, HelperMixin):
},
expect_success=True)
+ def test_app_run(self):
+ stdout, _ = self.run_helper(
+ 7,
+ ['--name=cat', '--name=dog'],
+ {'ABSLTEST_TEST_HELPER_USE_APP_RUN': '1'},
+ expect_success=True)
+ self.assertIn('Names in main() are: cat dog', stdout)
+ self.assertIn('Names in test_name_flag() are: cat dog', stdout)
+
def test_assert_in(self):
animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'}
@@ -966,6 +975,29 @@ test case
with self.assertRaisesWithPredicateMatch(ValueError, lambda e: True):
raise ValueError
+ def test_assert_raises_with_predicate_match_exception_captured(self):
+ def _raise_value_error():
+ raise ValueError
+
+ predicate = lambda e: e is not None
+ with self.assertRaisesWithPredicateMatch(ValueError, predicate) as ctx_mgr:
+ _raise_value_error()
+
+ expected = getattr(ctx_mgr, 'exception', None)
+ self.assertIsInstance(expected, ValueError)
+
+ def test_assert_raises_with_literal_match_exception_captured(self):
+ message = 'some value error'
+ def _raise_value_error():
+ raise ValueError(message)
+
+ # predicate = lambda e: e is not None
+ with self.assertRaisesWithLiteralMatch(ValueError, message) as ctx_mgr:
+ _raise_value_error()
+
+ expected = getattr(ctx_mgr, 'exception', None)
+ self.assertIsInstance(expected, ValueError)
+
def test_assert_contains_in_order(self):
# Valids
self.assertContainsInOrder(
@@ -2136,6 +2168,11 @@ class TempFileTest(absltest.TestCase, HelperMixin):
'TempFileHelperTest/test_failure',
'TempFileHelperTest/test_failure/failure',
'TempFileHelperTest/test_success',
+ 'TempFileHelperTest/test_subtest_failure',
+ 'TempFileHelperTest/test_subtest_failure/parent',
+ 'TempFileHelperTest/test_subtest_failure/successful_child',
+ 'TempFileHelperTest/test_subtest_failure/failed_child',
+ 'TempFileHelperTest/test_subtest_success',
}
self.run_tempfile_helper('SUCCESS', expected)
@@ -2144,6 +2181,8 @@ class TempFileTest(absltest.TestCase, HelperMixin):
'TempFileHelperTest',
'TempFileHelperTest/test_failure',
'TempFileHelperTest/test_success',
+ 'TempFileHelperTest/test_subtest_failure',
+ 'TempFileHelperTest/test_subtest_success',
}
self.run_tempfile_helper('ALWAYS', expected)
@@ -2154,6 +2193,14 @@ class TempFileTest(absltest.TestCase, HelperMixin):
'TempFileHelperTest/test_failure/failure',
'TempFileHelperTest/test_success',
'TempFileHelperTest/test_success/success',
+ 'TempFileHelperTest/test_subtest_failure',
+ 'TempFileHelperTest/test_subtest_failure/parent',
+ 'TempFileHelperTest/test_subtest_failure/successful_child',
+ 'TempFileHelperTest/test_subtest_failure/failed_child',
+ 'TempFileHelperTest/test_subtest_success',
+ 'TempFileHelperTest/test_subtest_success/parent',
+ 'TempFileHelperTest/test_subtest_success/child0',
+ 'TempFileHelperTest/test_subtest_success/child1',
}
self.run_tempfile_helper('OFF', expected)
@@ -2163,8 +2210,8 @@ class SkipClassTest(absltest.TestCase):
def test_incorrect_decorator_call(self):
with self.assertRaises(TypeError):
- @absltest.skipThisClass # pylint: disable=unused-variable
- class Test(absltest.TestCase):
+ @absltest.skipThisClass
+ class Test(absltest.TestCase): # pylint: disable=unused-variable
pass
def test_incorrect_decorator_subclass(self):
diff --git a/absl/testing/tests/absltest_test_helper.py b/absl/testing/tests/absltest_test_helper.py
index c6b2465..89b6721 100644
--- a/absl/testing/tests/absltest_test_helper.py
+++ b/absl/testing/tests/absltest_test_helper.py
@@ -14,26 +14,34 @@
"""Helper binary for absltest_test.py."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import tempfile
import unittest
+from absl import app
from absl import flags
from absl.testing import absltest
FLAGS = flags.FLAGS
-flags.DEFINE_integer('test_id', 0, 'Which test to run.')
+_TEST_ID = flags.DEFINE_integer('test_id', 0, 'Which test to run.')
+_NAME = flags.DEFINE_multi_string('name', [], 'List of names to print.')
+
+
+@flags.validator('name')
+def validate_name(value):
+ # This validator makes sure that the second FLAGS(sys.argv) inside
+ # absltest.main() won't actually trigger side effects of the flag parsing.
+ if len(value) > 2:
+ raise flags.ValidationError(
+ f'No more than two names should be specified, found {len(value)} names')
+ return True
class HelperTest(absltest.TestCase):
def test_flags(self):
- if FLAGS.test_id == 1:
+ if _TEST_ID.value == 1:
self.assertEqual(FLAGS.test_random_seed, 301)
if os.name == 'nt':
# On Windows, it's always in the temp dir, which doesn't start with '/'.
@@ -45,7 +53,7 @@ class HelperTest(absltest.TestCase):
'--test_tmpdir={} does not start with {}'.format(
absltest.TEST_TMPDIR.value, expected_prefix))
self.assertTrue(os.access(absltest.TEST_TMPDIR.value, os.W_OK))
- elif FLAGS.test_id == 2:
+ elif _TEST_ID.value == 2:
self.assertEqual(FLAGS.test_random_seed, 321)
self.assertEqual(
absltest.TEST_SRCDIR.value,
@@ -53,7 +61,7 @@ class HelperTest(absltest.TestCase):
self.assertEqual(
absltest.TEST_TMPDIR.value,
os.environ['ABSLTEST_TEST_HELPER_EXPECTED_TEST_TMPDIR'])
- elif FLAGS.test_id == 3:
+ elif _TEST_ID.value == 3:
self.assertEqual(FLAGS.test_random_seed, 123)
self.assertEqual(
absltest.TEST_SRCDIR.value,
@@ -61,7 +69,7 @@ class HelperTest(absltest.TestCase):
self.assertEqual(
absltest.TEST_TMPDIR.value,
os.environ['ABSLTEST_TEST_HELPER_EXPECTED_TEST_TMPDIR'])
- elif FLAGS.test_id == 4:
+ elif _TEST_ID.value == 4:
self.assertEqual(FLAGS.test_random_seed, 221)
self.assertEqual(
absltest.TEST_SRCDIR.value,
@@ -71,26 +79,35 @@ class HelperTest(absltest.TestCase):
os.environ['ABSLTEST_TEST_HELPER_EXPECTED_TEST_TMPDIR'])
else:
raise unittest.SkipTest(
- 'Not asked to run: --test_id={}'.format(FLAGS.test_id))
+ 'Not asked to run: --test_id={}'.format(_TEST_ID.value))
@unittest.expectedFailure
def test_expected_failure(self):
- if FLAGS.test_id == 5:
+ if _TEST_ID.value == 5:
self.assertEqual(1, 1) # Expected failure, got success.
else:
self.assertEqual(1, 2) # The expected failure.
def test_xml_env_vars(self):
- if FLAGS.test_id == 6:
+ if _TEST_ID.value == 6:
self.assertEqual(
FLAGS.xml_output_file,
os.environ['ABSLTEST_TEST_HELPER_EXPECTED_XML_OUTPUT_FILE'])
else:
raise unittest.SkipTest(
- 'Not asked to run: --test_id={}'.format(FLAGS.test_id))
+ 'Not asked to run: --test_id={}'.format(_TEST_ID.value))
+
+ def test_name_flag(self):
+ if _TEST_ID.value == 7:
+ print('Names in test_name_flag() are:', ' '.join(_NAME.value))
+ else:
+ raise unittest.SkipTest(
+ 'Not asked to run: --test_id={}'.format(_TEST_ID.value))
class TempFileHelperTest(absltest.TestCase):
+ """Helper test case for tempfile cleanup tests."""
+
tempfile_cleanup = absltest.TempFileCleanup[os.environ.get(
'ABSLTEST_TEST_HELPER_TEMPFILE_CLEANUP', 'SUCCESS')]
@@ -101,6 +118,29 @@ class TempFileHelperTest(absltest.TestCase):
def test_success(self):
self.create_tempfile('success')
+ def test_subtest_failure(self):
+ self.create_tempfile('parent')
+ with self.subTest('success'):
+ self.create_tempfile('successful_child')
+ with self.subTest('failure'):
+ self.create_tempfile('failed_child')
+ self.fail('expected failure')
-if __name__ == '__main__':
+ def test_subtest_success(self):
+ self.create_tempfile('parent')
+ for i in range(2):
+ with self.subTest(f'success{i}'):
+ self.create_tempfile(f'child{i}')
+
+
+def main(argv):
+ del argv # Unused.
+ print('Names in main() are:', ' '.join(_NAME.value))
absltest.main()
+
+
+if __name__ == '__main__':
+ if os.environ.get('ABSLTEST_TEST_HELPER_USE_APP_RUN'):
+ app.run(main)
+ else:
+ absltest.main()
diff --git a/absl/testing/tests/flagsaver_test.py b/absl/testing/tests/flagsaver_test.py
index 3439a32..e98cd06 100644
--- a/absl/testing/tests/flagsaver_test.py
+++ b/absl/testing/tests/flagsaver_test.py
@@ -13,10 +13,6 @@
# limitations under the License.
"""Tests for flagsaver."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
from absl import flags
from absl.testing import absltest
from absl.testing import flagsaver
diff --git a/absl/testing/tests/xml_reporter_helper_test.py b/absl/testing/tests/xml_reporter_helper_test.py
index 661bbdc..af3c63d 100644
--- a/absl/testing/tests/xml_reporter_helper_test.py
+++ b/absl/testing/tests/xml_reporter_helper_test.py
@@ -12,10 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import random
from absl import flags
diff --git a/absl/testing/tests/xml_reporter_test.py b/absl/testing/tests/xml_reporter_test.py
index 0261f64..c0d43a6 100644
--- a/absl/testing/tests/xml_reporter_test.py
+++ b/absl/testing/tests/xml_reporter_test.py
@@ -64,12 +64,12 @@ def xml_escaped_exception_type(exception_type):
OUTPUT_STRING = '\n'.join([
r'<\?xml version="1.0"\?>',
('<testsuites name="" tests="%(tests)d" failures="%(failures)d"'
- ' errors="%(errors)d" time="%(run_time).1f" timestamp="%(start_time)s">'),
+ ' errors="%(errors)d" time="%(run_time).3f" timestamp="%(start_time)s">'),
('<testsuite name="%(suite_name)s" tests="%(tests)d"'
- ' failures="%(failures)d" errors="%(errors)d" time="%(run_time).1f"'
+ ' failures="%(failures)d" errors="%(errors)d" time="%(run_time).3f"'
' timestamp="%(start_time)s">'),
(' <testcase name="%(test_name)s" status="%(status)s" result="%(result)s"'
- ' time="%(run_time).1f" classname="%(classname)s"'
+ ' time="%(run_time).3f" classname="%(classname)s"'
' timestamp="%(start_time)s">%(message)s'),
' </testcase>', '</testsuite>',
'</testsuites>',
@@ -696,8 +696,8 @@ class TextAndXMLTestResultTest(absltest.TestCase):
run_time = max(end_time1, end_time2) - min(start_time1, start_time2)
timestamp = self._iso_timestamp(start_time1)
expected_prefix = """<?xml version="1.0"?>
-<testsuites name="" tests="2" failures="0" errors="0" time="%.1f" timestamp="%s">
-<testsuite name="MockTest" tests="2" failures="0" errors="0" time="%.1f" timestamp="%s">
+<testsuites name="" tests="2" failures="0" errors="0" time="%.3f" timestamp="%s">
+<testsuite name="MockTest" tests="2" failures="0" errors="0" time="%.3f" timestamp="%s">
""" % (run_time, timestamp, run_time, timestamp)
xml_output = self.xml_stream.getvalue()
self.assertTrue(
diff --git a/absl/testing/xml_reporter.py b/absl/testing/xml_reporter.py
index da56e39..591eb7e 100644
--- a/absl/testing/xml_reporter.py
+++ b/absl/testing/xml_reporter.py
@@ -14,10 +14,6 @@
"""A Python test reporter that generates test reports in JUnit XML format."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import datetime
import re
import sys
@@ -206,7 +202,7 @@ class _TestCaseResult(object):
('name', '%s' % self.name),
('status', '%s' % status),
('result', '%s' % result),
- ('time', '%.1f' % self.run_time),
+ ('time', '%.3f' % self.run_time),
('classname', self.full_class_name),
('timestamp', _iso8601_timestamp(self.start_time)),
]
@@ -267,7 +263,7 @@ class _TestSuiteResult(object):
('tests', '%d' % overall_test_count),
('failures', '%d' % overall_failures),
('errors', '%d' % overall_errors),
- ('time', '%.1f' % (self.overall_end_time - self.overall_start_time)),
+ ('time', '%.3f' % (self.overall_end_time - self.overall_start_time)),
('timestamp', _iso8601_timestamp(self.overall_start_time)),
]
_print_xml_element_header('testsuites', overall_attributes, stream)
@@ -289,7 +285,7 @@ class _TestSuiteResult(object):
('tests', '%d' % len(suite)),
('failures', '%d' % failures),
('errors', '%d' % errors),
- ('time', '%.1f' % (suite_end_time - suite_start_time)),
+ ('time', '%.3f' % (suite_end_time - suite_start_time)),
('timestamp', _iso8601_timestamp(suite_start_time)),
]
_print_xml_element_header('testsuite', suite_attributes, stream)
diff --git a/absl/tests/app_test_helper.py b/absl/tests/app_test_helper.py
index 6bd1a89..f9fbdec 100644
--- a/absl/tests/app_test_helper.py
+++ b/absl/tests/app_test_helper.py
@@ -14,10 +14,6 @@
"""Helper script used by app_test.py."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import sys
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..8f39a96
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,37 @@
+# Minimal makefile for Sphinx documentation
+#
+# To generate html docs locally:
+# Recommend doing this from a virtual environment:
+# $ sudo apt-get install virtualenv python3-venv
+# $ virtualenv myproject # or python3 -m venv myproject
+# $ source myproject/bin/activate
+#
+# Then install sphinx packages (if running locally)
+# $ pip install m2r2
+# $ pip install sphinxcontrib-apidoc
+# $ pip install sphinx-rtd-theme
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SOURCEDIR = ../absl
+RSTDIR = source
+HTMLDIR = build
+HTMLDIR = build
+# If you change the conf.py apidoc_excluded_paths, you need to update
+# the excluded paths in APIDOC_EXCLUDE too. The paths are relative
+# to the docs/ directory (if you want to filter out absl/tests you need
+# to use "../*/tests".)
+APIDOC_EXCLUDE = ../*/*/tests/* ../*/tests/*
+SPHINXBUILD ?= sphinx-build
+SPHINXAPIDOC ?= sphinx-apidoc
+
+# Build .rst files for all Python sources in SOURCEDIR.
+# This rule isn't called by readthedocs, its only used for manual testing.
+rstfiles:
+ @$(SPHINXAPIDOC) -o $(RSTDIR) $(SOURCEDIR) $(APIDOC_EXCLUDE)
+
+# Run after "make rstfiles"
+# You can review sphinx generated files in docs/build directory.
+htmlfiles:
+ @$(SPHINXBUILD) -b html -c $(RSTDIR) $(RSTDIR) $(HTMLDIR)
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..364c154
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,60 @@
+"""Sphinx config file for https://github.com/abseil/abseil-py."""
+
+import os
+import sys
+
+# -- Project information
+project = 'Abseil Python Common Libraries'
+copyright = '2022, Abseil' # pylint: disable=redefined-builtin
+author = 'The Abseil Authors'
+
+release = ''
+version = ''
+
+# -- General configuration
+
+extensions = [
+ 'sphinx.ext.duration',
+ 'sphinx.ext.doctest',
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.autosummary',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.viewcode',
+ 'sphinx.ext.coverage',
+ 'sphinxcontrib.apidoc', # convert .py sources to .rst docs.
+ 'm2r2', # for .md files
+]
+
+# sphinxcontrib.apidoc vars
+apidoc_module_dir = '../../absl'
+apidoc_output_dir = '.'
+apidoc_toc_file = False
+apidoc_excluded_paths = [
+ '*/tests/*',
+ 'tests/*',
+]
+apidoc_separate_modules = True
+
+intersphinx_mapping = {
+ 'python': ('https://docs.python.org/3/', None),
+ 'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
+}
+intersphinx_disabled_domains = ['std']
+
+source_suffix = {
+ '.rst': 'restructuredtext',
+ '.md': 'markdown',
+}
+
+templates_path = ['_templates']
+
+# -- Options for HTML output
+
+html_theme = 'sphinx_rtd_theme'
+
+# -- Options for EPUB output
+epub_show_urls = 'footnote'
+
+sys.path.insert(0, os.path.abspath('../..')) # access to README.md
+sys.path.insert(0, os.path.abspath('../../absl')) # access to python sources
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..4bd00f9
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,16 @@
+.. include:: readme_link.rst
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+ :glob:
+
+ *
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/source/readme_link.rst b/docs/source/readme_link.rst
new file mode 100644
index 0000000..3bd447c
--- /dev/null
+++ b/docs/source/readme_link.rst
@@ -0,0 +1 @@
+.. mdinclude:: ../../README.md
diff --git a/setup.py b/setup.py
index 13b2353..f947fd7 100644
--- a/setup.py
+++ b/setup.py
@@ -14,10 +14,6 @@
"""Abseil setup configuration."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import os
import sys
@@ -47,7 +43,7 @@ with open(_README_PATH, 'rb') as fp:
setuptools.setup(
name='absl-py',
- version='1.1.0',
+ version='1.3.0',
description=(
'Abseil Python Common Libraries, '
'see https://github.com/abseil/abseil-py.'),
@@ -68,6 +64,7 @@ setuptools.setup(
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
'Intended Audience :: Developers',
'Topic :: Software Development :: Libraries :: Python Modules',
'License :: OSI Approved :: Apache Software License',
diff --git a/smoke_tests/sample_app.py b/smoke_tests/sample_app.py
index 532a11e..d45be96 100644
--- a/smoke_tests/sample_app.py
+++ b/smoke_tests/sample_app.py
@@ -14,10 +14,6 @@
"""Test helper for smoke_test.sh."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
import sys
from absl import app
diff --git a/smoke_tests/sample_test.py b/smoke_tests/sample_test.py
index 713677a..2d3f3e4 100644
--- a/smoke_tests/sample_test.py
+++ b/smoke_tests/sample_test.py
@@ -14,10 +14,6 @@
"""Test helper for smoke_test.sh."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
from absl.testing import absltest