Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .venv/lib/python3.13/site-packages/absl/__pycache__/__init__.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/__pycache__/app.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/__pycache__/command_name.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__init__.py +220 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/__init__.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_argument_parser.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_defines.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_exceptions.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_flag.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_flagvalues.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_helpers.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_validators.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/_validators_classes.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/__pycache__/argparse_flags.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/flags/_argument_parser.py +632 -0
- .venv/lib/python3.13/site-packages/absl/flags/_defines.py +1702 -0
- .venv/lib/python3.13/site-packages/absl/flags/_exceptions.py +107 -0
- .venv/lib/python3.13/site-packages/absl/flags/_flag.py +566 -0
- .venv/lib/python3.13/site-packages/absl/flags/_flagvalues.py +1552 -0
- .venv/lib/python3.13/site-packages/absl/flags/_helpers.py +403 -0
- .venv/lib/python3.13/site-packages/absl/flags/_validators.py +353 -0
- .venv/lib/python3.13/site-packages/absl/flags/_validators_classes.py +172 -0
- .venv/lib/python3.13/site-packages/absl/flags/argparse_flags.py +390 -0
- .venv/lib/python3.13/site-packages/absl/logging/__init__.py +1335 -0
- .venv/lib/python3.13/site-packages/absl/logging/__init__.pyi +278 -0
- .venv/lib/python3.13/site-packages/absl/logging/__pycache__/__init__.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/logging/__pycache__/converter.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/logging/converter.py +214 -0
- .venv/lib/python3.13/site-packages/absl/testing/__init__.py +13 -0
- .venv/lib/python3.13/site-packages/absl/testing/__pycache__/__init__.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/testing/__pycache__/_bazelize_command.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/testing/__pycache__/_pretty_print_reporter.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/testing/__pycache__/flagsaver.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/testing/__pycache__/parameterized.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/testing/__pycache__/xml_reporter.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/absl/testing/_bazelize_command.py +68 -0
- .venv/lib/python3.13/site-packages/absl/testing/_pretty_print_reporter.py +92 -0
- .venv/lib/python3.13/site-packages/absl/testing/absltest.py +0 -0
- .venv/lib/python3.13/site-packages/absl/testing/flagsaver.py +403 -0
- .venv/lib/python3.13/site-packages/absl/testing/parameterized.py +726 -0
- .venv/lib/python3.13/site-packages/absl/testing/xml_reporter.py +570 -0
- .venv/lib/python3.13/site-packages/attrs/__pycache__/__init__.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/attrs/__pycache__/converters.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/attrs/__pycache__/exceptions.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/attrs/__pycache__/filters.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/attrs/__pycache__/setters.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/attrs/__pycache__/validators.cpython-313.pyc +0 -0
- .venv/lib/python3.13/site-packages/black/resources/__init__.cpython-313-x86_64-linux-gnu.so +0 -0
- .venv/lib/python3.13/site-packages/black/resources/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/black/resources/black.schema.json +161 -0
.venv/lib/python3.13/site-packages/absl/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (207 Bytes). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/__pycache__/app.cpython-313.pyc
ADDED
|
Binary file (21.2 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/__pycache__/command_name.cpython-313.pyc
ADDED
|
Binary file (2.34 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__init__.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""This package is used to define and parse command line flags.
|
| 15 |
+
|
| 16 |
+
This package defines a *distributed* flag-definition policy: rather than
|
| 17 |
+
an application having to define all flags in or near main(), each Python
|
| 18 |
+
module defines flags that are useful to it. When one Python module
|
| 19 |
+
imports another, it gains access to the other's flags. (This is
|
| 20 |
+
implemented by having all modules share a common, global registry object
|
| 21 |
+
containing all the flag information.)
|
| 22 |
+
|
| 23 |
+
Flags are defined through the use of one of the DEFINE_xxx functions.
|
| 24 |
+
The specific function used determines how the flag is parsed, checked,
|
| 25 |
+
and optionally type-converted, when it's seen on the command line.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
import sys
|
| 29 |
+
|
| 30 |
+
from absl.flags import _argument_parser
|
| 31 |
+
from absl.flags import _defines
|
| 32 |
+
from absl.flags import _exceptions
|
| 33 |
+
from absl.flags import _flag
|
| 34 |
+
from absl.flags import _flagvalues
|
| 35 |
+
from absl.flags import _helpers
|
| 36 |
+
from absl.flags import _validators
|
| 37 |
+
|
| 38 |
+
__all__ = (
|
| 39 |
+
'DEFINE',
|
| 40 |
+
'DEFINE_flag',
|
| 41 |
+
'DEFINE_string',
|
| 42 |
+
'DEFINE_boolean',
|
| 43 |
+
'DEFINE_bool',
|
| 44 |
+
'DEFINE_float',
|
| 45 |
+
'DEFINE_integer',
|
| 46 |
+
'DEFINE_enum',
|
| 47 |
+
'DEFINE_enum_class',
|
| 48 |
+
'DEFINE_list',
|
| 49 |
+
'DEFINE_spaceseplist',
|
| 50 |
+
'DEFINE_multi',
|
| 51 |
+
'DEFINE_multi_string',
|
| 52 |
+
'DEFINE_multi_integer',
|
| 53 |
+
'DEFINE_multi_float',
|
| 54 |
+
'DEFINE_multi_enum',
|
| 55 |
+
'DEFINE_multi_enum_class',
|
| 56 |
+
'DEFINE_alias',
|
| 57 |
+
# Flag validators.
|
| 58 |
+
'register_validator',
|
| 59 |
+
'validator',
|
| 60 |
+
'register_multi_flags_validator',
|
| 61 |
+
'multi_flags_validator',
|
| 62 |
+
'mark_flag_as_required',
|
| 63 |
+
'mark_flags_as_required',
|
| 64 |
+
'mark_flags_as_mutual_exclusive',
|
| 65 |
+
'mark_bool_flags_as_mutual_exclusive',
|
| 66 |
+
# Flag modifiers.
|
| 67 |
+
'set_default',
|
| 68 |
+
'override_value',
|
| 69 |
+
# Key flag related functions.
|
| 70 |
+
'declare_key_flag',
|
| 71 |
+
'adopt_module_key_flags',
|
| 72 |
+
'disclaim_key_flags',
|
| 73 |
+
# Module exceptions.
|
| 74 |
+
'Error',
|
| 75 |
+
'CantOpenFlagFileError',
|
| 76 |
+
'DuplicateFlagError',
|
| 77 |
+
'IllegalFlagValueError',
|
| 78 |
+
'UnrecognizedFlagError',
|
| 79 |
+
'UnparsedFlagAccessError',
|
| 80 |
+
'ValidationError',
|
| 81 |
+
'FlagNameConflictsWithMethodError',
|
| 82 |
+
# Public classes.
|
| 83 |
+
'Flag',
|
| 84 |
+
'BooleanFlag',
|
| 85 |
+
'EnumFlag',
|
| 86 |
+
'EnumClassFlag',
|
| 87 |
+
'MultiFlag',
|
| 88 |
+
'MultiEnumClassFlag',
|
| 89 |
+
'FlagHolder',
|
| 90 |
+
'FlagValues',
|
| 91 |
+
'ArgumentParser',
|
| 92 |
+
'BooleanParser',
|
| 93 |
+
'EnumParser',
|
| 94 |
+
'EnumClassParser',
|
| 95 |
+
'ArgumentSerializer',
|
| 96 |
+
'FloatParser',
|
| 97 |
+
'IntegerParser',
|
| 98 |
+
'BaseListParser',
|
| 99 |
+
'ListParser',
|
| 100 |
+
'ListSerializer',
|
| 101 |
+
'EnumClassListSerializer',
|
| 102 |
+
'CsvListSerializer',
|
| 103 |
+
'WhitespaceSeparatedListParser',
|
| 104 |
+
'EnumClassSerializer',
|
| 105 |
+
# Helper functions.
|
| 106 |
+
'get_help_width',
|
| 107 |
+
'text_wrap',
|
| 108 |
+
'flag_dict_to_args',
|
| 109 |
+
'doc_to_help',
|
| 110 |
+
# The global FlagValues instance.
|
| 111 |
+
'FLAGS',
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
# Initialize the FLAGS_MODULE as early as possible.
|
| 115 |
+
# It's only used by adopt_module_key_flags to take SPECIAL_FLAGS into account.
|
| 116 |
+
_helpers.FLAGS_MODULE = sys.modules[__name__]
|
| 117 |
+
|
| 118 |
+
# Add current module to disclaimed module ids.
|
| 119 |
+
_helpers.disclaim_module_ids.add(id(sys.modules[__name__]))
|
| 120 |
+
|
| 121 |
+
# DEFINE functions. They are explained in more details in the module doc string.
|
| 122 |
+
# pylint: disable=invalid-name
|
| 123 |
+
DEFINE = _defines.DEFINE
|
| 124 |
+
DEFINE_flag = _defines.DEFINE_flag
|
| 125 |
+
DEFINE_string = _defines.DEFINE_string
|
| 126 |
+
DEFINE_boolean = _defines.DEFINE_boolean
|
| 127 |
+
DEFINE_bool = DEFINE_boolean # Match C++ API.
|
| 128 |
+
DEFINE_float = _defines.DEFINE_float
|
| 129 |
+
DEFINE_integer = _defines.DEFINE_integer
|
| 130 |
+
DEFINE_enum = _defines.DEFINE_enum
|
| 131 |
+
DEFINE_enum_class = _defines.DEFINE_enum_class
|
| 132 |
+
DEFINE_list = _defines.DEFINE_list
|
| 133 |
+
DEFINE_spaceseplist = _defines.DEFINE_spaceseplist
|
| 134 |
+
DEFINE_multi = _defines.DEFINE_multi
|
| 135 |
+
DEFINE_multi_string = _defines.DEFINE_multi_string
|
| 136 |
+
DEFINE_multi_integer = _defines.DEFINE_multi_integer
|
| 137 |
+
DEFINE_multi_float = _defines.DEFINE_multi_float
|
| 138 |
+
DEFINE_multi_enum = _defines.DEFINE_multi_enum
|
| 139 |
+
DEFINE_multi_enum_class = _defines.DEFINE_multi_enum_class
|
| 140 |
+
DEFINE_alias = _defines.DEFINE_alias
|
| 141 |
+
# pylint: enable=invalid-name
|
| 142 |
+
|
| 143 |
+
# Flag validators.
|
| 144 |
+
register_validator = _validators.register_validator
|
| 145 |
+
validator = _validators.validator
|
| 146 |
+
register_multi_flags_validator = _validators.register_multi_flags_validator
|
| 147 |
+
multi_flags_validator = _validators.multi_flags_validator
|
| 148 |
+
mark_flag_as_required = _validators.mark_flag_as_required
|
| 149 |
+
mark_flags_as_required = _validators.mark_flags_as_required
|
| 150 |
+
mark_flags_as_mutual_exclusive = _validators.mark_flags_as_mutual_exclusive
|
| 151 |
+
mark_bool_flags_as_mutual_exclusive = _validators.mark_bool_flags_as_mutual_exclusive
|
| 152 |
+
|
| 153 |
+
# Flag modifiers.
|
| 154 |
+
set_default = _defines.set_default
|
| 155 |
+
override_value = _defines.override_value
|
| 156 |
+
|
| 157 |
+
# Key flag related functions.
|
| 158 |
+
declare_key_flag = _defines.declare_key_flag
|
| 159 |
+
adopt_module_key_flags = _defines.adopt_module_key_flags
|
| 160 |
+
disclaim_key_flags = _defines.disclaim_key_flags
|
| 161 |
+
|
| 162 |
+
# Module exceptions.
|
| 163 |
+
# pylint: disable=invalid-name
|
| 164 |
+
Error = _exceptions.Error
|
| 165 |
+
CantOpenFlagFileError = _exceptions.CantOpenFlagFileError
|
| 166 |
+
DuplicateFlagError = _exceptions.DuplicateFlagError
|
| 167 |
+
IllegalFlagValueError = _exceptions.IllegalFlagValueError
|
| 168 |
+
UnrecognizedFlagError = _exceptions.UnrecognizedFlagError
|
| 169 |
+
UnparsedFlagAccessError = _exceptions.UnparsedFlagAccessError
|
| 170 |
+
ValidationError = _exceptions.ValidationError
|
| 171 |
+
FlagNameConflictsWithMethodError = _exceptions.FlagNameConflictsWithMethodError
|
| 172 |
+
|
| 173 |
+
# Public classes.
|
| 174 |
+
Flag = _flag.Flag
|
| 175 |
+
BooleanFlag = _flag.BooleanFlag
|
| 176 |
+
EnumFlag = _flag.EnumFlag
|
| 177 |
+
EnumClassFlag = _flag.EnumClassFlag
|
| 178 |
+
MultiFlag = _flag.MultiFlag
|
| 179 |
+
MultiEnumClassFlag = _flag.MultiEnumClassFlag
|
| 180 |
+
FlagHolder = _flagvalues.FlagHolder
|
| 181 |
+
FlagValues = _flagvalues.FlagValues
|
| 182 |
+
ArgumentParser = _argument_parser.ArgumentParser
|
| 183 |
+
BooleanParser = _argument_parser.BooleanParser
|
| 184 |
+
EnumParser = _argument_parser.EnumParser
|
| 185 |
+
EnumClassParser = _argument_parser.EnumClassParser
|
| 186 |
+
ArgumentSerializer = _argument_parser.ArgumentSerializer
|
| 187 |
+
FloatParser = _argument_parser.FloatParser
|
| 188 |
+
IntegerParser = _argument_parser.IntegerParser
|
| 189 |
+
BaseListParser = _argument_parser.BaseListParser
|
| 190 |
+
ListParser = _argument_parser.ListParser
|
| 191 |
+
ListSerializer = _argument_parser.ListSerializer
|
| 192 |
+
EnumClassListSerializer = _argument_parser.EnumClassListSerializer
|
| 193 |
+
CsvListSerializer = _argument_parser.CsvListSerializer
|
| 194 |
+
WhitespaceSeparatedListParser = _argument_parser.WhitespaceSeparatedListParser
|
| 195 |
+
EnumClassSerializer = _argument_parser.EnumClassSerializer
|
| 196 |
+
# pylint: enable=invalid-name
|
| 197 |
+
|
| 198 |
+
# Helper functions.
|
| 199 |
+
get_help_width = _helpers.get_help_width
|
| 200 |
+
text_wrap = _helpers.text_wrap
|
| 201 |
+
flag_dict_to_args = _helpers.flag_dict_to_args
|
| 202 |
+
doc_to_help = _helpers.doc_to_help
|
| 203 |
+
|
| 204 |
+
# Special flags.
|
| 205 |
+
_helpers.SPECIAL_FLAGS = FlagValues()
|
| 206 |
+
|
| 207 |
+
DEFINE_string(
|
| 208 |
+
'flagfile', '',
|
| 209 |
+
'Insert flag definitions from the given file into the command line.',
|
| 210 |
+
_helpers.SPECIAL_FLAGS) # pytype: disable=wrong-arg-types
|
| 211 |
+
|
| 212 |
+
DEFINE_string('undefok', '',
|
| 213 |
+
'comma-separated list of flag names that it is okay to specify '
|
| 214 |
+
'on the command line even if the program does not define a flag '
|
| 215 |
+
'with that name. IMPORTANT: flags in this list that have '
|
| 216 |
+
'arguments MUST use the --flag=value format.',
|
| 217 |
+
_helpers.SPECIAL_FLAGS) # pytype: disable=wrong-arg-types
|
| 218 |
+
|
| 219 |
+
#: The global FlagValues instance.
|
| 220 |
+
FLAGS = _flagvalues.FLAGS
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (5.75 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_argument_parser.cpython-313.pyc
ADDED
|
Binary file (30.8 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_defines.cpython-313.pyc
ADDED
|
Binary file (53.2 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_exceptions.cpython-313.pyc
ADDED
|
Binary file (4.68 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_flag.cpython-313.pyc
ADDED
|
Binary file (26.4 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_flagvalues.cpython-313.pyc
ADDED
|
Binary file (62.8 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_helpers.cpython-313.pyc
ADDED
|
Binary file (13.5 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_validators.cpython-313.pyc
ADDED
|
Binary file (15.1 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/_validators_classes.cpython-313.pyc
ADDED
|
Binary file (7.6 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/__pycache__/argparse_flags.cpython-313.pyc
ADDED
|
Binary file (15.1 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/flags/_argument_parser.py
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Contains base classes used to parse and convert arguments.
|
| 16 |
+
|
| 17 |
+
Do NOT import this module directly. Import the flags package and use the
|
| 18 |
+
aliases defined at the package level instead.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import collections
|
| 22 |
+
from collections.abc import Iterable, Sequence
|
| 23 |
+
import csv
|
| 24 |
+
import enum
|
| 25 |
+
import io
|
| 26 |
+
import string
|
| 27 |
+
from typing import Any, Generic, TypeVar
|
| 28 |
+
from xml.dom import minidom
|
| 29 |
+
|
| 30 |
+
from absl.flags import _helpers
|
| 31 |
+
|
| 32 |
+
_T = TypeVar('_T')
|
| 33 |
+
_ET = TypeVar('_ET', bound=enum.Enum)
|
| 34 |
+
_N = TypeVar('_N', int, float)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class _ArgumentParserCache(type):
|
| 38 |
+
"""Metaclass used to cache and share argument parsers among flags."""
|
| 39 |
+
|
| 40 |
+
_instances: dict[Any, Any] = {}
|
| 41 |
+
|
| 42 |
+
def __call__(cls, *args, **kwargs):
|
| 43 |
+
"""Returns an instance of the argument parser cls.
|
| 44 |
+
|
| 45 |
+
This method overrides behavior of the __new__ methods in
|
| 46 |
+
all subclasses of ArgumentParser (inclusive). If an instance
|
| 47 |
+
for cls with the same set of arguments exists, this instance is
|
| 48 |
+
returned, otherwise a new instance is created.
|
| 49 |
+
|
| 50 |
+
If any keyword arguments are defined, or the values in args
|
| 51 |
+
are not hashable, this method always returns a new instance of
|
| 52 |
+
cls.
|
| 53 |
+
|
| 54 |
+
Args:
|
| 55 |
+
*args: Positional initializer arguments.
|
| 56 |
+
**kwargs: Initializer keyword arguments.
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
An instance of cls, shared or new.
|
| 60 |
+
"""
|
| 61 |
+
if kwargs:
|
| 62 |
+
return type.__call__(cls, *args, **kwargs)
|
| 63 |
+
else:
|
| 64 |
+
instances = cls._instances
|
| 65 |
+
key = (cls,) + tuple(args)
|
| 66 |
+
try:
|
| 67 |
+
return instances[key]
|
| 68 |
+
except KeyError:
|
| 69 |
+
# No cache entry for key exists, create a new one.
|
| 70 |
+
return instances.setdefault(key, type.__call__(cls, *args))
|
| 71 |
+
except TypeError:
|
| 72 |
+
# An object in args cannot be hashed, always return
|
| 73 |
+
# a new instance.
|
| 74 |
+
return type.__call__(cls, *args)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class ArgumentParser(Generic[_T], metaclass=_ArgumentParserCache):
|
| 78 |
+
"""Base class used to parse and convert arguments.
|
| 79 |
+
|
| 80 |
+
The :meth:`parse` method checks to make sure that the string argument is a
|
| 81 |
+
legal value and convert it to a native type. If the value cannot be
|
| 82 |
+
converted, it should throw a ``ValueError`` exception with a human
|
| 83 |
+
readable explanation of why the value is illegal.
|
| 84 |
+
|
| 85 |
+
Subclasses should also define a syntactic_help string which may be
|
| 86 |
+
presented to the user to describe the form of the legal values.
|
| 87 |
+
|
| 88 |
+
Argument parser classes must be stateless, since instances are cached
|
| 89 |
+
and shared between flags. Initializer arguments are allowed, but all
|
| 90 |
+
member variables must be derived from initializer arguments only.
|
| 91 |
+
"""
|
| 92 |
+
|
| 93 |
+
syntactic_help: str = ''
|
| 94 |
+
|
| 95 |
+
def parse(self, argument: str) -> _T | None:
|
| 96 |
+
"""Parses the string argument and returns the native value.
|
| 97 |
+
|
| 98 |
+
By default it returns its argument unmodified.
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
argument: string argument passed in the commandline.
|
| 102 |
+
|
| 103 |
+
Raises:
|
| 104 |
+
ValueError: Raised when it fails to parse the argument.
|
| 105 |
+
TypeError: Raised when the argument has the wrong type.
|
| 106 |
+
|
| 107 |
+
Returns:
|
| 108 |
+
The parsed value in native type.
|
| 109 |
+
"""
|
| 110 |
+
if not isinstance(argument, str):
|
| 111 |
+
raise TypeError('flag value must be a string, found "{}"'.format(
|
| 112 |
+
type(argument)))
|
| 113 |
+
return argument # type: ignore[return-value]
|
| 114 |
+
|
| 115 |
+
def flag_type(self) -> str:
|
| 116 |
+
"""Returns a string representing the type of the flag."""
|
| 117 |
+
return 'string'
|
| 118 |
+
|
| 119 |
+
def _custom_xml_dom_elements(
|
| 120 |
+
self, doc: minidom.Document
|
| 121 |
+
) -> list[minidom.Element]:
|
| 122 |
+
"""Returns a list of minidom.Element to add additional flag information.
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
doc: minidom.Document, the DOM document it should create nodes from.
|
| 126 |
+
"""
|
| 127 |
+
del doc # Unused.
|
| 128 |
+
return []
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class ArgumentSerializer(Generic[_T]):
|
| 132 |
+
"""Base class for generating string representations of a flag value."""
|
| 133 |
+
|
| 134 |
+
def serialize(self, value: _T) -> str:
|
| 135 |
+
"""Returns a serialized string of the value."""
|
| 136 |
+
return str(value)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
class NumericParser(ArgumentParser[_N]):
|
| 140 |
+
"""Parser of numeric values.
|
| 141 |
+
|
| 142 |
+
Parsed value may be bounded to a given upper and lower bound.
|
| 143 |
+
"""
|
| 144 |
+
|
| 145 |
+
lower_bound: _N | None
|
| 146 |
+
upper_bound: _N | None
|
| 147 |
+
|
| 148 |
+
def is_outside_bounds(self, val: _N) -> bool:
|
| 149 |
+
"""Returns whether the value is outside the bounds or not."""
|
| 150 |
+
return ((self.lower_bound is not None and val < self.lower_bound) or
|
| 151 |
+
(self.upper_bound is not None and val > self.upper_bound))
|
| 152 |
+
|
| 153 |
+
def parse(self, argument: str | _N) -> _N:
|
| 154 |
+
"""See base class."""
|
| 155 |
+
val = self.convert(argument)
|
| 156 |
+
if self.is_outside_bounds(val):
|
| 157 |
+
raise ValueError('%s is not %s' % (val, self.syntactic_help))
|
| 158 |
+
return val
|
| 159 |
+
|
| 160 |
+
def _custom_xml_dom_elements(
|
| 161 |
+
self, doc: minidom.Document
|
| 162 |
+
) -> list[minidom.Element]:
|
| 163 |
+
elements = []
|
| 164 |
+
if self.lower_bound is not None:
|
| 165 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 166 |
+
doc, 'lower_bound', self.lower_bound))
|
| 167 |
+
if self.upper_bound is not None:
|
| 168 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 169 |
+
doc, 'upper_bound', self.upper_bound))
|
| 170 |
+
return elements
|
| 171 |
+
|
| 172 |
+
def convert(self, argument: str | _N) -> _N:
|
| 173 |
+
"""Returns the correct numeric value of argument.
|
| 174 |
+
|
| 175 |
+
Subclass must implement this method, and raise TypeError if argument is not
|
| 176 |
+
string or has the right numeric type.
|
| 177 |
+
|
| 178 |
+
Args:
|
| 179 |
+
argument: string argument passed in the commandline, or the numeric type.
|
| 180 |
+
|
| 181 |
+
Raises:
|
| 182 |
+
TypeError: Raised when argument is not a string or the right numeric type.
|
| 183 |
+
ValueError: Raised when failed to convert argument to the numeric value.
|
| 184 |
+
"""
|
| 185 |
+
raise NotImplementedError
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class FloatParser(NumericParser[float]):
|
| 189 |
+
"""Parser of floating point values.
|
| 190 |
+
|
| 191 |
+
Parsed value may be bounded to a given upper and lower bound.
|
| 192 |
+
"""
|
| 193 |
+
number_article = 'a'
|
| 194 |
+
number_name = 'number'
|
| 195 |
+
syntactic_help = ' '.join((number_article, number_name))
|
| 196 |
+
|
| 197 |
+
def __init__(
|
| 198 |
+
self,
|
| 199 |
+
lower_bound: float | None = None,
|
| 200 |
+
upper_bound: float | None = None,
|
| 201 |
+
) -> None:
|
| 202 |
+
super().__init__()
|
| 203 |
+
self.lower_bound = lower_bound
|
| 204 |
+
self.upper_bound = upper_bound
|
| 205 |
+
sh = self.syntactic_help
|
| 206 |
+
if lower_bound is not None and upper_bound is not None:
|
| 207 |
+
sh = ('%s in the range [%s, %s]' % (sh, lower_bound, upper_bound))
|
| 208 |
+
elif lower_bound == 0:
|
| 209 |
+
sh = 'a non-negative %s' % self.number_name
|
| 210 |
+
elif upper_bound == 0:
|
| 211 |
+
sh = 'a non-positive %s' % self.number_name
|
| 212 |
+
elif upper_bound is not None:
|
| 213 |
+
sh = '%s <= %s' % (self.number_name, upper_bound)
|
| 214 |
+
elif lower_bound is not None:
|
| 215 |
+
sh = '%s >= %s' % (self.number_name, lower_bound)
|
| 216 |
+
self.syntactic_help = sh
|
| 217 |
+
|
| 218 |
+
def convert(self, argument: int | float | str) -> float:
|
| 219 |
+
"""Returns the float value of argument."""
|
| 220 |
+
if (
|
| 221 |
+
(isinstance(argument, int) and not isinstance(argument, bool))
|
| 222 |
+
or isinstance(argument, float)
|
| 223 |
+
or isinstance(argument, str)
|
| 224 |
+
):
|
| 225 |
+
return float(argument)
|
| 226 |
+
else:
|
| 227 |
+
raise TypeError(
|
| 228 |
+
'Expect argument to be a string, int, or float, found {}'.format(
|
| 229 |
+
type(argument)))
|
| 230 |
+
|
| 231 |
+
def flag_type(self) -> str:
|
| 232 |
+
"""See base class."""
|
| 233 |
+
return 'float'
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
class IntegerParser(NumericParser[int]):
|
| 237 |
+
"""Parser of an integer value.
|
| 238 |
+
|
| 239 |
+
Parsed value may be bounded to a given upper and lower bound.
|
| 240 |
+
"""
|
| 241 |
+
number_article = 'an'
|
| 242 |
+
number_name = 'integer'
|
| 243 |
+
syntactic_help = ' '.join((number_article, number_name))
|
| 244 |
+
|
| 245 |
+
def __init__(
|
| 246 |
+
self, lower_bound: int | None = None, upper_bound: int | None = None
|
| 247 |
+
) -> None:
|
| 248 |
+
super().__init__()
|
| 249 |
+
self.lower_bound = lower_bound
|
| 250 |
+
self.upper_bound = upper_bound
|
| 251 |
+
sh = self.syntactic_help
|
| 252 |
+
if lower_bound is not None and upper_bound is not None:
|
| 253 |
+
sh = ('%s in the range [%s, %s]' % (sh, lower_bound, upper_bound))
|
| 254 |
+
elif lower_bound == 1:
|
| 255 |
+
sh = 'a positive %s' % self.number_name
|
| 256 |
+
elif upper_bound == -1:
|
| 257 |
+
sh = 'a negative %s' % self.number_name
|
| 258 |
+
elif lower_bound == 0:
|
| 259 |
+
sh = 'a non-negative %s' % self.number_name
|
| 260 |
+
elif upper_bound == 0:
|
| 261 |
+
sh = 'a non-positive %s' % self.number_name
|
| 262 |
+
elif upper_bound is not None:
|
| 263 |
+
sh = '%s <= %s' % (self.number_name, upper_bound)
|
| 264 |
+
elif lower_bound is not None:
|
| 265 |
+
sh = '%s >= %s' % (self.number_name, lower_bound)
|
| 266 |
+
self.syntactic_help = sh
|
| 267 |
+
|
| 268 |
+
def convert(self, argument: int | str) -> int:
|
| 269 |
+
"""Returns the int value of argument."""
|
| 270 |
+
if isinstance(argument, int) and not isinstance(argument, bool):
|
| 271 |
+
return argument
|
| 272 |
+
elif isinstance(argument, str):
|
| 273 |
+
base = 10
|
| 274 |
+
if len(argument) > 2 and argument[0] == '0':
|
| 275 |
+
if argument[1] == 'o':
|
| 276 |
+
base = 8
|
| 277 |
+
elif argument[1] == 'x':
|
| 278 |
+
base = 16
|
| 279 |
+
return int(argument, base)
|
| 280 |
+
else:
|
| 281 |
+
raise TypeError('Expect argument to be a string or int, found {}'.format(
|
| 282 |
+
type(argument)))
|
| 283 |
+
|
| 284 |
+
def flag_type(self) -> str:
|
| 285 |
+
"""See base class."""
|
| 286 |
+
return 'int'
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
class BooleanParser(ArgumentParser[bool]):
|
| 290 |
+
"""Parser of boolean values."""
|
| 291 |
+
|
| 292 |
+
def parse(self, argument: str | int) -> bool:
|
| 293 |
+
"""See base class."""
|
| 294 |
+
if isinstance(argument, str):
|
| 295 |
+
if argument.lower() in ('true', 't', '1'):
|
| 296 |
+
return True
|
| 297 |
+
elif argument.lower() in ('false', 'f', '0'):
|
| 298 |
+
return False
|
| 299 |
+
else:
|
| 300 |
+
raise ValueError('Non-boolean argument to boolean flag', argument)
|
| 301 |
+
elif isinstance(argument, int):
|
| 302 |
+
# Only allow bool or integer 0, 1.
|
| 303 |
+
# Note that float 1.0 == True, 0.0 == False.
|
| 304 |
+
bool_value = bool(argument)
|
| 305 |
+
if argument == bool_value:
|
| 306 |
+
return bool_value
|
| 307 |
+
else:
|
| 308 |
+
raise ValueError('Non-boolean argument to boolean flag', argument)
|
| 309 |
+
|
| 310 |
+
raise TypeError('Non-boolean argument to boolean flag', argument)
|
| 311 |
+
|
| 312 |
+
def flag_type(self) -> str:
|
| 313 |
+
"""See base class."""
|
| 314 |
+
return 'bool'
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
class EnumParser(ArgumentParser[str]):
|
| 318 |
+
"""Parser of a string enum value (a string value from a given set)."""
|
| 319 |
+
|
| 320 |
+
def __init__(
|
| 321 |
+
self, enum_values: Iterable[str], case_sensitive: bool = True
|
| 322 |
+
) -> None:
|
| 323 |
+
"""Initializes EnumParser.
|
| 324 |
+
|
| 325 |
+
Args:
|
| 326 |
+
enum_values: [str], a non-empty list of string values in the enum.
|
| 327 |
+
case_sensitive: bool, whether or not the enum is to be case-sensitive.
|
| 328 |
+
|
| 329 |
+
Raises:
|
| 330 |
+
ValueError: When enum_values is empty.
|
| 331 |
+
"""
|
| 332 |
+
if not enum_values:
|
| 333 |
+
raise ValueError(f'enum_values cannot be empty, found "{enum_values}"')
|
| 334 |
+
if isinstance(enum_values, str):
|
| 335 |
+
raise ValueError(f'enum_values cannot be a str, found "{enum_values}"')
|
| 336 |
+
super().__init__()
|
| 337 |
+
self.enum_values = list(enum_values)
|
| 338 |
+
self.case_sensitive = case_sensitive
|
| 339 |
+
|
| 340 |
+
def parse(self, argument: str) -> str:
|
| 341 |
+
"""Determines validity of argument and returns the correct element of enum.
|
| 342 |
+
|
| 343 |
+
Args:
|
| 344 |
+
argument: str, the supplied flag value.
|
| 345 |
+
|
| 346 |
+
Returns:
|
| 347 |
+
The first matching element from enum_values.
|
| 348 |
+
|
| 349 |
+
Raises:
|
| 350 |
+
ValueError: Raised when argument didn't match anything in enum.
|
| 351 |
+
"""
|
| 352 |
+
if self.case_sensitive:
|
| 353 |
+
if argument not in self.enum_values:
|
| 354 |
+
raise ValueError('value should be one of <%s>' %
|
| 355 |
+
'|'.join(self.enum_values))
|
| 356 |
+
else:
|
| 357 |
+
return argument
|
| 358 |
+
else:
|
| 359 |
+
if argument.upper() not in [value.upper() for value in self.enum_values]:
|
| 360 |
+
raise ValueError('value should be one of <%s>' %
|
| 361 |
+
'|'.join(self.enum_values))
|
| 362 |
+
else:
|
| 363 |
+
return [value for value in self.enum_values
|
| 364 |
+
if value.upper() == argument.upper()][0]
|
| 365 |
+
|
| 366 |
+
def flag_type(self) -> str:
|
| 367 |
+
"""See base class."""
|
| 368 |
+
return 'string enum'
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
class EnumClassParser(ArgumentParser[_ET]):
|
| 372 |
+
"""Parser of an Enum class member."""
|
| 373 |
+
|
| 374 |
+
def __init__(
|
| 375 |
+
self, enum_class: type[_ET], case_sensitive: bool = True
|
| 376 |
+
) -> None:
|
| 377 |
+
"""Initializes EnumParser.
|
| 378 |
+
|
| 379 |
+
Args:
|
| 380 |
+
enum_class: class, the Enum class with all possible flag values.
|
| 381 |
+
case_sensitive: bool, whether or not the enum is to be case-sensitive. If
|
| 382 |
+
False, all member names must be unique when case is ignored.
|
| 383 |
+
|
| 384 |
+
Raises:
|
| 385 |
+
TypeError: When enum_class is not a subclass of Enum.
|
| 386 |
+
ValueError: When enum_class is empty.
|
| 387 |
+
"""
|
| 388 |
+
if not issubclass(enum_class, enum.Enum):
|
| 389 |
+
raise TypeError(f'{enum_class} is not a subclass of Enum.')
|
| 390 |
+
if not enum_class.__members__:
|
| 391 |
+
raise ValueError('enum_class cannot be empty, but "{}" is empty.'
|
| 392 |
+
.format(enum_class))
|
| 393 |
+
if not case_sensitive:
|
| 394 |
+
members = collections.Counter(
|
| 395 |
+
name.lower() for name in enum_class.__members__)
|
| 396 |
+
duplicate_keys = {
|
| 397 |
+
member for member, count in members.items() if count > 1
|
| 398 |
+
}
|
| 399 |
+
if duplicate_keys:
|
| 400 |
+
raise ValueError(
|
| 401 |
+
'Duplicate enum values for {} using case_sensitive=False'.format(
|
| 402 |
+
duplicate_keys))
|
| 403 |
+
|
| 404 |
+
super().__init__()
|
| 405 |
+
self.enum_class = enum_class
|
| 406 |
+
self._case_sensitive = case_sensitive
|
| 407 |
+
if case_sensitive:
|
| 408 |
+
self._member_names = tuple(enum_class.__members__)
|
| 409 |
+
else:
|
| 410 |
+
self._member_names = tuple(
|
| 411 |
+
name.lower() for name in enum_class.__members__)
|
| 412 |
+
|
| 413 |
+
@property
|
| 414 |
+
def member_names(self) -> Sequence[str]:
|
| 415 |
+
"""The accepted enum names, in lowercase if not case sensitive."""
|
| 416 |
+
return self._member_names
|
| 417 |
+
|
| 418 |
+
def parse(self, argument: _ET | str) -> _ET:
|
| 419 |
+
"""Determines validity of argument and returns the correct element of enum.
|
| 420 |
+
|
| 421 |
+
Args:
|
| 422 |
+
argument: str or Enum class member, the supplied flag value.
|
| 423 |
+
|
| 424 |
+
Returns:
|
| 425 |
+
The first matching Enum class member in Enum class.
|
| 426 |
+
|
| 427 |
+
Raises:
|
| 428 |
+
ValueError: Raised when argument didn't match anything in enum.
|
| 429 |
+
"""
|
| 430 |
+
if isinstance(argument, self.enum_class):
|
| 431 |
+
return argument # pytype: disable=bad-return-type
|
| 432 |
+
elif not isinstance(argument, str):
|
| 433 |
+
raise ValueError(
|
| 434 |
+
'{} is not an enum member or a name of a member in {}'.format(
|
| 435 |
+
argument, self.enum_class))
|
| 436 |
+
key = EnumParser(
|
| 437 |
+
self._member_names, case_sensitive=self._case_sensitive).parse(argument)
|
| 438 |
+
if self._case_sensitive:
|
| 439 |
+
return self.enum_class[key]
|
| 440 |
+
else:
|
| 441 |
+
# If EnumParser.parse() return a value, we're guaranteed to find it
|
| 442 |
+
# as a member of the class
|
| 443 |
+
return next(value for name, value in self.enum_class.__members__.items()
|
| 444 |
+
if name.lower() == key.lower())
|
| 445 |
+
|
| 446 |
+
def flag_type(self) -> str:
|
| 447 |
+
"""See base class."""
|
| 448 |
+
return 'enum class'
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
class ListSerializer(Generic[_T], ArgumentSerializer[list[_T]]):
|
| 452 |
+
|
| 453 |
+
def __init__(self, list_sep: str) -> None:
|
| 454 |
+
self.list_sep = list_sep
|
| 455 |
+
|
| 456 |
+
def serialize(self, value: list[_T]) -> str:
|
| 457 |
+
"""See base class."""
|
| 458 |
+
return self.list_sep.join([str(x) for x in value])
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
class EnumClassListSerializer(ListSerializer[_ET]):
|
| 462 |
+
"""A serializer for :class:`MultiEnumClass` flags.
|
| 463 |
+
|
| 464 |
+
This serializer simply joins the output of `EnumClassSerializer` using a
|
| 465 |
+
provided separator.
|
| 466 |
+
"""
|
| 467 |
+
|
| 468 |
+
_element_serializer: 'EnumClassSerializer'
|
| 469 |
+
|
| 470 |
+
def __init__(self, list_sep: str, **kwargs) -> None:
|
| 471 |
+
"""Initializes EnumClassListSerializer.
|
| 472 |
+
|
| 473 |
+
Args:
|
| 474 |
+
list_sep: String to be used as a separator when serializing
|
| 475 |
+
**kwargs: Keyword arguments to the `EnumClassSerializer` used to serialize
|
| 476 |
+
individual values.
|
| 477 |
+
"""
|
| 478 |
+
super().__init__(list_sep)
|
| 479 |
+
self._element_serializer = EnumClassSerializer(**kwargs)
|
| 480 |
+
|
| 481 |
+
def serialize(self, value: _ET | list[_ET]) -> str:
|
| 482 |
+
"""See base class."""
|
| 483 |
+
if isinstance(value, list):
|
| 484 |
+
return self.list_sep.join(
|
| 485 |
+
self._element_serializer.serialize(x) for x in value)
|
| 486 |
+
else:
|
| 487 |
+
return self._element_serializer.serialize(value)
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
class CsvListSerializer(ListSerializer[str]):
|
| 491 |
+
|
| 492 |
+
def serialize(self, value: list[str]) -> str:
|
| 493 |
+
"""Serializes a list as a CSV string or unicode."""
|
| 494 |
+
output = io.StringIO()
|
| 495 |
+
writer = csv.writer(output, delimiter=self.list_sep)
|
| 496 |
+
writer.writerow([str(x) for x in value])
|
| 497 |
+
serialized_value = output.getvalue().strip()
|
| 498 |
+
|
| 499 |
+
# We need the returned value to be pure ascii or Unicodes so that
|
| 500 |
+
# when the xml help is generated they are usefully encodable.
|
| 501 |
+
return str(serialized_value)
|
| 502 |
+
|
| 503 |
+
|
| 504 |
+
class EnumClassSerializer(ArgumentSerializer[_ET]):
|
| 505 |
+
"""Class for generating string representations of an enum class flag value."""
|
| 506 |
+
|
| 507 |
+
def __init__(self, lowercase: bool) -> None:
|
| 508 |
+
"""Initializes EnumClassSerializer.
|
| 509 |
+
|
| 510 |
+
Args:
|
| 511 |
+
lowercase: If True, enum member names are lowercased during serialization.
|
| 512 |
+
"""
|
| 513 |
+
self._lowercase = lowercase
|
| 514 |
+
|
| 515 |
+
def serialize(self, value: _ET) -> str:
|
| 516 |
+
"""Returns a serialized string of the Enum class value."""
|
| 517 |
+
as_string = str(value.name)
|
| 518 |
+
return as_string.lower() if self._lowercase else as_string
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
class BaseListParser(ArgumentParser):
|
| 522 |
+
"""Base class for a parser of lists of strings.
|
| 523 |
+
|
| 524 |
+
To extend, inherit from this class; from the subclass ``__init__``, call::
|
| 525 |
+
|
| 526 |
+
super().__init__(token, name)
|
| 527 |
+
|
| 528 |
+
where token is a character used to tokenize, and name is a description
|
| 529 |
+
of the separator.
|
| 530 |
+
"""
|
| 531 |
+
|
| 532 |
+
def __init__(self, token: str | None = None, name: str | None = None) -> None:
|
| 533 |
+
assert name
|
| 534 |
+
super().__init__()
|
| 535 |
+
self._token = token
|
| 536 |
+
self._name = name
|
| 537 |
+
self.syntactic_help = 'a %s separated list' % self._name
|
| 538 |
+
|
| 539 |
+
def parse(self, argument: str) -> list[str]:
|
| 540 |
+
"""See base class."""
|
| 541 |
+
if isinstance(argument, list):
|
| 542 |
+
return argument
|
| 543 |
+
elif not argument:
|
| 544 |
+
return []
|
| 545 |
+
else:
|
| 546 |
+
return [s.strip() for s in argument.split(self._token)]
|
| 547 |
+
|
| 548 |
+
def flag_type(self) -> str:
|
| 549 |
+
"""See base class."""
|
| 550 |
+
return '%s separated list of strings' % self._name
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
class ListParser(BaseListParser):
|
| 554 |
+
"""Parser for a comma-separated list of strings."""
|
| 555 |
+
|
| 556 |
+
def __init__(self) -> None:
|
| 557 |
+
super().__init__(',', 'comma')
|
| 558 |
+
|
| 559 |
+
def parse(self, argument: str | list[str]) -> list[str]:
|
| 560 |
+
"""Parses argument as comma-separated list of strings."""
|
| 561 |
+
if isinstance(argument, list):
|
| 562 |
+
return argument
|
| 563 |
+
elif not argument:
|
| 564 |
+
return []
|
| 565 |
+
else:
|
| 566 |
+
try:
|
| 567 |
+
return [s.strip() for s in list(csv.reader([argument], strict=True))[0]]
|
| 568 |
+
except csv.Error as e:
|
| 569 |
+
# Provide a helpful report for case like
|
| 570 |
+
# --listflag="$(printf 'hello,\nworld')"
|
| 571 |
+
# IOW, list flag values containing naked newlines. This error
|
| 572 |
+
# was previously "reported" by allowing csv.Error to
|
| 573 |
+
# propagate.
|
| 574 |
+
raise ValueError('Unable to parse the value %r as a %s: %s'
|
| 575 |
+
% (argument, self.flag_type(), e))
|
| 576 |
+
|
| 577 |
+
def _custom_xml_dom_elements(
|
| 578 |
+
self, doc: minidom.Document
|
| 579 |
+
) -> list[minidom.Element]:
|
| 580 |
+
elements = super()._custom_xml_dom_elements(doc)
|
| 581 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 582 |
+
doc, 'list_separator', repr(',')))
|
| 583 |
+
return elements
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
class WhitespaceSeparatedListParser(BaseListParser):
|
| 587 |
+
"""Parser for a whitespace-separated list of strings."""
|
| 588 |
+
|
| 589 |
+
def __init__(self, comma_compat: bool = False) -> None:
|
| 590 |
+
"""Initializer.
|
| 591 |
+
|
| 592 |
+
Args:
|
| 593 |
+
comma_compat: bool, whether to support comma as an additional separator.
|
| 594 |
+
If False then only whitespace is supported. This is intended only for
|
| 595 |
+
backwards compatibility with flags that used to be comma-separated.
|
| 596 |
+
"""
|
| 597 |
+
self._comma_compat = comma_compat
|
| 598 |
+
name = 'whitespace or comma' if self._comma_compat else 'whitespace'
|
| 599 |
+
super().__init__(None, name)
|
| 600 |
+
|
| 601 |
+
def parse(self, argument: str | list[str]) -> list[str]:
|
| 602 |
+
"""Parses argument as whitespace-separated list of strings.
|
| 603 |
+
|
| 604 |
+
It also parses argument as comma-separated list of strings if requested.
|
| 605 |
+
|
| 606 |
+
Args:
|
| 607 |
+
argument: string argument passed in the commandline.
|
| 608 |
+
|
| 609 |
+
Returns:
|
| 610 |
+
[str], the parsed flag value.
|
| 611 |
+
"""
|
| 612 |
+
if isinstance(argument, list):
|
| 613 |
+
return argument
|
| 614 |
+
elif not argument:
|
| 615 |
+
return []
|
| 616 |
+
else:
|
| 617 |
+
if self._comma_compat:
|
| 618 |
+
argument = argument.replace(',', ' ')
|
| 619 |
+
return argument.split()
|
| 620 |
+
|
| 621 |
+
def _custom_xml_dom_elements(
|
| 622 |
+
self, doc: minidom.Document
|
| 623 |
+
) -> list[minidom.Element]:
|
| 624 |
+
elements = super()._custom_xml_dom_elements(doc)
|
| 625 |
+
separators = list(string.whitespace)
|
| 626 |
+
if self._comma_compat:
|
| 627 |
+
separators.append(',')
|
| 628 |
+
separators.sort()
|
| 629 |
+
for sep_char in separators:
|
| 630 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 631 |
+
doc, 'list_separator', repr(sep_char)))
|
| 632 |
+
return elements
|
.venv/lib/python3.13/site-packages/absl/flags/_defines.py
ADDED
|
@@ -0,0 +1,1702 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""This modules contains flags DEFINE functions.
|
| 15 |
+
|
| 16 |
+
Do NOT import this module directly. Import the flags package and use the
|
| 17 |
+
aliases defined at the package level instead.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from collections.abc import Iterable
|
| 21 |
+
import enum
|
| 22 |
+
import sys
|
| 23 |
+
import types
|
| 24 |
+
from typing import Any, Literal, TypeVar, overload
|
| 25 |
+
|
| 26 |
+
from absl.flags import _argument_parser
|
| 27 |
+
from absl.flags import _exceptions
|
| 28 |
+
from absl.flags import _flag
|
| 29 |
+
from absl.flags import _flagvalues
|
| 30 |
+
from absl.flags import _helpers
|
| 31 |
+
from absl.flags import _validators
|
| 32 |
+
|
| 33 |
+
_helpers.disclaim_module_ids.add(id(sys.modules[__name__]))
|
| 34 |
+
|
| 35 |
+
_T = TypeVar('_T')
|
| 36 |
+
_ET = TypeVar('_ET', bound=enum.Enum)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _register_bounds_validator_if_needed(parser, name, flag_values):
|
| 40 |
+
"""Enforces lower and upper bounds for numeric flags.
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
parser: NumericParser (either FloatParser or IntegerParser), provides lower
|
| 44 |
+
and upper bounds, and help text to display.
|
| 45 |
+
name: str, name of the flag
|
| 46 |
+
flag_values: FlagValues.
|
| 47 |
+
"""
|
| 48 |
+
if parser.lower_bound is not None or parser.upper_bound is not None:
|
| 49 |
+
|
| 50 |
+
def checker(value):
|
| 51 |
+
if value is not None and parser.is_outside_bounds(value):
|
| 52 |
+
message = '%s is not %s' % (value, parser.syntactic_help)
|
| 53 |
+
raise _exceptions.ValidationError(message)
|
| 54 |
+
return True
|
| 55 |
+
|
| 56 |
+
_validators.register_validator(name, checker, flag_values=flag_values)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@overload
|
| 60 |
+
def DEFINE( # pylint: disable=invalid-name
|
| 61 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 62 |
+
name: str,
|
| 63 |
+
default: Any,
|
| 64 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 65 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 66 |
+
serializer: _argument_parser.ArgumentSerializer[_T] | None = ...,
|
| 67 |
+
module_name: str | None = ...,
|
| 68 |
+
required: Literal[True] = ...,
|
| 69 |
+
**args: Any
|
| 70 |
+
) -> _flagvalues.FlagHolder[_T]:
|
| 71 |
+
...
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
@overload
|
| 75 |
+
def DEFINE( # pylint: disable=invalid-name
|
| 76 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 77 |
+
name: str,
|
| 78 |
+
default: Any | None,
|
| 79 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 80 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 81 |
+
serializer: _argument_parser.ArgumentSerializer[_T] | None = ...,
|
| 82 |
+
module_name: str | None = ...,
|
| 83 |
+
required: bool = ...,
|
| 84 |
+
**args: Any
|
| 85 |
+
) -> _flagvalues.FlagHolder[_T | None]:
|
| 86 |
+
...
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def DEFINE( # pylint: disable=invalid-name
|
| 90 |
+
parser,
|
| 91 |
+
name,
|
| 92 |
+
default,
|
| 93 |
+
help, # pylint: disable=redefined-builtin
|
| 94 |
+
flag_values=_flagvalues.FLAGS,
|
| 95 |
+
serializer=None,
|
| 96 |
+
module_name=None,
|
| 97 |
+
required=False,
|
| 98 |
+
**args):
|
| 99 |
+
"""Registers a generic Flag object.
|
| 100 |
+
|
| 101 |
+
NOTE: in the docstrings of all DEFINE* functions, "registers" is short
|
| 102 |
+
for "creates a new flag and registers it".
|
| 103 |
+
|
| 104 |
+
Auxiliary function: clients should use the specialized ``DEFINE_<type>``
|
| 105 |
+
function instead.
|
| 106 |
+
|
| 107 |
+
Args:
|
| 108 |
+
parser: :class:`ArgumentParser`, used to parse the flag arguments.
|
| 109 |
+
name: str, the flag name.
|
| 110 |
+
default: The default value of the flag.
|
| 111 |
+
help: str, the help message.
|
| 112 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 113 |
+
flag will be registered. This should almost never need to be overridden.
|
| 114 |
+
serializer: :class:`ArgumentSerializer`, the flag serializer instance.
|
| 115 |
+
module_name: str, the name of the Python module declaring this flag. If not
|
| 116 |
+
provided, it will be computed using the stack trace of this call.
|
| 117 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 118 |
+
argument.
|
| 119 |
+
**args: dict, the extra keyword args that are passed to ``Flag.__init__``.
|
| 120 |
+
|
| 121 |
+
Returns:
|
| 122 |
+
a handle to defined flag.
|
| 123 |
+
"""
|
| 124 |
+
return DEFINE_flag(
|
| 125 |
+
_flag.Flag(parser, serializer, name, default, help, **args),
|
| 126 |
+
flag_values,
|
| 127 |
+
module_name,
|
| 128 |
+
required=True if required else False,
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
@overload
|
| 133 |
+
def DEFINE_flag( # pylint: disable=invalid-name
|
| 134 |
+
flag: _flag.Flag[_T],
|
| 135 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 136 |
+
module_name: str | None = ...,
|
| 137 |
+
required: Literal[True] = ...,
|
| 138 |
+
) -> _flagvalues.FlagHolder[_T]:
|
| 139 |
+
...
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
@overload
|
| 143 |
+
def DEFINE_flag( # pylint: disable=invalid-name
|
| 144 |
+
flag: _flag.Flag[_T],
|
| 145 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 146 |
+
module_name: str | None = ...,
|
| 147 |
+
required: bool = ...,
|
| 148 |
+
) -> _flagvalues.FlagHolder[_T | None]:
|
| 149 |
+
...
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def DEFINE_flag( # pylint: disable=invalid-name
|
| 153 |
+
flag,
|
| 154 |
+
flag_values=_flagvalues.FLAGS,
|
| 155 |
+
module_name=None,
|
| 156 |
+
required=False):
|
| 157 |
+
"""Registers a :class:`Flag` object with a :class:`FlagValues` object.
|
| 158 |
+
|
| 159 |
+
By default, the global :const:`FLAGS` ``FlagValue`` object is used.
|
| 160 |
+
|
| 161 |
+
Typical users will use one of the more specialized DEFINE_xxx
|
| 162 |
+
functions, such as :func:`DEFINE_string` or :func:`DEFINE_integer`. But
|
| 163 |
+
developers who need to create :class:`Flag` objects themselves should use
|
| 164 |
+
this function to register their flags.
|
| 165 |
+
|
| 166 |
+
Args:
|
| 167 |
+
flag: :class:`Flag`, a flag that is key to the module.
|
| 168 |
+
flag_values: :class:`FlagValues`, the ``FlagValues`` instance with which the
|
| 169 |
+
flag will be registered. This should almost never need to be overridden.
|
| 170 |
+
module_name: str, the name of the Python module declaring this flag. If not
|
| 171 |
+
provided, it will be computed using the stack trace of this call.
|
| 172 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 173 |
+
argument.
|
| 174 |
+
|
| 175 |
+
Returns:
|
| 176 |
+
a handle to defined flag.
|
| 177 |
+
"""
|
| 178 |
+
if required and flag.default is not None:
|
| 179 |
+
raise ValueError(
|
| 180 |
+
'Required flag --%s needs to have None as default' % flag.name
|
| 181 |
+
)
|
| 182 |
+
# Copying the reference to flag_values prevents pychecker warnings.
|
| 183 |
+
fv = flag_values
|
| 184 |
+
fv[flag.name] = flag
|
| 185 |
+
# Tell flag_values who's defining the flag.
|
| 186 |
+
if module_name:
|
| 187 |
+
module = sys.modules.get(module_name)
|
| 188 |
+
else:
|
| 189 |
+
module, module_name = _helpers.get_calling_module_object_and_name()
|
| 190 |
+
flag_values.register_flag_by_module(module_name, flag)
|
| 191 |
+
flag_values.register_flag_by_module_id(id(module), flag)
|
| 192 |
+
if required:
|
| 193 |
+
_validators.mark_flag_as_required(flag.name, fv)
|
| 194 |
+
ensure_non_none_value = (flag.default is not None) or required
|
| 195 |
+
return _flagvalues.FlagHolder(
|
| 196 |
+
fv, flag, ensure_non_none_value=ensure_non_none_value)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def set_default(flag_holder: _flagvalues.FlagHolder[_T], value: _T) -> None:
|
| 200 |
+
"""Changes the default value of the provided flag object.
|
| 201 |
+
|
| 202 |
+
The flag's current value is also updated if the flag is currently using
|
| 203 |
+
the default value, i.e. not specified in the command line, and not set
|
| 204 |
+
by FLAGS.name = value.
|
| 205 |
+
|
| 206 |
+
Args:
|
| 207 |
+
flag_holder: FlagHolder, the flag to modify.
|
| 208 |
+
value: The new default value.
|
| 209 |
+
|
| 210 |
+
Raises:
|
| 211 |
+
IllegalFlagValueError: Raised when value is not valid.
|
| 212 |
+
"""
|
| 213 |
+
flag_holder._flagvalues.set_default(flag_holder.name, value) # pylint: disable=protected-access
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def override_value(flag_holder: _flagvalues.FlagHolder[_T], value: _T) -> None:
|
| 217 |
+
"""Overrides the value of the provided flag.
|
| 218 |
+
|
| 219 |
+
This value takes precedent over the default value and, when called after flag
|
| 220 |
+
parsing, any value provided at the command line.
|
| 221 |
+
|
| 222 |
+
Args:
|
| 223 |
+
flag_holder: FlagHolder, the flag to modify.
|
| 224 |
+
value: The new value.
|
| 225 |
+
|
| 226 |
+
Raises:
|
| 227 |
+
IllegalFlagValueError: The value did not pass the flag parser or validators.
|
| 228 |
+
"""
|
| 229 |
+
fv = flag_holder._flagvalues # pylint: disable=protected-access
|
| 230 |
+
# Ensure the new value satisfies the flag's parser while avoiding side
|
| 231 |
+
# effects of calling parse().
|
| 232 |
+
parsed = fv[flag_holder.name]._parse(value) # pylint: disable=protected-access
|
| 233 |
+
if parsed != value:
|
| 234 |
+
raise _exceptions.IllegalFlagValueError(
|
| 235 |
+
'flag %s: parsed value %r not equal to original %r'
|
| 236 |
+
% (flag_holder.name, parsed, value)
|
| 237 |
+
)
|
| 238 |
+
setattr(fv, flag_holder.name, value)
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def _internal_declare_key_flags(
|
| 242 |
+
flag_names: list[str],
|
| 243 |
+
flag_values: _flagvalues.FlagValues = _flagvalues.FLAGS,
|
| 244 |
+
key_flag_values: _flagvalues.FlagValues | None = None,
|
| 245 |
+
) -> None:
|
| 246 |
+
"""Declares a flag as key for the calling module.
|
| 247 |
+
|
| 248 |
+
Internal function. User code should call declare_key_flag or
|
| 249 |
+
adopt_module_key_flags instead.
|
| 250 |
+
|
| 251 |
+
Args:
|
| 252 |
+
flag_names: [str], a list of names of already-registered Flag objects.
|
| 253 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 254 |
+
flags listed in flag_names have registered (the value of the flag_values
|
| 255 |
+
argument from the ``DEFINE_*`` calls that defined those flags). This
|
| 256 |
+
should almost never need to be overridden.
|
| 257 |
+
key_flag_values: :class:`FlagValues`, the FlagValues instance that (among
|
| 258 |
+
possibly many other things) keeps track of the key flags for each module.
|
| 259 |
+
Default ``None`` means "same as flag_values". This should almost never
|
| 260 |
+
need to be overridden.
|
| 261 |
+
|
| 262 |
+
Raises:
|
| 263 |
+
UnrecognizedFlagError: Raised when the flag is not defined.
|
| 264 |
+
"""
|
| 265 |
+
key_flag_values = key_flag_values or flag_values
|
| 266 |
+
|
| 267 |
+
module = _helpers.get_calling_module()
|
| 268 |
+
|
| 269 |
+
for flag_name in flag_names:
|
| 270 |
+
key_flag_values.register_key_flag_for_module(module, flag_values[flag_name])
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def declare_key_flag(
|
| 274 |
+
flag_name: str | _flagvalues.FlagHolder,
|
| 275 |
+
flag_values: _flagvalues.FlagValues = _flagvalues.FLAGS,
|
| 276 |
+
) -> None:
|
| 277 |
+
"""Declares one flag as key to the current module.
|
| 278 |
+
|
| 279 |
+
Key flags are flags that are deemed really important for a module.
|
| 280 |
+
They are important when listing help messages; e.g., if the
|
| 281 |
+
--helpshort command-line flag is used, then only the key flags of the
|
| 282 |
+
main module are listed (instead of all flags, as in the case of
|
| 283 |
+
--helpfull).
|
| 284 |
+
|
| 285 |
+
Sample usage::
|
| 286 |
+
|
| 287 |
+
flags.declare_key_flag('flag_1')
|
| 288 |
+
|
| 289 |
+
Args:
|
| 290 |
+
flag_name: str | :class:`FlagHolder`, the name or holder of an already
|
| 291 |
+
declared flag. (Redeclaring flags as key, including flags implicitly key
|
| 292 |
+
because they were declared in this module, is a no-op.)
|
| 293 |
+
Positional-only parameter.
|
| 294 |
+
flag_values: :class:`FlagValues`, the FlagValues instance in which the
|
| 295 |
+
flag will be declared as a key flag. This should almost never need to be
|
| 296 |
+
overridden.
|
| 297 |
+
|
| 298 |
+
Raises:
|
| 299 |
+
ValueError: Raised if flag_name not defined as a Python flag.
|
| 300 |
+
"""
|
| 301 |
+
flag_name, flag_values = _flagvalues.resolve_flag_ref(flag_name, flag_values)
|
| 302 |
+
if flag_name in _helpers.SPECIAL_FLAGS:
|
| 303 |
+
# Take care of the special flags, e.g., --flagfile, --undefok.
|
| 304 |
+
# These flags are defined in SPECIAL_FLAGS, and are treated
|
| 305 |
+
# specially during flag parsing, taking precedence over the
|
| 306 |
+
# user-defined flags.
|
| 307 |
+
_internal_declare_key_flags([flag_name],
|
| 308 |
+
flag_values=_helpers.SPECIAL_FLAGS,
|
| 309 |
+
key_flag_values=flag_values)
|
| 310 |
+
return
|
| 311 |
+
try:
|
| 312 |
+
_internal_declare_key_flags([flag_name], flag_values=flag_values)
|
| 313 |
+
except KeyError:
|
| 314 |
+
raise ValueError('Flag --%s is undefined. To set a flag as a key flag '
|
| 315 |
+
'first define it in Python.' % flag_name)
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def adopt_module_key_flags(
|
| 319 |
+
module: Any, flag_values: _flagvalues.FlagValues = _flagvalues.FLAGS
|
| 320 |
+
) -> None:
|
| 321 |
+
"""Declares that all flags key to a module are key to the current module.
|
| 322 |
+
|
| 323 |
+
Args:
|
| 324 |
+
module: module, the module object from which all key flags will be declared
|
| 325 |
+
as key flags to the current module.
|
| 326 |
+
flag_values: :class:`FlagValues`, the FlagValues instance in which the
|
| 327 |
+
flags will be declared as key flags. This should almost never need to be
|
| 328 |
+
overridden.
|
| 329 |
+
|
| 330 |
+
Raises:
|
| 331 |
+
Error: Raised when given an argument that is a module name (a string),
|
| 332 |
+
instead of a module object.
|
| 333 |
+
"""
|
| 334 |
+
if not isinstance(module, types.ModuleType):
|
| 335 |
+
raise _exceptions.Error('Expected a module object, not %r.' % (module,))
|
| 336 |
+
_internal_declare_key_flags(
|
| 337 |
+
[f.name for f in flag_values.get_key_flags_for_module(module.__name__)],
|
| 338 |
+
flag_values=flag_values)
|
| 339 |
+
# If module is this flag module, take _helpers.SPECIAL_FLAGS into account.
|
| 340 |
+
if module == _helpers.FLAGS_MODULE:
|
| 341 |
+
_internal_declare_key_flags(
|
| 342 |
+
# As we associate flags with get_calling_module_object_and_name(), the
|
| 343 |
+
# special flags defined in this module are incorrectly registered with
|
| 344 |
+
# a different module. So, we can't use get_key_flags_for_module.
|
| 345 |
+
# Instead, we take all flags from _helpers.SPECIAL_FLAGS (a private
|
| 346 |
+
# FlagValues, where no other module should register flags).
|
| 347 |
+
[_helpers.SPECIAL_FLAGS[name].name for name in _helpers.SPECIAL_FLAGS],
|
| 348 |
+
flag_values=_helpers.SPECIAL_FLAGS,
|
| 349 |
+
key_flag_values=flag_values)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def disclaim_key_flags() -> None:
|
| 353 |
+
"""Declares that the current module will not define any more key flags.
|
| 354 |
+
|
| 355 |
+
Normally, the module that calls the DEFINE_xxx functions claims the
|
| 356 |
+
flag to be its key flag. This is undesirable for modules that
|
| 357 |
+
define additional DEFINE_yyy functions with its own flag parsers and
|
| 358 |
+
serializers, since that module will accidentally claim flags defined
|
| 359 |
+
by DEFINE_yyy as its key flags. After calling this function, the
|
| 360 |
+
module disclaims flag definitions thereafter, so the key flags will
|
| 361 |
+
be correctly attributed to the caller of DEFINE_yyy.
|
| 362 |
+
|
| 363 |
+
After calling this function, the module will not be able to define
|
| 364 |
+
any more flags. This function will affect all FlagValues objects.
|
| 365 |
+
"""
|
| 366 |
+
globals_for_caller = sys._getframe(1).f_globals # pylint: disable=protected-access
|
| 367 |
+
module = _helpers.get_module_object_and_name(globals_for_caller)
|
| 368 |
+
if module is not None:
|
| 369 |
+
_helpers.disclaim_module_ids.add(id(module.module))
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
@overload
|
| 373 |
+
def DEFINE_string( # pylint: disable=invalid-name
|
| 374 |
+
name: str,
|
| 375 |
+
default: str | None,
|
| 376 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 377 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 378 |
+
*,
|
| 379 |
+
required: Literal[True],
|
| 380 |
+
**args: Any
|
| 381 |
+
) -> _flagvalues.FlagHolder[str]:
|
| 382 |
+
...
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
@overload
|
| 386 |
+
def DEFINE_string( # pylint: disable=invalid-name
|
| 387 |
+
name: str,
|
| 388 |
+
default: None,
|
| 389 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 390 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 391 |
+
required: bool = ...,
|
| 392 |
+
**args: Any
|
| 393 |
+
) -> _flagvalues.FlagHolder[str | None]:
|
| 394 |
+
...
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
@overload
|
| 398 |
+
def DEFINE_string( # pylint: disable=invalid-name
|
| 399 |
+
name: str,
|
| 400 |
+
default: str,
|
| 401 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 402 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 403 |
+
required: bool = ...,
|
| 404 |
+
**args: Any
|
| 405 |
+
) -> _flagvalues.FlagHolder[str]:
|
| 406 |
+
...
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
def DEFINE_string( # pylint: disable=invalid-name
|
| 410 |
+
name,
|
| 411 |
+
default,
|
| 412 |
+
help, # pylint: disable=redefined-builtin
|
| 413 |
+
flag_values=_flagvalues.FLAGS,
|
| 414 |
+
required=False,
|
| 415 |
+
**args
|
| 416 |
+
):
|
| 417 |
+
"""Registers a flag whose value can be any string."""
|
| 418 |
+
parser = _argument_parser.ArgumentParser[str]()
|
| 419 |
+
serializer = _argument_parser.ArgumentSerializer[str]()
|
| 420 |
+
return DEFINE(
|
| 421 |
+
parser,
|
| 422 |
+
name,
|
| 423 |
+
default,
|
| 424 |
+
help,
|
| 425 |
+
flag_values,
|
| 426 |
+
serializer,
|
| 427 |
+
required=True if required else False,
|
| 428 |
+
**args,
|
| 429 |
+
)
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
@overload
|
| 433 |
+
def DEFINE_boolean( # pylint: disable=invalid-name
|
| 434 |
+
name: str,
|
| 435 |
+
default: None | str | bool | int,
|
| 436 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 437 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 438 |
+
module_name: str | None = ...,
|
| 439 |
+
*,
|
| 440 |
+
required: Literal[True],
|
| 441 |
+
**args: Any
|
| 442 |
+
) -> _flagvalues.FlagHolder[bool]:
|
| 443 |
+
...
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
@overload
|
| 447 |
+
def DEFINE_boolean( # pylint: disable=invalid-name
|
| 448 |
+
name: str,
|
| 449 |
+
default: None,
|
| 450 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 451 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 452 |
+
module_name: str | None = ...,
|
| 453 |
+
required: bool = ...,
|
| 454 |
+
**args: Any
|
| 455 |
+
) -> _flagvalues.FlagHolder[bool | None]:
|
| 456 |
+
...
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
@overload
|
| 460 |
+
def DEFINE_boolean( # pylint: disable=invalid-name
|
| 461 |
+
name: str,
|
| 462 |
+
default: str | bool | int,
|
| 463 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 464 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 465 |
+
module_name: str | None = ...,
|
| 466 |
+
required: bool = ...,
|
| 467 |
+
**args: Any
|
| 468 |
+
) -> _flagvalues.FlagHolder[bool]:
|
| 469 |
+
...
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
def DEFINE_boolean( # pylint: disable=invalid-name
|
| 473 |
+
name,
|
| 474 |
+
default,
|
| 475 |
+
help, # pylint: disable=redefined-builtin
|
| 476 |
+
flag_values=_flagvalues.FLAGS,
|
| 477 |
+
module_name=None,
|
| 478 |
+
required=False,
|
| 479 |
+
**args
|
| 480 |
+
):
|
| 481 |
+
"""Registers a boolean flag.
|
| 482 |
+
|
| 483 |
+
Such a boolean flag does not take an argument. If a user wants to
|
| 484 |
+
specify a false value explicitly, the long option beginning with 'no'
|
| 485 |
+
must be used: i.e. --noflag
|
| 486 |
+
|
| 487 |
+
This flag will have a value of None, True or False. None is possible
|
| 488 |
+
if default=None and the user does not specify the flag on the command
|
| 489 |
+
line.
|
| 490 |
+
|
| 491 |
+
Args:
|
| 492 |
+
name: str, the flag name.
|
| 493 |
+
default: bool|str|None, the default value of the flag.
|
| 494 |
+
help: str, the help message.
|
| 495 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 496 |
+
flag will be registered. This should almost never need to be overridden.
|
| 497 |
+
module_name: str, the name of the Python module declaring this flag. If not
|
| 498 |
+
provided, it will be computed using the stack trace of this call.
|
| 499 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 500 |
+
argument.
|
| 501 |
+
**args: dict, the extra keyword args that are passed to ``Flag.__init__``.
|
| 502 |
+
|
| 503 |
+
Returns:
|
| 504 |
+
a handle to defined flag.
|
| 505 |
+
"""
|
| 506 |
+
return DEFINE_flag( # pytype: disable=bad-return-type
|
| 507 |
+
_flag.BooleanFlag(name, default, help, **args),
|
| 508 |
+
flag_values,
|
| 509 |
+
module_name,
|
| 510 |
+
required=True if required else False,
|
| 511 |
+
)
|
| 512 |
+
|
| 513 |
+
|
| 514 |
+
@overload
|
| 515 |
+
def DEFINE_float( # pylint: disable=invalid-name
|
| 516 |
+
name: str,
|
| 517 |
+
default: None | float | str,
|
| 518 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 519 |
+
lower_bound: float | None = ...,
|
| 520 |
+
upper_bound: float | None = ...,
|
| 521 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 522 |
+
*,
|
| 523 |
+
required: Literal[True],
|
| 524 |
+
**args: Any
|
| 525 |
+
) -> _flagvalues.FlagHolder[float]:
|
| 526 |
+
...
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
@overload
|
| 530 |
+
def DEFINE_float( # pylint: disable=invalid-name
|
| 531 |
+
name: str,
|
| 532 |
+
default: None,
|
| 533 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 534 |
+
lower_bound: float | None = ...,
|
| 535 |
+
upper_bound: float | None = ...,
|
| 536 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 537 |
+
required: bool = ...,
|
| 538 |
+
**args: Any
|
| 539 |
+
) -> _flagvalues.FlagHolder[float | None]:
|
| 540 |
+
...
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
@overload
|
| 544 |
+
def DEFINE_float( # pylint: disable=invalid-name
|
| 545 |
+
name: str,
|
| 546 |
+
default: float | str,
|
| 547 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 548 |
+
lower_bound: float | None = ...,
|
| 549 |
+
upper_bound: float | None = ...,
|
| 550 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 551 |
+
required: bool = ...,
|
| 552 |
+
**args: Any
|
| 553 |
+
) -> _flagvalues.FlagHolder[float]:
|
| 554 |
+
...
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
def DEFINE_float( # pylint: disable=invalid-name
|
| 558 |
+
name,
|
| 559 |
+
default,
|
| 560 |
+
help, # pylint: disable=redefined-builtin
|
| 561 |
+
lower_bound=None,
|
| 562 |
+
upper_bound=None,
|
| 563 |
+
flag_values=_flagvalues.FLAGS,
|
| 564 |
+
required=False,
|
| 565 |
+
**args
|
| 566 |
+
):
|
| 567 |
+
"""Registers a flag whose value must be a float.
|
| 568 |
+
|
| 569 |
+
If ``lower_bound`` or ``upper_bound`` are set, then this flag must be
|
| 570 |
+
within the given range.
|
| 571 |
+
|
| 572 |
+
Args:
|
| 573 |
+
name: str, the flag name.
|
| 574 |
+
default: float|str|None, the default value of the flag.
|
| 575 |
+
help: str, the help message.
|
| 576 |
+
lower_bound: float, min value of the flag.
|
| 577 |
+
upper_bound: float, max value of the flag.
|
| 578 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 579 |
+
flag will be registered. This should almost never need to be overridden.
|
| 580 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 581 |
+
argument.
|
| 582 |
+
**args: dict, the extra keyword args that are passed to :func:`DEFINE`.
|
| 583 |
+
|
| 584 |
+
Returns:
|
| 585 |
+
a handle to defined flag.
|
| 586 |
+
"""
|
| 587 |
+
parser = _argument_parser.FloatParser(lower_bound, upper_bound)
|
| 588 |
+
serializer = _argument_parser.ArgumentSerializer()
|
| 589 |
+
result = DEFINE(
|
| 590 |
+
parser,
|
| 591 |
+
name,
|
| 592 |
+
default,
|
| 593 |
+
help, # pylint: disable=redefined-builtin
|
| 594 |
+
flag_values,
|
| 595 |
+
serializer,
|
| 596 |
+
required=True if required else False,
|
| 597 |
+
**args,
|
| 598 |
+
)
|
| 599 |
+
_register_bounds_validator_if_needed(parser, name, flag_values=flag_values)
|
| 600 |
+
return result
|
| 601 |
+
|
| 602 |
+
|
| 603 |
+
@overload
|
| 604 |
+
def DEFINE_integer( # pylint: disable=invalid-name
|
| 605 |
+
name: str,
|
| 606 |
+
default: None | int | str,
|
| 607 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 608 |
+
lower_bound: int | None = ...,
|
| 609 |
+
upper_bound: int | None = ...,
|
| 610 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 611 |
+
*,
|
| 612 |
+
required: Literal[True],
|
| 613 |
+
**args: Any
|
| 614 |
+
) -> _flagvalues.FlagHolder[int]:
|
| 615 |
+
...
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
@overload
|
| 619 |
+
def DEFINE_integer( # pylint: disable=invalid-name
|
| 620 |
+
name: str,
|
| 621 |
+
default: None,
|
| 622 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 623 |
+
lower_bound: int | None = ...,
|
| 624 |
+
upper_bound: int | None = ...,
|
| 625 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 626 |
+
required: bool = ...,
|
| 627 |
+
**args: Any
|
| 628 |
+
) -> _flagvalues.FlagHolder[int | None]:
|
| 629 |
+
...
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
@overload
|
| 633 |
+
def DEFINE_integer( # pylint: disable=invalid-name
|
| 634 |
+
name: str,
|
| 635 |
+
default: int | str,
|
| 636 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 637 |
+
lower_bound: int | None = ...,
|
| 638 |
+
upper_bound: int | None = ...,
|
| 639 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 640 |
+
required: bool = ...,
|
| 641 |
+
**args: Any
|
| 642 |
+
) -> _flagvalues.FlagHolder[int]:
|
| 643 |
+
...
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
def DEFINE_integer( # pylint: disable=invalid-name
|
| 647 |
+
name,
|
| 648 |
+
default,
|
| 649 |
+
help, # pylint: disable=redefined-builtin
|
| 650 |
+
lower_bound=None,
|
| 651 |
+
upper_bound=None,
|
| 652 |
+
flag_values=_flagvalues.FLAGS,
|
| 653 |
+
required=False,
|
| 654 |
+
**args
|
| 655 |
+
):
|
| 656 |
+
"""Registers a flag whose value must be an integer.
|
| 657 |
+
|
| 658 |
+
If ``lower_bound``, or ``upper_bound`` are set, then this flag must be
|
| 659 |
+
within the given range.
|
| 660 |
+
|
| 661 |
+
Args:
|
| 662 |
+
name: str, the flag name.
|
| 663 |
+
default: int|str|None, the default value of the flag.
|
| 664 |
+
help: str, the help message.
|
| 665 |
+
lower_bound: int, min value of the flag.
|
| 666 |
+
upper_bound: int, max value of the flag.
|
| 667 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 668 |
+
flag will be registered. This should almost never need to be overridden.
|
| 669 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 670 |
+
argument.
|
| 671 |
+
**args: dict, the extra keyword args that are passed to :func:`DEFINE`.
|
| 672 |
+
|
| 673 |
+
Returns:
|
| 674 |
+
a handle to defined flag.
|
| 675 |
+
"""
|
| 676 |
+
parser = _argument_parser.IntegerParser(lower_bound, upper_bound)
|
| 677 |
+
serializer = _argument_parser.ArgumentSerializer()
|
| 678 |
+
result = DEFINE(
|
| 679 |
+
parser,
|
| 680 |
+
name,
|
| 681 |
+
default,
|
| 682 |
+
help, # pylint: disable=redefined-builtin
|
| 683 |
+
flag_values,
|
| 684 |
+
serializer,
|
| 685 |
+
required=True if required else False,
|
| 686 |
+
**args,
|
| 687 |
+
)
|
| 688 |
+
_register_bounds_validator_if_needed(parser, name, flag_values=flag_values)
|
| 689 |
+
return result
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
@overload
|
| 693 |
+
def DEFINE_enum( # pylint: disable=invalid-name
|
| 694 |
+
name: str,
|
| 695 |
+
default: str | None,
|
| 696 |
+
enum_values: Iterable[str],
|
| 697 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 698 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 699 |
+
module_name: str | None = ...,
|
| 700 |
+
*,
|
| 701 |
+
required: Literal[True],
|
| 702 |
+
**args: Any
|
| 703 |
+
) -> _flagvalues.FlagHolder[str]:
|
| 704 |
+
...
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
@overload
|
| 708 |
+
def DEFINE_enum( # pylint: disable=invalid-name
|
| 709 |
+
name: str,
|
| 710 |
+
default: None,
|
| 711 |
+
enum_values: Iterable[str],
|
| 712 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 713 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 714 |
+
module_name: str | None = ...,
|
| 715 |
+
required: bool = ...,
|
| 716 |
+
**args: Any
|
| 717 |
+
) -> _flagvalues.FlagHolder[str | None]:
|
| 718 |
+
...
|
| 719 |
+
|
| 720 |
+
|
| 721 |
+
@overload
|
| 722 |
+
def DEFINE_enum( # pylint: disable=invalid-name
|
| 723 |
+
name: str,
|
| 724 |
+
default: str,
|
| 725 |
+
enum_values: Iterable[str],
|
| 726 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 727 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 728 |
+
module_name: str | None = ...,
|
| 729 |
+
required: bool = ...,
|
| 730 |
+
**args: Any
|
| 731 |
+
) -> _flagvalues.FlagHolder[str]:
|
| 732 |
+
...
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
def DEFINE_enum( # pylint: disable=invalid-name
|
| 736 |
+
name,
|
| 737 |
+
default,
|
| 738 |
+
enum_values,
|
| 739 |
+
help, # pylint: disable=redefined-builtin
|
| 740 |
+
flag_values=_flagvalues.FLAGS,
|
| 741 |
+
module_name=None,
|
| 742 |
+
required=False,
|
| 743 |
+
**args
|
| 744 |
+
):
|
| 745 |
+
"""Registers a flag whose value can be any string from enum_values.
|
| 746 |
+
|
| 747 |
+
Instead of a string enum, prefer `DEFINE_enum_class`, which allows
|
| 748 |
+
defining enums from an `enum.Enum` class.
|
| 749 |
+
|
| 750 |
+
Args:
|
| 751 |
+
name: str, the flag name.
|
| 752 |
+
default: str|None, the default value of the flag.
|
| 753 |
+
enum_values: [str], a non-empty list of strings with the possible values for
|
| 754 |
+
the flag.
|
| 755 |
+
help: str, the help message.
|
| 756 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 757 |
+
flag will be registered. This should almost never need to be overridden.
|
| 758 |
+
module_name: str, the name of the Python module declaring this flag. If not
|
| 759 |
+
provided, it will be computed using the stack trace of this call.
|
| 760 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 761 |
+
argument.
|
| 762 |
+
**args: dict, the extra keyword args that are passed to ``Flag.__init__``.
|
| 763 |
+
|
| 764 |
+
Returns:
|
| 765 |
+
a handle to defined flag.
|
| 766 |
+
"""
|
| 767 |
+
result = DEFINE_flag(
|
| 768 |
+
_flag.EnumFlag(name, default, help, enum_values, **args),
|
| 769 |
+
flag_values,
|
| 770 |
+
module_name,
|
| 771 |
+
required=True if required else False,
|
| 772 |
+
)
|
| 773 |
+
return result
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
@overload
|
| 777 |
+
def DEFINE_enum_class( # pylint: disable=invalid-name
|
| 778 |
+
name: str,
|
| 779 |
+
default: None | _ET | str,
|
| 780 |
+
enum_class: type[_ET],
|
| 781 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 782 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 783 |
+
module_name: str | None = ...,
|
| 784 |
+
case_sensitive: bool = ...,
|
| 785 |
+
*,
|
| 786 |
+
required: Literal[True],
|
| 787 |
+
**args: Any
|
| 788 |
+
) -> _flagvalues.FlagHolder[_ET]:
|
| 789 |
+
...
|
| 790 |
+
|
| 791 |
+
|
| 792 |
+
@overload
|
| 793 |
+
def DEFINE_enum_class( # pylint: disable=invalid-name
|
| 794 |
+
name: str,
|
| 795 |
+
default: None,
|
| 796 |
+
enum_class: type[_ET],
|
| 797 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 798 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 799 |
+
module_name: str | None = ...,
|
| 800 |
+
case_sensitive: bool = ...,
|
| 801 |
+
required: bool = ...,
|
| 802 |
+
**args: Any
|
| 803 |
+
) -> _flagvalues.FlagHolder[_ET | None]:
|
| 804 |
+
...
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
@overload
|
| 808 |
+
def DEFINE_enum_class( # pylint: disable=invalid-name
|
| 809 |
+
name: str,
|
| 810 |
+
default: _ET | str,
|
| 811 |
+
enum_class: type[_ET],
|
| 812 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 813 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 814 |
+
module_name: str | None = ...,
|
| 815 |
+
case_sensitive: bool = ...,
|
| 816 |
+
required: bool = ...,
|
| 817 |
+
**args: Any
|
| 818 |
+
) -> _flagvalues.FlagHolder[_ET]:
|
| 819 |
+
...
|
| 820 |
+
|
| 821 |
+
|
| 822 |
+
def DEFINE_enum_class( # pylint: disable=invalid-name
|
| 823 |
+
name,
|
| 824 |
+
default,
|
| 825 |
+
enum_class,
|
| 826 |
+
help, # pylint: disable=redefined-builtin
|
| 827 |
+
flag_values=_flagvalues.FLAGS,
|
| 828 |
+
module_name=None,
|
| 829 |
+
case_sensitive=False,
|
| 830 |
+
required=False,
|
| 831 |
+
**args
|
| 832 |
+
):
|
| 833 |
+
"""Registers a flag whose value can be the name of enum members.
|
| 834 |
+
|
| 835 |
+
Args:
|
| 836 |
+
name: str, the flag name.
|
| 837 |
+
default: Enum|str|None, the default value of the flag.
|
| 838 |
+
enum_class: class, the Enum class with all the possible values for the flag.
|
| 839 |
+
help: str, the help message.
|
| 840 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 841 |
+
flag will be registered. This should almost never need to be overridden.
|
| 842 |
+
module_name: str, the name of the Python module declaring this flag. If not
|
| 843 |
+
provided, it will be computed using the stack trace of this call.
|
| 844 |
+
case_sensitive: bool, whether to map strings to members of the enum_class
|
| 845 |
+
without considering case.
|
| 846 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 847 |
+
argument.
|
| 848 |
+
**args: dict, the extra keyword args that are passed to ``Flag.__init__``.
|
| 849 |
+
|
| 850 |
+
Returns:
|
| 851 |
+
a handle to defined flag.
|
| 852 |
+
"""
|
| 853 |
+
# NOTE: pytype fails if this is a direct return.
|
| 854 |
+
result = DEFINE_flag(
|
| 855 |
+
_flag.EnumClassFlag(
|
| 856 |
+
name, default, help, enum_class, case_sensitive=case_sensitive, **args
|
| 857 |
+
),
|
| 858 |
+
flag_values,
|
| 859 |
+
module_name,
|
| 860 |
+
required=True if required else False,
|
| 861 |
+
)
|
| 862 |
+
return result
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
@overload
|
| 866 |
+
def DEFINE_list( # pylint: disable=invalid-name
|
| 867 |
+
name: str,
|
| 868 |
+
default: None | Iterable[str] | str,
|
| 869 |
+
help: str, # pylint: disable=redefined-builtin
|
| 870 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 871 |
+
*,
|
| 872 |
+
required: Literal[True],
|
| 873 |
+
**args: Any
|
| 874 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 875 |
+
...
|
| 876 |
+
|
| 877 |
+
|
| 878 |
+
@overload
|
| 879 |
+
def DEFINE_list( # pylint: disable=invalid-name
|
| 880 |
+
name: str,
|
| 881 |
+
default: None,
|
| 882 |
+
help: str, # pylint: disable=redefined-builtin
|
| 883 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 884 |
+
required: bool = ...,
|
| 885 |
+
**args: Any
|
| 886 |
+
) -> _flagvalues.FlagHolder[list[str] | None]:
|
| 887 |
+
...
|
| 888 |
+
|
| 889 |
+
|
| 890 |
+
@overload
|
| 891 |
+
def DEFINE_list( # pylint: disable=invalid-name
|
| 892 |
+
name: str,
|
| 893 |
+
default: Iterable[str] | str,
|
| 894 |
+
help: str, # pylint: disable=redefined-builtin
|
| 895 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 896 |
+
required: bool = ...,
|
| 897 |
+
**args: Any
|
| 898 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 899 |
+
...
|
| 900 |
+
|
| 901 |
+
|
| 902 |
+
def DEFINE_list( # pylint: disable=invalid-name
|
| 903 |
+
name,
|
| 904 |
+
default,
|
| 905 |
+
help, # pylint: disable=redefined-builtin
|
| 906 |
+
flag_values=_flagvalues.FLAGS,
|
| 907 |
+
required=False,
|
| 908 |
+
**args
|
| 909 |
+
):
|
| 910 |
+
"""Registers a flag whose value is a comma-separated list of strings.
|
| 911 |
+
|
| 912 |
+
The flag value is parsed with a CSV parser.
|
| 913 |
+
|
| 914 |
+
Args:
|
| 915 |
+
name: str, the flag name.
|
| 916 |
+
default: list|str|None, the default value of the flag.
|
| 917 |
+
help: str, the help message.
|
| 918 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 919 |
+
flag will be registered. This should almost never need to be overridden.
|
| 920 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 921 |
+
argument.
|
| 922 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 923 |
+
``Flag.__init__``.
|
| 924 |
+
|
| 925 |
+
Returns:
|
| 926 |
+
a handle to defined flag.
|
| 927 |
+
"""
|
| 928 |
+
parser = _argument_parser.ListParser()
|
| 929 |
+
serializer = _argument_parser.CsvListSerializer(',')
|
| 930 |
+
return DEFINE(
|
| 931 |
+
parser,
|
| 932 |
+
name,
|
| 933 |
+
default,
|
| 934 |
+
help,
|
| 935 |
+
flag_values,
|
| 936 |
+
serializer,
|
| 937 |
+
required=True if required else False,
|
| 938 |
+
**args,
|
| 939 |
+
)
|
| 940 |
+
|
| 941 |
+
|
| 942 |
+
@overload
|
| 943 |
+
def DEFINE_spaceseplist( # pylint: disable=invalid-name
|
| 944 |
+
name: str,
|
| 945 |
+
default: None | Iterable[str] | str,
|
| 946 |
+
help: str, # pylint: disable=redefined-builtin
|
| 947 |
+
comma_compat: bool = ...,
|
| 948 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 949 |
+
*,
|
| 950 |
+
required: Literal[True],
|
| 951 |
+
**args: Any
|
| 952 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 953 |
+
...
|
| 954 |
+
|
| 955 |
+
|
| 956 |
+
@overload
|
| 957 |
+
def DEFINE_spaceseplist( # pylint: disable=invalid-name
|
| 958 |
+
name: str,
|
| 959 |
+
default: None,
|
| 960 |
+
help: str, # pylint: disable=redefined-builtin
|
| 961 |
+
comma_compat: bool = ...,
|
| 962 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 963 |
+
required: bool = ...,
|
| 964 |
+
**args: Any
|
| 965 |
+
) -> _flagvalues.FlagHolder[list[str] | None]:
|
| 966 |
+
...
|
| 967 |
+
|
| 968 |
+
|
| 969 |
+
@overload
|
| 970 |
+
def DEFINE_spaceseplist( # pylint: disable=invalid-name
|
| 971 |
+
name: str,
|
| 972 |
+
default: Iterable[str] | str,
|
| 973 |
+
help: str, # pylint: disable=redefined-builtin
|
| 974 |
+
comma_compat: bool = ...,
|
| 975 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 976 |
+
required: bool = ...,
|
| 977 |
+
**args: Any
|
| 978 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 979 |
+
...
|
| 980 |
+
|
| 981 |
+
|
| 982 |
+
def DEFINE_spaceseplist( # pylint: disable=invalid-name
|
| 983 |
+
name,
|
| 984 |
+
default,
|
| 985 |
+
help, # pylint: disable=redefined-builtin
|
| 986 |
+
comma_compat=False,
|
| 987 |
+
flag_values=_flagvalues.FLAGS,
|
| 988 |
+
required=False,
|
| 989 |
+
**args
|
| 990 |
+
):
|
| 991 |
+
"""Registers a flag whose value is a whitespace-separated list of strings.
|
| 992 |
+
|
| 993 |
+
Any whitespace can be used as a separator.
|
| 994 |
+
|
| 995 |
+
Args:
|
| 996 |
+
name: str, the flag name.
|
| 997 |
+
default: list|str|None, the default value of the flag.
|
| 998 |
+
help: str, the help message.
|
| 999 |
+
comma_compat: bool - Whether to support comma as an additional separator. If
|
| 1000 |
+
false then only whitespace is supported. This is intended only for
|
| 1001 |
+
backwards compatibility with flags that used to be comma-separated.
|
| 1002 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1003 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1004 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 1005 |
+
argument.
|
| 1006 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 1007 |
+
``Flag.__init__``.
|
| 1008 |
+
|
| 1009 |
+
Returns:
|
| 1010 |
+
a handle to defined flag.
|
| 1011 |
+
"""
|
| 1012 |
+
parser = _argument_parser.WhitespaceSeparatedListParser(
|
| 1013 |
+
comma_compat=comma_compat)
|
| 1014 |
+
serializer = _argument_parser.ListSerializer(' ')
|
| 1015 |
+
return DEFINE(
|
| 1016 |
+
parser,
|
| 1017 |
+
name,
|
| 1018 |
+
default,
|
| 1019 |
+
help,
|
| 1020 |
+
flag_values,
|
| 1021 |
+
serializer,
|
| 1022 |
+
required=True if required else False,
|
| 1023 |
+
**args,
|
| 1024 |
+
)
|
| 1025 |
+
|
| 1026 |
+
|
| 1027 |
+
@overload
|
| 1028 |
+
def DEFINE_multi( # pylint: disable=invalid-name
|
| 1029 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 1030 |
+
serializer: _argument_parser.ArgumentSerializer[_T],
|
| 1031 |
+
name: str,
|
| 1032 |
+
default: Iterable[_T],
|
| 1033 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1034 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1035 |
+
module_name: str | None = ...,
|
| 1036 |
+
*,
|
| 1037 |
+
required: Literal[True],
|
| 1038 |
+
**args: Any
|
| 1039 |
+
) -> _flagvalues.FlagHolder[list[_T]]:
|
| 1040 |
+
...
|
| 1041 |
+
|
| 1042 |
+
|
| 1043 |
+
@overload
|
| 1044 |
+
def DEFINE_multi( # pylint: disable=invalid-name
|
| 1045 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 1046 |
+
serializer: _argument_parser.ArgumentSerializer[_T],
|
| 1047 |
+
name: str,
|
| 1048 |
+
default: None | _T,
|
| 1049 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1050 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1051 |
+
module_name: str | None = ...,
|
| 1052 |
+
*,
|
| 1053 |
+
required: Literal[True],
|
| 1054 |
+
**args: Any
|
| 1055 |
+
) -> _flagvalues.FlagHolder[list[_T]]:
|
| 1056 |
+
...
|
| 1057 |
+
|
| 1058 |
+
|
| 1059 |
+
@overload
|
| 1060 |
+
def DEFINE_multi( # pylint: disable=invalid-name
|
| 1061 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 1062 |
+
serializer: _argument_parser.ArgumentSerializer[_T],
|
| 1063 |
+
name: str,
|
| 1064 |
+
default: None,
|
| 1065 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1066 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1067 |
+
module_name: str | None = ...,
|
| 1068 |
+
required: bool = ...,
|
| 1069 |
+
**args: Any
|
| 1070 |
+
) -> _flagvalues.FlagHolder[list[_T] | None]:
|
| 1071 |
+
...
|
| 1072 |
+
|
| 1073 |
+
|
| 1074 |
+
@overload
|
| 1075 |
+
def DEFINE_multi( # pylint: disable=invalid-name
|
| 1076 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 1077 |
+
serializer: _argument_parser.ArgumentSerializer[_T],
|
| 1078 |
+
name: str,
|
| 1079 |
+
default: Iterable[_T],
|
| 1080 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1081 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1082 |
+
module_name: str | None = ...,
|
| 1083 |
+
required: bool = ...,
|
| 1084 |
+
**args: Any
|
| 1085 |
+
) -> _flagvalues.FlagHolder[list[_T]]:
|
| 1086 |
+
...
|
| 1087 |
+
|
| 1088 |
+
|
| 1089 |
+
@overload
|
| 1090 |
+
def DEFINE_multi( # pylint: disable=invalid-name
|
| 1091 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 1092 |
+
serializer: _argument_parser.ArgumentSerializer[_T],
|
| 1093 |
+
name: str,
|
| 1094 |
+
default: _T,
|
| 1095 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1096 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1097 |
+
module_name: str | None = ...,
|
| 1098 |
+
required: bool = ...,
|
| 1099 |
+
**args: Any
|
| 1100 |
+
) -> _flagvalues.FlagHolder[list[_T]]:
|
| 1101 |
+
...
|
| 1102 |
+
|
| 1103 |
+
|
| 1104 |
+
def DEFINE_multi( # pylint: disable=invalid-name
|
| 1105 |
+
parser,
|
| 1106 |
+
serializer,
|
| 1107 |
+
name,
|
| 1108 |
+
default,
|
| 1109 |
+
help, # pylint: disable=redefined-builtin
|
| 1110 |
+
flag_values=_flagvalues.FLAGS,
|
| 1111 |
+
module_name=None,
|
| 1112 |
+
required=False,
|
| 1113 |
+
**args
|
| 1114 |
+
):
|
| 1115 |
+
"""Registers a generic MultiFlag that parses its args with a given parser.
|
| 1116 |
+
|
| 1117 |
+
Auxiliary function. Normal users should NOT use it directly.
|
| 1118 |
+
|
| 1119 |
+
Developers who need to create their own 'Parser' classes for options
|
| 1120 |
+
which can appear multiple times can call this module function to
|
| 1121 |
+
register their flags.
|
| 1122 |
+
|
| 1123 |
+
Args:
|
| 1124 |
+
parser: ArgumentParser, used to parse the flag arguments.
|
| 1125 |
+
serializer: ArgumentSerializer, the flag serializer instance.
|
| 1126 |
+
name: str, the flag name.
|
| 1127 |
+
default: Union[Iterable[T], str, None], the default value of the flag. If
|
| 1128 |
+
the value is text, it will be parsed as if it was provided from the
|
| 1129 |
+
command line. If the value is a non-string iterable, it will be iterated
|
| 1130 |
+
over to create a shallow copy of the values. If it is None, it is left
|
| 1131 |
+
as-is.
|
| 1132 |
+
help: str, the help message.
|
| 1133 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1134 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1135 |
+
module_name: A string, the name of the Python module declaring this flag. If
|
| 1136 |
+
not provided, it will be computed using the stack trace of this call.
|
| 1137 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 1138 |
+
argument.
|
| 1139 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 1140 |
+
``Flag.__init__``.
|
| 1141 |
+
|
| 1142 |
+
Returns:
|
| 1143 |
+
a handle to defined flag.
|
| 1144 |
+
"""
|
| 1145 |
+
result = DEFINE_flag(
|
| 1146 |
+
_flag.MultiFlag(parser, serializer, name, default, help, **args),
|
| 1147 |
+
flag_values,
|
| 1148 |
+
module_name,
|
| 1149 |
+
required=True if required else False,
|
| 1150 |
+
)
|
| 1151 |
+
return result
|
| 1152 |
+
|
| 1153 |
+
|
| 1154 |
+
@overload
|
| 1155 |
+
def DEFINE_multi_string( # pylint: disable=invalid-name
|
| 1156 |
+
name: str,
|
| 1157 |
+
default: None | Iterable[str] | str,
|
| 1158 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1159 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1160 |
+
*,
|
| 1161 |
+
required: Literal[True],
|
| 1162 |
+
**args: Any
|
| 1163 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 1164 |
+
...
|
| 1165 |
+
|
| 1166 |
+
|
| 1167 |
+
@overload
|
| 1168 |
+
def DEFINE_multi_string( # pylint: disable=invalid-name
|
| 1169 |
+
name: str,
|
| 1170 |
+
default: None,
|
| 1171 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1172 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1173 |
+
required: bool = ...,
|
| 1174 |
+
**args: Any
|
| 1175 |
+
) -> _flagvalues.FlagHolder[list[str] | None]:
|
| 1176 |
+
...
|
| 1177 |
+
|
| 1178 |
+
|
| 1179 |
+
@overload
|
| 1180 |
+
def DEFINE_multi_string( # pylint: disable=invalid-name
|
| 1181 |
+
name: str,
|
| 1182 |
+
default: Iterable[str] | str,
|
| 1183 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1184 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1185 |
+
required: bool = ...,
|
| 1186 |
+
**args: Any
|
| 1187 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 1188 |
+
...
|
| 1189 |
+
|
| 1190 |
+
|
| 1191 |
+
def DEFINE_multi_string( # pylint: disable=invalid-name
|
| 1192 |
+
name,
|
| 1193 |
+
default,
|
| 1194 |
+
help, # pylint: disable=redefined-builtin
|
| 1195 |
+
flag_values=_flagvalues.FLAGS,
|
| 1196 |
+
required=False,
|
| 1197 |
+
**args
|
| 1198 |
+
):
|
| 1199 |
+
"""Registers a flag whose value can be a list of any strings.
|
| 1200 |
+
|
| 1201 |
+
Use the flag on the command line multiple times to place multiple
|
| 1202 |
+
string values into the list. The 'default' may be a single string
|
| 1203 |
+
(which will be converted into a single-element list) or a list of
|
| 1204 |
+
strings.
|
| 1205 |
+
|
| 1206 |
+
|
| 1207 |
+
Args:
|
| 1208 |
+
name: str, the flag name.
|
| 1209 |
+
default: Union[Iterable[str], str, None], the default value of the flag; see
|
| 1210 |
+
:func:`DEFINE_multi`.
|
| 1211 |
+
help: str, the help message.
|
| 1212 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1213 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1214 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 1215 |
+
argument.
|
| 1216 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 1217 |
+
``Flag.__init__``.
|
| 1218 |
+
|
| 1219 |
+
Returns:
|
| 1220 |
+
a handle to defined flag.
|
| 1221 |
+
"""
|
| 1222 |
+
parser = _argument_parser.ArgumentParser()
|
| 1223 |
+
serializer = _argument_parser.ArgumentSerializer()
|
| 1224 |
+
return DEFINE_multi(
|
| 1225 |
+
parser,
|
| 1226 |
+
serializer,
|
| 1227 |
+
name,
|
| 1228 |
+
default,
|
| 1229 |
+
help, # pylint: disable=redefined-builtin
|
| 1230 |
+
flag_values,
|
| 1231 |
+
required=True if required else False,
|
| 1232 |
+
**args,
|
| 1233 |
+
)
|
| 1234 |
+
|
| 1235 |
+
|
| 1236 |
+
@overload
|
| 1237 |
+
def DEFINE_multi_integer( # pylint: disable=invalid-name
|
| 1238 |
+
name: str,
|
| 1239 |
+
default: None | Iterable[int] | int | str,
|
| 1240 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1241 |
+
lower_bound: int | None = ...,
|
| 1242 |
+
upper_bound: int | None = ...,
|
| 1243 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1244 |
+
*,
|
| 1245 |
+
required: Literal[True],
|
| 1246 |
+
**args: Any
|
| 1247 |
+
) -> _flagvalues.FlagHolder[list[int]]:
|
| 1248 |
+
...
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
@overload
|
| 1252 |
+
def DEFINE_multi_integer( # pylint: disable=invalid-name
|
| 1253 |
+
name: str,
|
| 1254 |
+
default: None,
|
| 1255 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1256 |
+
lower_bound: int | None = ...,
|
| 1257 |
+
upper_bound: int | None = ...,
|
| 1258 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1259 |
+
required: bool = ...,
|
| 1260 |
+
**args: Any
|
| 1261 |
+
) -> _flagvalues.FlagHolder[list[int] | None]:
|
| 1262 |
+
...
|
| 1263 |
+
|
| 1264 |
+
|
| 1265 |
+
@overload
|
| 1266 |
+
def DEFINE_multi_integer( # pylint: disable=invalid-name
|
| 1267 |
+
name: str,
|
| 1268 |
+
default: Iterable[int] | int | str,
|
| 1269 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1270 |
+
lower_bound: int | None = ...,
|
| 1271 |
+
upper_bound: int | None = ...,
|
| 1272 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1273 |
+
required: bool = ...,
|
| 1274 |
+
**args: Any
|
| 1275 |
+
) -> _flagvalues.FlagHolder[list[int]]:
|
| 1276 |
+
...
|
| 1277 |
+
|
| 1278 |
+
|
| 1279 |
+
def DEFINE_multi_integer( # pylint: disable=invalid-name
|
| 1280 |
+
name,
|
| 1281 |
+
default,
|
| 1282 |
+
help, # pylint: disable=redefined-builtin
|
| 1283 |
+
lower_bound=None,
|
| 1284 |
+
upper_bound=None,
|
| 1285 |
+
flag_values=_flagvalues.FLAGS,
|
| 1286 |
+
required=False,
|
| 1287 |
+
**args
|
| 1288 |
+
):
|
| 1289 |
+
"""Registers a flag whose value can be a list of arbitrary integers.
|
| 1290 |
+
|
| 1291 |
+
Use the flag on the command line multiple times to place multiple
|
| 1292 |
+
integer values into the list. The 'default' may be a single integer
|
| 1293 |
+
(which will be converted into a single-element list) or a list of
|
| 1294 |
+
integers.
|
| 1295 |
+
|
| 1296 |
+
Args:
|
| 1297 |
+
name: str, the flag name.
|
| 1298 |
+
default: Union[Iterable[int], str, None], the default value of the flag; see
|
| 1299 |
+
`DEFINE_multi`.
|
| 1300 |
+
help: str, the help message.
|
| 1301 |
+
lower_bound: int, min values of the flag.
|
| 1302 |
+
upper_bound: int, max values of the flag.
|
| 1303 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1304 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1305 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 1306 |
+
argument.
|
| 1307 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 1308 |
+
``Flag.__init__``.
|
| 1309 |
+
|
| 1310 |
+
Returns:
|
| 1311 |
+
a handle to defined flag.
|
| 1312 |
+
"""
|
| 1313 |
+
parser = _argument_parser.IntegerParser(lower_bound, upper_bound)
|
| 1314 |
+
serializer = _argument_parser.ArgumentSerializer()
|
| 1315 |
+
return DEFINE_multi(
|
| 1316 |
+
parser,
|
| 1317 |
+
serializer,
|
| 1318 |
+
name,
|
| 1319 |
+
default,
|
| 1320 |
+
help, # pylint: disable=redefined-builtin
|
| 1321 |
+
flag_values,
|
| 1322 |
+
required=True if required else False,
|
| 1323 |
+
**args,
|
| 1324 |
+
)
|
| 1325 |
+
|
| 1326 |
+
|
| 1327 |
+
@overload
|
| 1328 |
+
def DEFINE_multi_float( # pylint: disable=invalid-name
|
| 1329 |
+
name: str,
|
| 1330 |
+
default: None | Iterable[float] | float | str,
|
| 1331 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1332 |
+
lower_bound: float | None = ...,
|
| 1333 |
+
upper_bound: float | None = ...,
|
| 1334 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1335 |
+
*,
|
| 1336 |
+
required: Literal[True],
|
| 1337 |
+
**args: Any
|
| 1338 |
+
) -> _flagvalues.FlagHolder[list[float]]:
|
| 1339 |
+
...
|
| 1340 |
+
|
| 1341 |
+
|
| 1342 |
+
@overload
|
| 1343 |
+
def DEFINE_multi_float( # pylint: disable=invalid-name
|
| 1344 |
+
name: str,
|
| 1345 |
+
default: None,
|
| 1346 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1347 |
+
lower_bound: float | None = ...,
|
| 1348 |
+
upper_bound: float | None = ...,
|
| 1349 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1350 |
+
required: bool = ...,
|
| 1351 |
+
**args: Any
|
| 1352 |
+
) -> _flagvalues.FlagHolder[list[float] | None]:
|
| 1353 |
+
...
|
| 1354 |
+
|
| 1355 |
+
|
| 1356 |
+
@overload
|
| 1357 |
+
def DEFINE_multi_float( # pylint: disable=invalid-name
|
| 1358 |
+
name: str,
|
| 1359 |
+
default: Iterable[float] | float | str,
|
| 1360 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1361 |
+
lower_bound: float | None = ...,
|
| 1362 |
+
upper_bound: float | None = ...,
|
| 1363 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1364 |
+
required: bool = ...,
|
| 1365 |
+
**args: Any
|
| 1366 |
+
) -> _flagvalues.FlagHolder[list[float]]:
|
| 1367 |
+
...
|
| 1368 |
+
|
| 1369 |
+
|
| 1370 |
+
def DEFINE_multi_float( # pylint: disable=invalid-name
|
| 1371 |
+
name,
|
| 1372 |
+
default,
|
| 1373 |
+
help, # pylint: disable=redefined-builtin
|
| 1374 |
+
lower_bound=None,
|
| 1375 |
+
upper_bound=None,
|
| 1376 |
+
flag_values=_flagvalues.FLAGS,
|
| 1377 |
+
required=False,
|
| 1378 |
+
**args
|
| 1379 |
+
):
|
| 1380 |
+
"""Registers a flag whose value can be a list of arbitrary floats.
|
| 1381 |
+
|
| 1382 |
+
Use the flag on the command line multiple times to place multiple
|
| 1383 |
+
float values into the list. The 'default' may be a single float
|
| 1384 |
+
(which will be converted into a single-element list) or a list of
|
| 1385 |
+
floats.
|
| 1386 |
+
|
| 1387 |
+
Args:
|
| 1388 |
+
name: str, the flag name.
|
| 1389 |
+
default: Union[Iterable[float], str, None], the default value of the flag;
|
| 1390 |
+
see `DEFINE_multi`.
|
| 1391 |
+
help: str, the help message.
|
| 1392 |
+
lower_bound: float, min values of the flag.
|
| 1393 |
+
upper_bound: float, max values of the flag.
|
| 1394 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1395 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1396 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 1397 |
+
argument.
|
| 1398 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 1399 |
+
``Flag.__init__``.
|
| 1400 |
+
|
| 1401 |
+
Returns:
|
| 1402 |
+
a handle to defined flag.
|
| 1403 |
+
"""
|
| 1404 |
+
parser = _argument_parser.FloatParser(lower_bound, upper_bound)
|
| 1405 |
+
serializer = _argument_parser.ArgumentSerializer()
|
| 1406 |
+
return DEFINE_multi(
|
| 1407 |
+
parser,
|
| 1408 |
+
serializer,
|
| 1409 |
+
name,
|
| 1410 |
+
default,
|
| 1411 |
+
help,
|
| 1412 |
+
flag_values,
|
| 1413 |
+
required=True if required else False,
|
| 1414 |
+
**args,
|
| 1415 |
+
)
|
| 1416 |
+
|
| 1417 |
+
|
| 1418 |
+
@overload
|
| 1419 |
+
def DEFINE_multi_enum( # pylint: disable=invalid-name
|
| 1420 |
+
name: str,
|
| 1421 |
+
default: None | Iterable[str] | str,
|
| 1422 |
+
enum_values: Iterable[str],
|
| 1423 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1424 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1425 |
+
*,
|
| 1426 |
+
required: Literal[True],
|
| 1427 |
+
**args: Any
|
| 1428 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 1429 |
+
...
|
| 1430 |
+
|
| 1431 |
+
|
| 1432 |
+
@overload
|
| 1433 |
+
def DEFINE_multi_enum( # pylint: disable=invalid-name
|
| 1434 |
+
name: str,
|
| 1435 |
+
default: None,
|
| 1436 |
+
enum_values: Iterable[str],
|
| 1437 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1438 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1439 |
+
required: bool = ...,
|
| 1440 |
+
**args: Any
|
| 1441 |
+
) -> _flagvalues.FlagHolder[list[str] | None]:
|
| 1442 |
+
...
|
| 1443 |
+
|
| 1444 |
+
|
| 1445 |
+
@overload
|
| 1446 |
+
def DEFINE_multi_enum( # pylint: disable=invalid-name
|
| 1447 |
+
name: str,
|
| 1448 |
+
default: Iterable[str] | str,
|
| 1449 |
+
enum_values: Iterable[str],
|
| 1450 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1451 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1452 |
+
required: bool = ...,
|
| 1453 |
+
**args: Any
|
| 1454 |
+
) -> _flagvalues.FlagHolder[list[str]]:
|
| 1455 |
+
...
|
| 1456 |
+
|
| 1457 |
+
|
| 1458 |
+
def DEFINE_multi_enum( # pylint: disable=invalid-name
|
| 1459 |
+
name,
|
| 1460 |
+
default,
|
| 1461 |
+
enum_values,
|
| 1462 |
+
help, # pylint: disable=redefined-builtin
|
| 1463 |
+
flag_values=_flagvalues.FLAGS,
|
| 1464 |
+
case_sensitive=True,
|
| 1465 |
+
required=False,
|
| 1466 |
+
**args
|
| 1467 |
+
):
|
| 1468 |
+
"""Registers a flag whose value can be a list strings from enum_values.
|
| 1469 |
+
|
| 1470 |
+
Use the flag on the command line multiple times to place multiple
|
| 1471 |
+
enum values into the list. The 'default' may be a single string
|
| 1472 |
+
(which will be converted into a single-element list) or a list of
|
| 1473 |
+
strings.
|
| 1474 |
+
|
| 1475 |
+
Args:
|
| 1476 |
+
name: str, the flag name.
|
| 1477 |
+
default: Union[Iterable[str], str, None], the default value of the flag; see
|
| 1478 |
+
`DEFINE_multi`.
|
| 1479 |
+
enum_values: [str], a non-empty list of strings with the possible values for
|
| 1480 |
+
the flag.
|
| 1481 |
+
help: str, the help message.
|
| 1482 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1483 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1484 |
+
case_sensitive: Whether or not the enum is to be case-sensitive.
|
| 1485 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 1486 |
+
argument.
|
| 1487 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 1488 |
+
``Flag.__init__``.
|
| 1489 |
+
|
| 1490 |
+
Returns:
|
| 1491 |
+
a handle to defined flag.
|
| 1492 |
+
"""
|
| 1493 |
+
parser = _argument_parser.EnumParser(enum_values, case_sensitive)
|
| 1494 |
+
serializer = _argument_parser.ArgumentSerializer()
|
| 1495 |
+
return DEFINE_multi(
|
| 1496 |
+
parser,
|
| 1497 |
+
serializer,
|
| 1498 |
+
name,
|
| 1499 |
+
default,
|
| 1500 |
+
'<%s>: %s' % ('|'.join(enum_values), help),
|
| 1501 |
+
flag_values,
|
| 1502 |
+
required=True if required else False,
|
| 1503 |
+
**args,
|
| 1504 |
+
)
|
| 1505 |
+
|
| 1506 |
+
|
| 1507 |
+
@overload
|
| 1508 |
+
def DEFINE_multi_enum_class( # pylint: disable=invalid-name
|
| 1509 |
+
name: str,
|
| 1510 |
+
# This is separate from `Union[None, _ET, Iterable[str], str]` to avoid a
|
| 1511 |
+
# Pytype issue inferring the return value to
|
| 1512 |
+
# FlagHolder[List[Union[_ET, enum.Enum]]] when an iterable of concrete enum
|
| 1513 |
+
# subclasses are used.
|
| 1514 |
+
default: Iterable[_ET],
|
| 1515 |
+
enum_class: type[_ET],
|
| 1516 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1517 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1518 |
+
module_name: str | None = ...,
|
| 1519 |
+
*,
|
| 1520 |
+
required: Literal[True],
|
| 1521 |
+
**args: Any
|
| 1522 |
+
) -> _flagvalues.FlagHolder[list[_ET]]:
|
| 1523 |
+
...
|
| 1524 |
+
|
| 1525 |
+
|
| 1526 |
+
@overload
|
| 1527 |
+
def DEFINE_multi_enum_class( # pylint: disable=invalid-name
|
| 1528 |
+
name: str,
|
| 1529 |
+
default: None | _ET | Iterable[str] | str,
|
| 1530 |
+
enum_class: type[_ET],
|
| 1531 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1532 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1533 |
+
module_name: str | None = ...,
|
| 1534 |
+
*,
|
| 1535 |
+
required: Literal[True],
|
| 1536 |
+
**args: Any
|
| 1537 |
+
) -> _flagvalues.FlagHolder[list[_ET]]:
|
| 1538 |
+
...
|
| 1539 |
+
|
| 1540 |
+
|
| 1541 |
+
@overload
|
| 1542 |
+
def DEFINE_multi_enum_class( # pylint: disable=invalid-name
|
| 1543 |
+
name: str,
|
| 1544 |
+
default: None,
|
| 1545 |
+
enum_class: type[_ET],
|
| 1546 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1547 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1548 |
+
module_name: str | None = ...,
|
| 1549 |
+
required: bool = ...,
|
| 1550 |
+
**args: Any
|
| 1551 |
+
) -> _flagvalues.FlagHolder[list[_ET] | None]:
|
| 1552 |
+
...
|
| 1553 |
+
|
| 1554 |
+
|
| 1555 |
+
@overload
|
| 1556 |
+
def DEFINE_multi_enum_class( # pylint: disable=invalid-name
|
| 1557 |
+
name: str,
|
| 1558 |
+
# This is separate from `Union[None, _ET, Iterable[str], str]` to avoid a
|
| 1559 |
+
# Pytype issue inferring the return value to
|
| 1560 |
+
# FlagHolder[List[Union[_ET, enum.Enum]]] when an iterable of concrete enum
|
| 1561 |
+
# subclasses are used.
|
| 1562 |
+
default: Iterable[_ET],
|
| 1563 |
+
enum_class: type[_ET],
|
| 1564 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1565 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1566 |
+
module_name: str | None = ...,
|
| 1567 |
+
required: bool = ...,
|
| 1568 |
+
**args: Any
|
| 1569 |
+
) -> _flagvalues.FlagHolder[list[_ET]]:
|
| 1570 |
+
...
|
| 1571 |
+
|
| 1572 |
+
|
| 1573 |
+
@overload
|
| 1574 |
+
def DEFINE_multi_enum_class( # pylint: disable=invalid-name
|
| 1575 |
+
name: str,
|
| 1576 |
+
default: _ET | Iterable[str] | str,
|
| 1577 |
+
enum_class: type[_ET],
|
| 1578 |
+
help: str, # pylint: disable=redefined-builtin
|
| 1579 |
+
flag_values: _flagvalues.FlagValues = ...,
|
| 1580 |
+
module_name: str | None = ...,
|
| 1581 |
+
required: bool = ...,
|
| 1582 |
+
**args: Any
|
| 1583 |
+
) -> _flagvalues.FlagHolder[list[_ET]]:
|
| 1584 |
+
...
|
| 1585 |
+
|
| 1586 |
+
|
| 1587 |
+
def DEFINE_multi_enum_class( # pylint: disable=invalid-name
|
| 1588 |
+
name,
|
| 1589 |
+
default,
|
| 1590 |
+
enum_class,
|
| 1591 |
+
help, # pylint: disable=redefined-builtin
|
| 1592 |
+
flag_values=_flagvalues.FLAGS,
|
| 1593 |
+
module_name=None,
|
| 1594 |
+
case_sensitive=False,
|
| 1595 |
+
required=False,
|
| 1596 |
+
**args
|
| 1597 |
+
):
|
| 1598 |
+
"""Registers a flag whose value can be a list of enum members.
|
| 1599 |
+
|
| 1600 |
+
Use the flag on the command line multiple times to place multiple
|
| 1601 |
+
enum values into the list.
|
| 1602 |
+
|
| 1603 |
+
Args:
|
| 1604 |
+
name: str, the flag name.
|
| 1605 |
+
default: Union[Iterable[Enum], Iterable[str], Enum, str, None], the default
|
| 1606 |
+
value of the flag; see `DEFINE_multi`; only differences are documented
|
| 1607 |
+
here. If the value is a single Enum, it is treated as a single-item list
|
| 1608 |
+
of that Enum value. If it is an iterable, text values within the iterable
|
| 1609 |
+
will be converted to the equivalent Enum objects.
|
| 1610 |
+
enum_class: class, the Enum class with all the possible values for the flag.
|
| 1611 |
+
help: str, the help message.
|
| 1612 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1613 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1614 |
+
module_name: A string, the name of the Python module declaring this flag. If
|
| 1615 |
+
not provided, it will be computed using the stack trace of this call.
|
| 1616 |
+
case_sensitive: bool, whether to map strings to members of the enum_class
|
| 1617 |
+
without considering case.
|
| 1618 |
+
required: bool, is this a required flag. This must be used as a keyword
|
| 1619 |
+
argument.
|
| 1620 |
+
**args: Dictionary with extra keyword args that are passed to the
|
| 1621 |
+
``Flag.__init__``.
|
| 1622 |
+
|
| 1623 |
+
Returns:
|
| 1624 |
+
a handle to defined flag.
|
| 1625 |
+
"""
|
| 1626 |
+
# NOTE: pytype fails if this is a direct return.
|
| 1627 |
+
result = DEFINE_flag(
|
| 1628 |
+
_flag.MultiEnumClassFlag(
|
| 1629 |
+
name,
|
| 1630 |
+
default,
|
| 1631 |
+
help,
|
| 1632 |
+
enum_class,
|
| 1633 |
+
case_sensitive=case_sensitive,
|
| 1634 |
+
**args,
|
| 1635 |
+
),
|
| 1636 |
+
flag_values,
|
| 1637 |
+
module_name,
|
| 1638 |
+
required=True if required else False,
|
| 1639 |
+
)
|
| 1640 |
+
return result
|
| 1641 |
+
|
| 1642 |
+
|
| 1643 |
+
def DEFINE_alias( # pylint: disable=invalid-name
|
| 1644 |
+
name: str,
|
| 1645 |
+
original_name: str,
|
| 1646 |
+
flag_values: _flagvalues.FlagValues = _flagvalues.FLAGS,
|
| 1647 |
+
module_name: str | None = None,
|
| 1648 |
+
) -> _flagvalues.FlagHolder[Any]:
|
| 1649 |
+
"""Defines an alias flag for an existing one.
|
| 1650 |
+
|
| 1651 |
+
Args:
|
| 1652 |
+
name: str, the flag name.
|
| 1653 |
+
original_name: str, the original flag name.
|
| 1654 |
+
flag_values: :class:`FlagValues`, the FlagValues instance with which the
|
| 1655 |
+
flag will be registered. This should almost never need to be overridden.
|
| 1656 |
+
module_name: A string, the name of the module that defines this flag.
|
| 1657 |
+
|
| 1658 |
+
Returns:
|
| 1659 |
+
a handle to defined flag.
|
| 1660 |
+
|
| 1661 |
+
Raises:
|
| 1662 |
+
flags.FlagError:
|
| 1663 |
+
UnrecognizedFlagError: if the referenced flag doesn't exist.
|
| 1664 |
+
DuplicateFlagError: if the alias name has been used by some existing flag.
|
| 1665 |
+
"""
|
| 1666 |
+
if original_name not in flag_values:
|
| 1667 |
+
raise _exceptions.UnrecognizedFlagError(original_name)
|
| 1668 |
+
flag = flag_values[original_name]
|
| 1669 |
+
|
| 1670 |
+
class _FlagAlias(_flag.Flag):
|
| 1671 |
+
"""Overrides Flag class so alias value is copy of original flag value."""
|
| 1672 |
+
|
| 1673 |
+
def parse(self, argument):
|
| 1674 |
+
flag.parse(argument)
|
| 1675 |
+
self.present += 1
|
| 1676 |
+
|
| 1677 |
+
def _parse_from_default(self, value):
|
| 1678 |
+
# The value was already parsed by the aliased flag, so there is no
|
| 1679 |
+
# need to call the parser on it a second time.
|
| 1680 |
+
# Additionally, because of how MultiFlag parses and merges values,
|
| 1681 |
+
# it isn't possible to delegate to the aliased flag and still get
|
| 1682 |
+
# the correct values.
|
| 1683 |
+
return value
|
| 1684 |
+
|
| 1685 |
+
@property
|
| 1686 |
+
def value(self):
|
| 1687 |
+
return flag.value
|
| 1688 |
+
|
| 1689 |
+
@value.setter
|
| 1690 |
+
def value(self, value):
|
| 1691 |
+
flag.value = value
|
| 1692 |
+
|
| 1693 |
+
help_msg = 'Alias for --%s.' % flag.name
|
| 1694 |
+
# If alias_name has been used, flags.DuplicatedFlag will be raised.
|
| 1695 |
+
return DEFINE_flag(
|
| 1696 |
+
_FlagAlias(
|
| 1697 |
+
flag.parser,
|
| 1698 |
+
flag.serializer,
|
| 1699 |
+
name,
|
| 1700 |
+
flag.default,
|
| 1701 |
+
help_msg,
|
| 1702 |
+
boolean=flag.boolean), flag_values, module_name)
|
.venv/lib/python3.13/site-packages/absl/flags/_exceptions.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Exception classes in ABSL flags library.
|
| 16 |
+
|
| 17 |
+
Do NOT import this module directly. Import the flags package and use the
|
| 18 |
+
aliases defined at the package level instead.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import sys
|
| 22 |
+
|
| 23 |
+
from absl.flags import _helpers
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
_helpers.disclaim_module_ids.add(id(sys.modules[__name__]))
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class Error(Exception):
|
| 30 |
+
"""The base class for all flags errors."""
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class CantOpenFlagFileError(Error):
|
| 34 |
+
"""Raised when flagfile fails to open.
|
| 35 |
+
|
| 36 |
+
E.g. the file doesn't exist, or has wrong permissions.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class DuplicateFlagError(Error):
|
| 41 |
+
"""Raised if there is a flag naming conflict."""
|
| 42 |
+
|
| 43 |
+
@classmethod
|
| 44 |
+
def from_flag(cls, flagname, flag_values, other_flag_values=None):
|
| 45 |
+
"""Creates a DuplicateFlagError by providing flag name and values.
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
flagname: str, the name of the flag being redefined.
|
| 49 |
+
flag_values: :class:`FlagValues`, the FlagValues instance containing the
|
| 50 |
+
first definition of flagname.
|
| 51 |
+
other_flag_values: :class:`FlagValues`, if it is not None, it should be
|
| 52 |
+
the FlagValues object where the second definition of flagname occurs.
|
| 53 |
+
If it is None, we assume that we're being called when attempting to
|
| 54 |
+
create the flag a second time, and we use the module calling this one
|
| 55 |
+
as the source of the second definition.
|
| 56 |
+
|
| 57 |
+
Returns:
|
| 58 |
+
An instance of DuplicateFlagError.
|
| 59 |
+
"""
|
| 60 |
+
first_module = flag_values.find_module_defining_flag(
|
| 61 |
+
flagname, default='<unknown>')
|
| 62 |
+
if other_flag_values is None:
|
| 63 |
+
second_module = _helpers.get_calling_module()
|
| 64 |
+
else:
|
| 65 |
+
second_module = other_flag_values.find_module_defining_flag(
|
| 66 |
+
flagname, default='<unknown>')
|
| 67 |
+
flag_summary = flag_values[flagname].help
|
| 68 |
+
msg = ("The flag '%s' is defined twice. First from %s, Second from %s. "
|
| 69 |
+
"Description from first occurrence: %s") % (
|
| 70 |
+
flagname, first_module, second_module, flag_summary)
|
| 71 |
+
return cls(msg)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class IllegalFlagValueError(Error):
|
| 75 |
+
"""Raised when the flag command line argument is illegal."""
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class UnrecognizedFlagError(Error):
|
| 79 |
+
"""Raised when a flag is unrecognized.
|
| 80 |
+
|
| 81 |
+
Attributes:
|
| 82 |
+
flagname: str, the name of the unrecognized flag.
|
| 83 |
+
flagvalue: The value of the flag, empty if the flag is not defined.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def __init__(self, flagname, flagvalue='', suggestions=None):
|
| 87 |
+
self.flagname = flagname
|
| 88 |
+
self.flagvalue = flagvalue
|
| 89 |
+
if suggestions:
|
| 90 |
+
# Space before the question mark is intentional to not include it in the
|
| 91 |
+
# selection when copy-pasting the suggestion from (some) terminals.
|
| 92 |
+
tip = '. Did you mean: %s ?' % ', '.join(suggestions)
|
| 93 |
+
else:
|
| 94 |
+
tip = ''
|
| 95 |
+
super().__init__("Unknown command line flag '%s'%s" % (flagname, tip))
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class UnparsedFlagAccessError(Error):
|
| 99 |
+
"""Raised when accessing the flag value from unparsed :class:`FlagValues`."""
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class ValidationError(Error):
|
| 103 |
+
"""Raised when flag validator constraint is not satisfied."""
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class FlagNameConflictsWithMethodError(Error):
|
| 107 |
+
"""Raised when a flag name conflicts with :class:`FlagValues` methods."""
|
.venv/lib/python3.13/site-packages/absl/flags/_flag.py
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Contains Flag class - information about single command-line flag.
|
| 16 |
+
|
| 17 |
+
Do NOT import this module directly. Import the flags package and use the
|
| 18 |
+
aliases defined at the package level instead.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from collections.abc import Iterable
|
| 22 |
+
import copy
|
| 23 |
+
import enum
|
| 24 |
+
import functools
|
| 25 |
+
from typing import Any, Generic, TypeVar
|
| 26 |
+
from xml.dom import minidom
|
| 27 |
+
|
| 28 |
+
from absl.flags import _argument_parser
|
| 29 |
+
from absl.flags import _exceptions
|
| 30 |
+
from absl.flags import _helpers
|
| 31 |
+
|
| 32 |
+
_T = TypeVar('_T')
|
| 33 |
+
_ET = TypeVar('_ET', bound=enum.Enum)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@functools.total_ordering
|
| 37 |
+
class Flag(Generic[_T]):
|
| 38 |
+
"""Information about a command-line flag.
|
| 39 |
+
|
| 40 |
+
Attributes:
|
| 41 |
+
name: the name for this flag
|
| 42 |
+
default: the default value for this flag
|
| 43 |
+
default_unparsed: the unparsed default value for this flag.
|
| 44 |
+
default_as_str: default value as repr'd string, e.g., "'true'"
|
| 45 |
+
(or None)
|
| 46 |
+
value: the most recent parsed value of this flag set by :meth:`parse`
|
| 47 |
+
help: a help string or None if no help is available
|
| 48 |
+
short_name: the single letter alias for this flag (or None)
|
| 49 |
+
boolean: if 'true', this flag does not accept arguments
|
| 50 |
+
present: true if this flag was parsed from command line flags
|
| 51 |
+
parser: an :class:`~absl.flags.ArgumentParser` object
|
| 52 |
+
serializer: an ArgumentSerializer object
|
| 53 |
+
allow_override: the flag may be redefined without raising an error,
|
| 54 |
+
and newly defined flag overrides the old one.
|
| 55 |
+
allow_override_cpp: use the flag from C++ if available the flag
|
| 56 |
+
definition is replaced by the C++ flag after init
|
| 57 |
+
allow_hide_cpp: use the Python flag despite having a C++ flag with
|
| 58 |
+
the same name (ignore the C++ flag)
|
| 59 |
+
using_default_value: the flag value has not been set by user
|
| 60 |
+
allow_overwrite: the flag may be parsed more than once without
|
| 61 |
+
raising an error, the last set value will be used
|
| 62 |
+
allow_using_method_names: whether this flag can be defined even if
|
| 63 |
+
it has a name that conflicts with a FlagValues method.
|
| 64 |
+
validators: list of the flag validators.
|
| 65 |
+
|
| 66 |
+
The only public method of a ``Flag`` object is :meth:`parse`, but it is
|
| 67 |
+
typically only called by a :class:`~absl.flags.FlagValues` object. The
|
| 68 |
+
:meth:`parse` method is a thin wrapper around the
|
| 69 |
+
:meth:`ArgumentParser.parse()<absl.flags.ArgumentParser.parse>` method. The
|
| 70 |
+
parsed value is saved in ``.value``, and the ``.present`` attribute is
|
| 71 |
+
updated. If this flag was already present, an Error is raised.
|
| 72 |
+
|
| 73 |
+
:meth:`parse` is also called during ``__init__`` to parse the default value
|
| 74 |
+
and initialize the ``.value`` attribute. This enables other python modules to
|
| 75 |
+
safely use flags even if the ``__main__`` module neglects to parse the
|
| 76 |
+
command line arguments. The ``.present`` attribute is cleared after
|
| 77 |
+
``__init__`` parsing. If the default value is set to ``None``, then the
|
| 78 |
+
``__init__`` parsing step is skipped and the ``.value`` attribute is
|
| 79 |
+
initialized to None.
|
| 80 |
+
|
| 81 |
+
Note: The default value is also presented to the user in the help
|
| 82 |
+
string, so it is important that it be a legal value for this flag.
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
# NOTE: pytype doesn't find defaults without this.
|
| 86 |
+
default: _T | None
|
| 87 |
+
default_as_str: str | None
|
| 88 |
+
default_unparsed: _T | None | str
|
| 89 |
+
|
| 90 |
+
parser: _argument_parser.ArgumentParser[_T]
|
| 91 |
+
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
parser: _argument_parser.ArgumentParser[_T],
|
| 95 |
+
serializer: _argument_parser.ArgumentSerializer[_T] | None,
|
| 96 |
+
name: str,
|
| 97 |
+
default: _T | None | str,
|
| 98 |
+
help_string: str | None,
|
| 99 |
+
short_name: str | None = None,
|
| 100 |
+
boolean: bool = False,
|
| 101 |
+
allow_override: bool = False,
|
| 102 |
+
allow_override_cpp: bool = False,
|
| 103 |
+
allow_hide_cpp: bool = False,
|
| 104 |
+
allow_overwrite: bool = True,
|
| 105 |
+
allow_using_method_names: bool = False,
|
| 106 |
+
) -> None:
|
| 107 |
+
self.name = name
|
| 108 |
+
|
| 109 |
+
if not help_string:
|
| 110 |
+
help_string = '(no help available)'
|
| 111 |
+
|
| 112 |
+
self.help = help_string
|
| 113 |
+
self.short_name = short_name
|
| 114 |
+
self.boolean = boolean
|
| 115 |
+
self.present = 0
|
| 116 |
+
self.parser = parser # type: ignore[annotation-type-mismatch]
|
| 117 |
+
self.serializer = serializer
|
| 118 |
+
self.allow_override = allow_override
|
| 119 |
+
self.allow_override_cpp = allow_override_cpp
|
| 120 |
+
self.allow_hide_cpp = allow_hide_cpp
|
| 121 |
+
self.allow_overwrite = allow_overwrite
|
| 122 |
+
self.allow_using_method_names = allow_using_method_names
|
| 123 |
+
|
| 124 |
+
self.using_default_value = True
|
| 125 |
+
self._value: _T | None = None
|
| 126 |
+
self.validators: list[Any] = []
|
| 127 |
+
if self.allow_hide_cpp and self.allow_override_cpp:
|
| 128 |
+
raise _exceptions.Error(
|
| 129 |
+
"Can't have both allow_hide_cpp (means use Python flag) and "
|
| 130 |
+
'allow_override_cpp (means use C++ flag after InitGoogle)')
|
| 131 |
+
|
| 132 |
+
self._set_default(default)
|
| 133 |
+
|
| 134 |
+
@property
|
| 135 |
+
def value(self) -> _T | None:
|
| 136 |
+
return self._value
|
| 137 |
+
|
| 138 |
+
@value.setter
|
| 139 |
+
def value(self, value: _T | None):
|
| 140 |
+
self._value = value
|
| 141 |
+
|
| 142 |
+
def __hash__(self):
|
| 143 |
+
return hash(id(self))
|
| 144 |
+
|
| 145 |
+
def __eq__(self, other):
|
| 146 |
+
return self is other
|
| 147 |
+
|
| 148 |
+
def __lt__(self, other):
|
| 149 |
+
if isinstance(other, Flag):
|
| 150 |
+
return id(self) < id(other)
|
| 151 |
+
return NotImplemented
|
| 152 |
+
|
| 153 |
+
def __bool__(self):
|
| 154 |
+
raise TypeError('A Flag instance would always be True. '
|
| 155 |
+
'Did you mean to test the `.value` attribute?')
|
| 156 |
+
|
| 157 |
+
def __getstate__(self):
|
| 158 |
+
raise TypeError("can't pickle Flag objects")
|
| 159 |
+
|
| 160 |
+
def __copy__(self):
|
| 161 |
+
raise TypeError('%s does not support shallow copies. '
|
| 162 |
+
'Use copy.deepcopy instead.' % type(self).__name__)
|
| 163 |
+
|
| 164 |
+
def __deepcopy__(self, memo: dict[int, Any]) -> 'Flag[_T]':
|
| 165 |
+
result = object.__new__(type(self))
|
| 166 |
+
result.__dict__ = copy.deepcopy(self.__dict__, memo)
|
| 167 |
+
return result
|
| 168 |
+
|
| 169 |
+
def _get_parsed_value_as_string(self, value: _T | None) -> str | None:
|
| 170 |
+
"""Returns parsed flag value as string."""
|
| 171 |
+
if value is None:
|
| 172 |
+
return None
|
| 173 |
+
if self.serializer:
|
| 174 |
+
return repr(self.serializer.serialize(value))
|
| 175 |
+
if self.boolean:
|
| 176 |
+
if value:
|
| 177 |
+
return repr('true')
|
| 178 |
+
else:
|
| 179 |
+
return repr('false')
|
| 180 |
+
return repr(str(value))
|
| 181 |
+
|
| 182 |
+
def parse(self, argument: str | _T) -> None:
|
| 183 |
+
"""Parses string and sets flag value.
|
| 184 |
+
|
| 185 |
+
Args:
|
| 186 |
+
argument: str or the correct flag value type, argument to be parsed.
|
| 187 |
+
"""
|
| 188 |
+
if self.present and not self.allow_overwrite:
|
| 189 |
+
raise _exceptions.IllegalFlagValueError(
|
| 190 |
+
'flag --%s=%s: already defined as %s' % (
|
| 191 |
+
self.name, argument, self.value))
|
| 192 |
+
self.value = self._parse(argument)
|
| 193 |
+
self.present += 1
|
| 194 |
+
|
| 195 |
+
def _parse(self, argument: str | _T) -> _T | None:
|
| 196 |
+
"""Internal parse function.
|
| 197 |
+
|
| 198 |
+
It returns the parsed value, and does not modify class states.
|
| 199 |
+
|
| 200 |
+
Args:
|
| 201 |
+
argument: str or the correct flag value type, argument to be parsed.
|
| 202 |
+
|
| 203 |
+
Returns:
|
| 204 |
+
The parsed value.
|
| 205 |
+
"""
|
| 206 |
+
try:
|
| 207 |
+
return self.parser.parse(argument) # type: ignore[arg-type]
|
| 208 |
+
except (TypeError, ValueError, OverflowError) as e:
|
| 209 |
+
# Recast as IllegalFlagValueError.
|
| 210 |
+
raise _exceptions.IllegalFlagValueError(
|
| 211 |
+
'flag --%s=%s: %s' % (self.name, argument, e))
|
| 212 |
+
|
| 213 |
+
def unparse(self) -> None:
|
| 214 |
+
self.value = self.default
|
| 215 |
+
self.using_default_value = True
|
| 216 |
+
self.present = 0
|
| 217 |
+
|
| 218 |
+
def serialize(self) -> str:
|
| 219 |
+
"""Serializes the flag."""
|
| 220 |
+
return self._serialize(self.value)
|
| 221 |
+
|
| 222 |
+
def _serialize(self, value: _T | None) -> str:
|
| 223 |
+
"""Internal serialize function."""
|
| 224 |
+
if value is None:
|
| 225 |
+
return ''
|
| 226 |
+
if self.boolean:
|
| 227 |
+
if value:
|
| 228 |
+
return '--%s' % self.name
|
| 229 |
+
else:
|
| 230 |
+
return '--no%s' % self.name
|
| 231 |
+
else:
|
| 232 |
+
if not self.serializer:
|
| 233 |
+
raise _exceptions.Error(
|
| 234 |
+
'Serializer not present for flag %s' % self.name)
|
| 235 |
+
return '--%s=%s' % (self.name, self.serializer.serialize(value))
|
| 236 |
+
|
| 237 |
+
def _set_default(self, value: _T | None | str) -> None:
|
| 238 |
+
"""Changes the default value (and current value too) for this Flag."""
|
| 239 |
+
self.default_unparsed = value
|
| 240 |
+
if value is None:
|
| 241 |
+
self.default = None
|
| 242 |
+
else:
|
| 243 |
+
self.default = self._parse_from_default(value)
|
| 244 |
+
self.default_as_str = self._get_parsed_value_as_string(self.default)
|
| 245 |
+
if self.using_default_value:
|
| 246 |
+
self.value = self.default
|
| 247 |
+
|
| 248 |
+
# This is split out so that aliases can skip regular parsing of the default
|
| 249 |
+
# value.
|
| 250 |
+
def _parse_from_default(self, value: str | _T) -> _T | None:
|
| 251 |
+
return self._parse(value)
|
| 252 |
+
|
| 253 |
+
def flag_type(self) -> str:
|
| 254 |
+
"""Returns a str that describes the type of the flag.
|
| 255 |
+
|
| 256 |
+
NOTE: we use strings, and not the types.*Type constants because
|
| 257 |
+
our flags can have more exotic types, e.g., 'comma separated list
|
| 258 |
+
of strings', 'whitespace separated list of strings', etc.
|
| 259 |
+
"""
|
| 260 |
+
return self.parser.flag_type()
|
| 261 |
+
|
| 262 |
+
def _create_xml_dom_element(
|
| 263 |
+
self, doc: minidom.Document, module_name: str, is_key: bool = False
|
| 264 |
+
) -> minidom.Element:
|
| 265 |
+
"""Returns an XML element that contains this flag's information.
|
| 266 |
+
|
| 267 |
+
This is information that is relevant to all flags (e.g., name,
|
| 268 |
+
meaning, etc.). If you defined a flag that has some other pieces of
|
| 269 |
+
info, then please override _ExtraXMLInfo.
|
| 270 |
+
|
| 271 |
+
Please do NOT override this method.
|
| 272 |
+
|
| 273 |
+
Args:
|
| 274 |
+
doc: minidom.Document, the DOM document it should create nodes from.
|
| 275 |
+
module_name: str,, the name of the module that defines this flag.
|
| 276 |
+
is_key: boolean, True iff this flag is key for main module.
|
| 277 |
+
|
| 278 |
+
Returns:
|
| 279 |
+
A minidom.Element instance.
|
| 280 |
+
"""
|
| 281 |
+
element = doc.createElement('flag')
|
| 282 |
+
if is_key:
|
| 283 |
+
element.appendChild(_helpers.create_xml_dom_element(doc, 'key', 'yes'))
|
| 284 |
+
element.appendChild(_helpers.create_xml_dom_element(
|
| 285 |
+
doc, 'file', module_name))
|
| 286 |
+
# Adds flag features that are relevant for all flags.
|
| 287 |
+
element.appendChild(_helpers.create_xml_dom_element(doc, 'name', self.name))
|
| 288 |
+
if self.short_name:
|
| 289 |
+
element.appendChild(_helpers.create_xml_dom_element(
|
| 290 |
+
doc, 'short_name', self.short_name))
|
| 291 |
+
if self.help:
|
| 292 |
+
element.appendChild(_helpers.create_xml_dom_element(
|
| 293 |
+
doc, 'meaning', self.help))
|
| 294 |
+
# The default flag value can either be represented as a string like on the
|
| 295 |
+
# command line, or as a Python object. We serialize this value in the
|
| 296 |
+
# latter case in order to remain consistent.
|
| 297 |
+
if self.serializer and not isinstance(self.default, str):
|
| 298 |
+
if self.default is not None:
|
| 299 |
+
default_serialized = self.serializer.serialize(self.default)
|
| 300 |
+
else:
|
| 301 |
+
default_serialized = ''
|
| 302 |
+
else:
|
| 303 |
+
default_serialized = self.default # type: ignore[assignment]
|
| 304 |
+
element.appendChild(_helpers.create_xml_dom_element(
|
| 305 |
+
doc, 'default', default_serialized))
|
| 306 |
+
value_serialized = self._serialize_value_for_xml(self.value)
|
| 307 |
+
element.appendChild(_helpers.create_xml_dom_element(
|
| 308 |
+
doc, 'current', value_serialized))
|
| 309 |
+
element.appendChild(_helpers.create_xml_dom_element(
|
| 310 |
+
doc, 'type', self.flag_type()))
|
| 311 |
+
# Adds extra flag features this flag may have.
|
| 312 |
+
for e in self._extra_xml_dom_elements(doc):
|
| 313 |
+
element.appendChild(e)
|
| 314 |
+
return element
|
| 315 |
+
|
| 316 |
+
def _serialize_value_for_xml(self, value: _T | None) -> Any:
|
| 317 |
+
"""Returns the serialized value, for use in an XML help text."""
|
| 318 |
+
return value
|
| 319 |
+
|
| 320 |
+
def _extra_xml_dom_elements(
|
| 321 |
+
self, doc: minidom.Document
|
| 322 |
+
) -> list[minidom.Element]:
|
| 323 |
+
"""Returns extra info about this flag in XML.
|
| 324 |
+
|
| 325 |
+
"Extra" means "not already included by _create_xml_dom_element above."
|
| 326 |
+
|
| 327 |
+
Args:
|
| 328 |
+
doc: minidom.Document, the DOM document it should create nodes from.
|
| 329 |
+
|
| 330 |
+
Returns:
|
| 331 |
+
A list of minidom.Element.
|
| 332 |
+
"""
|
| 333 |
+
# Usually, the parser knows the extra details about the flag, so
|
| 334 |
+
# we just forward the call to it.
|
| 335 |
+
return self.parser._custom_xml_dom_elements(doc) # pylint: disable=protected-access
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
class BooleanFlag(Flag[bool]):
|
| 339 |
+
"""Basic boolean flag.
|
| 340 |
+
|
| 341 |
+
Boolean flags do not take any arguments, and their value is either
|
| 342 |
+
``True`` (1) or ``False`` (0). The false value is specified on the command
|
| 343 |
+
line by prepending the word ``'no'`` to either the long or the short flag
|
| 344 |
+
name.
|
| 345 |
+
|
| 346 |
+
For example, if a Boolean flag was created whose long name was
|
| 347 |
+
``'update'`` and whose short name was ``'x'``, then this flag could be
|
| 348 |
+
explicitly unset through either ``--noupdate`` or ``--nox``.
|
| 349 |
+
"""
|
| 350 |
+
|
| 351 |
+
def __init__(
|
| 352 |
+
self,
|
| 353 |
+
name: str,
|
| 354 |
+
default: bool | None | str,
|
| 355 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 356 |
+
short_name: str | None = None,
|
| 357 |
+
**args
|
| 358 |
+
) -> None:
|
| 359 |
+
p = _argument_parser.BooleanParser()
|
| 360 |
+
super().__init__(p, None, name, default, help, short_name, True, **args)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
class EnumFlag(Flag[str]):
|
| 364 |
+
"""Basic enum flag; its value can be any string from list of enum_values."""
|
| 365 |
+
|
| 366 |
+
parser: _argument_parser.EnumParser
|
| 367 |
+
|
| 368 |
+
def __init__(
|
| 369 |
+
self,
|
| 370 |
+
name: str,
|
| 371 |
+
default: str | None,
|
| 372 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 373 |
+
enum_values: Iterable[str],
|
| 374 |
+
short_name: str | None = None,
|
| 375 |
+
case_sensitive: bool = True,
|
| 376 |
+
**args
|
| 377 |
+
):
|
| 378 |
+
p = _argument_parser.EnumParser(enum_values, case_sensitive)
|
| 379 |
+
g: _argument_parser.ArgumentSerializer[str]
|
| 380 |
+
g = _argument_parser.ArgumentSerializer()
|
| 381 |
+
super().__init__(p, g, name, default, help, short_name, **args)
|
| 382 |
+
self.parser = p
|
| 383 |
+
self.help = '<%s>: %s' % ('|'.join(p.enum_values), self.help)
|
| 384 |
+
|
| 385 |
+
def _extra_xml_dom_elements(
|
| 386 |
+
self, doc: minidom.Document
|
| 387 |
+
) -> list[minidom.Element]:
|
| 388 |
+
elements = []
|
| 389 |
+
for enum_value in self.parser.enum_values:
|
| 390 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 391 |
+
doc, 'enum_value', enum_value))
|
| 392 |
+
return elements
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
class EnumClassFlag(Flag[_ET]):
|
| 396 |
+
"""Basic enum flag; its value is an enum class's member."""
|
| 397 |
+
|
| 398 |
+
parser: _argument_parser.EnumClassParser
|
| 399 |
+
|
| 400 |
+
def __init__(
|
| 401 |
+
self,
|
| 402 |
+
name: str,
|
| 403 |
+
default: _ET | None | str,
|
| 404 |
+
help: str | None, # pylint: disable=redefined-builtin
|
| 405 |
+
enum_class: type[_ET],
|
| 406 |
+
short_name: str | None = None,
|
| 407 |
+
case_sensitive: bool = False,
|
| 408 |
+
**args
|
| 409 |
+
):
|
| 410 |
+
p = _argument_parser.EnumClassParser(
|
| 411 |
+
enum_class, case_sensitive=case_sensitive
|
| 412 |
+
)
|
| 413 |
+
g: _argument_parser.EnumClassSerializer[_ET]
|
| 414 |
+
g = _argument_parser.EnumClassSerializer(lowercase=not case_sensitive)
|
| 415 |
+
super().__init__(p, g, name, default, help, short_name, **args)
|
| 416 |
+
self.parser = p
|
| 417 |
+
self.help = '<%s>: %s' % ('|'.join(p.member_names), self.help)
|
| 418 |
+
|
| 419 |
+
def _extra_xml_dom_elements(
|
| 420 |
+
self, doc: minidom.Document
|
| 421 |
+
) -> list[minidom.Element]:
|
| 422 |
+
elements = []
|
| 423 |
+
for enum_value in self.parser.enum_class.__members__.keys():
|
| 424 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 425 |
+
doc, 'enum_value', enum_value))
|
| 426 |
+
return elements
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
class MultiFlag(Generic[_T], Flag[list[_T]]):
|
| 430 |
+
"""A flag that can appear multiple time on the command-line.
|
| 431 |
+
|
| 432 |
+
The value of such a flag is a list that contains the individual values
|
| 433 |
+
from all the appearances of that flag on the command-line.
|
| 434 |
+
|
| 435 |
+
See the __doc__ for Flag for most behavior of this class. Only
|
| 436 |
+
differences in behavior are described here:
|
| 437 |
+
|
| 438 |
+
* The default value may be either a single value or an iterable of values.
|
| 439 |
+
A single value is transformed into a single-item list of that value.
|
| 440 |
+
|
| 441 |
+
* The value of the flag is always a list, even if the option was
|
| 442 |
+
only supplied once, and even if the default value is a single
|
| 443 |
+
value
|
| 444 |
+
"""
|
| 445 |
+
|
| 446 |
+
def __init__(self, *args, **kwargs):
|
| 447 |
+
super().__init__(*args, **kwargs)
|
| 448 |
+
self.help += ';\n repeat this option to specify a list of values'
|
| 449 |
+
|
| 450 |
+
def parse(self, arguments: str | _T | Iterable[_T]): # pylint: disable=arguments-renamed
|
| 451 |
+
"""Parses one or more arguments with the installed parser.
|
| 452 |
+
|
| 453 |
+
Args:
|
| 454 |
+
arguments: a single argument or a list of arguments (typically a
|
| 455 |
+
list of default values); a single argument is converted
|
| 456 |
+
internally into a list containing one item.
|
| 457 |
+
"""
|
| 458 |
+
new_values = self._parse(arguments)
|
| 459 |
+
if self.present:
|
| 460 |
+
assert self.value is not None
|
| 461 |
+
self.value.extend(new_values)
|
| 462 |
+
else:
|
| 463 |
+
self.value = new_values
|
| 464 |
+
self.present += len(new_values)
|
| 465 |
+
|
| 466 |
+
def _parse(self, arguments: str | _T | Iterable[_T]) -> list[_T]: # pylint: disable=arguments-renamed
|
| 467 |
+
arguments_list: list[str | _T]
|
| 468 |
+
|
| 469 |
+
if isinstance(arguments, str):
|
| 470 |
+
arguments_list = [arguments]
|
| 471 |
+
|
| 472 |
+
elif isinstance(arguments, Iterable):
|
| 473 |
+
arguments_list = list(arguments)
|
| 474 |
+
|
| 475 |
+
else:
|
| 476 |
+
# Default value may be a list of values. Most other arguments
|
| 477 |
+
# will not be, so convert them into a single-item list to make
|
| 478 |
+
# processing simpler below.
|
| 479 |
+
arguments_list = [arguments]
|
| 480 |
+
|
| 481 |
+
return [super(MultiFlag, self)._parse(item) for item in arguments_list] # type: ignore
|
| 482 |
+
|
| 483 |
+
def _serialize(self, value: list[_T] | None) -> str:
|
| 484 |
+
"""See base class."""
|
| 485 |
+
if not self.serializer:
|
| 486 |
+
raise _exceptions.Error(
|
| 487 |
+
'Serializer not present for flag %s' % self.name)
|
| 488 |
+
if value is None:
|
| 489 |
+
return ''
|
| 490 |
+
|
| 491 |
+
serialized_items = [
|
| 492 |
+
super(MultiFlag, self)._serialize(value_item) # type: ignore[arg-type]
|
| 493 |
+
for value_item in value
|
| 494 |
+
]
|
| 495 |
+
|
| 496 |
+
return '\n'.join(serialized_items)
|
| 497 |
+
|
| 498 |
+
def flag_type(self):
|
| 499 |
+
"""See base class."""
|
| 500 |
+
return 'multi ' + self.parser.flag_type()
|
| 501 |
+
|
| 502 |
+
def _extra_xml_dom_elements(
|
| 503 |
+
self, doc: minidom.Document
|
| 504 |
+
) -> list[minidom.Element]:
|
| 505 |
+
elements = []
|
| 506 |
+
if hasattr(self.parser, 'enum_values'):
|
| 507 |
+
for enum_value in self.parser.enum_values: # pytype: disable=attribute-error
|
| 508 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 509 |
+
doc, 'enum_value', enum_value))
|
| 510 |
+
return elements
|
| 511 |
+
|
| 512 |
+
|
| 513 |
+
class MultiEnumClassFlag(MultiFlag[_ET]): # pytype: disable=not-indexable
|
| 514 |
+
"""A multi_enum_class flag.
|
| 515 |
+
|
| 516 |
+
See the __doc__ for MultiFlag for most behaviors of this class. In addition,
|
| 517 |
+
this class knows how to handle enum.Enum instances as values for this flag
|
| 518 |
+
type.
|
| 519 |
+
"""
|
| 520 |
+
|
| 521 |
+
parser: _argument_parser.EnumClassParser[_ET] # type: ignore[assignment]
|
| 522 |
+
|
| 523 |
+
def __init__(
|
| 524 |
+
self,
|
| 525 |
+
name: str,
|
| 526 |
+
default: None | Iterable[_ET] | _ET | Iterable[str] | str,
|
| 527 |
+
help_string: str,
|
| 528 |
+
enum_class: type[_ET],
|
| 529 |
+
case_sensitive: bool = False,
|
| 530 |
+
**args
|
| 531 |
+
):
|
| 532 |
+
p = _argument_parser.EnumClassParser(
|
| 533 |
+
enum_class, case_sensitive=case_sensitive)
|
| 534 |
+
g: _argument_parser.EnumClassListSerializer
|
| 535 |
+
g = _argument_parser.EnumClassListSerializer(
|
| 536 |
+
list_sep=',', lowercase=not case_sensitive)
|
| 537 |
+
super().__init__(p, g, name, default, help_string, **args)
|
| 538 |
+
# NOTE: parser should be typed EnumClassParser[_ET] but the constructor
|
| 539 |
+
# restricts the available interface to ArgumentParser[str].
|
| 540 |
+
self.parser = p
|
| 541 |
+
# NOTE: serializer should be non-Optional but this isn't inferred.
|
| 542 |
+
self.serializer = g
|
| 543 |
+
self.help = (
|
| 544 |
+
'<%s>: %s;\n repeat this option to specify a list of values' %
|
| 545 |
+
('|'.join(p.member_names), help_string or '(no help available)'))
|
| 546 |
+
|
| 547 |
+
def _extra_xml_dom_elements(
|
| 548 |
+
self, doc: minidom.Document
|
| 549 |
+
) -> list[minidom.Element]:
|
| 550 |
+
elements = []
|
| 551 |
+
for enum_value in self.parser.enum_class.__members__.keys(): # pytype: disable=attribute-error
|
| 552 |
+
elements.append(_helpers.create_xml_dom_element(
|
| 553 |
+
doc, 'enum_value', enum_value))
|
| 554 |
+
return elements
|
| 555 |
+
|
| 556 |
+
def _serialize_value_for_xml(self, value):
|
| 557 |
+
"""See base class."""
|
| 558 |
+
if value is not None:
|
| 559 |
+
if not self.serializer:
|
| 560 |
+
raise _exceptions.Error(
|
| 561 |
+
'Serializer not present for flag %s' % self.name
|
| 562 |
+
)
|
| 563 |
+
value_serialized = self.serializer.serialize(value)
|
| 564 |
+
else:
|
| 565 |
+
value_serialized = ''
|
| 566 |
+
return value_serialized
|
.venv/lib/python3.13/site-packages/absl/flags/_flagvalues.py
ADDED
|
@@ -0,0 +1,1552 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""Defines the FlagValues class - registry of 'Flag' objects.
|
| 15 |
+
|
| 16 |
+
Do NOT import this module directly. Import the flags package and use the
|
| 17 |
+
aliases defined at the package level instead.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
| 21 |
+
import copy
|
| 22 |
+
from importlib import abc
|
| 23 |
+
import logging
|
| 24 |
+
import os
|
| 25 |
+
import sys
|
| 26 |
+
from typing import Any, Generic, TextIO, TypeVar
|
| 27 |
+
from xml.dom import minidom
|
| 28 |
+
|
| 29 |
+
from absl.flags import _exceptions
|
| 30 |
+
from absl.flags import _flag
|
| 31 |
+
from absl.flags import _helpers
|
| 32 |
+
from absl.flags import _validators_classes
|
| 33 |
+
from absl.flags._flag import Flag
|
| 34 |
+
|
| 35 |
+
# Add flagvalues module to disclaimed module ids.
|
| 36 |
+
_helpers.disclaim_module_ids.add(id(sys.modules[__name__]))
|
| 37 |
+
|
| 38 |
+
_T = TypeVar('_T')
|
| 39 |
+
_T_co = TypeVar('_T_co', covariant=True) # pytype: disable=not-supported-yet
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class ReloadDetector(abc.MetaPathFinder):
|
| 43 |
+
"""Helper class for detecting reloads."""
|
| 44 |
+
|
| 45 |
+
def __init__(self):
|
| 46 |
+
self.reloading_modules = set()
|
| 47 |
+
|
| 48 |
+
def find_spec(self, fullname, path, target=None):
|
| 49 |
+
if fullname in sys.modules: # Indicates a reload.
|
| 50 |
+
self.reloading_modules.add(fullname)
|
| 51 |
+
return None
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
reload_detector = ReloadDetector()
|
| 55 |
+
|
| 56 |
+
# Register the hook by inserting it right before the last path finder.
|
| 57 |
+
# This should play nicely with lazy imports.
|
| 58 |
+
sys.meta_path.insert(-1, reload_detector)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class FlagValues:
|
| 62 |
+
"""Registry of :class:`~absl.flags.Flag` objects.
|
| 63 |
+
|
| 64 |
+
A :class:`FlagValues` can then scan command line arguments, passing flag
|
| 65 |
+
arguments through to the 'Flag' objects that it owns. It also
|
| 66 |
+
provides easy access to the flag values. Typically only one
|
| 67 |
+
:class:`FlagValues` object is needed by an application:
|
| 68 |
+
:const:`FLAGS`.
|
| 69 |
+
|
| 70 |
+
This class is heavily overloaded:
|
| 71 |
+
|
| 72 |
+
:class:`Flag` objects are registered via ``__setitem__``::
|
| 73 |
+
|
| 74 |
+
FLAGS['longname'] = x # register a new flag
|
| 75 |
+
|
| 76 |
+
The ``.value`` attribute of the registered :class:`~absl.flags.Flag` objects
|
| 77 |
+
can be accessed as attributes of this :class:`FlagValues` object, through
|
| 78 |
+
``__getattr__``. Both the long and short name of the original
|
| 79 |
+
:class:`~absl.flags.Flag` objects can be used to access its value::
|
| 80 |
+
|
| 81 |
+
FLAGS.longname # parsed flag value
|
| 82 |
+
FLAGS.x # parsed flag value (short name)
|
| 83 |
+
|
| 84 |
+
Command line arguments are scanned and passed to the registered
|
| 85 |
+
:class:`~absl.flags.Flag` objects through the ``__call__`` method. Unparsed
|
| 86 |
+
arguments, including ``argv[0]`` (e.g. the program name) are returned::
|
| 87 |
+
|
| 88 |
+
argv = FLAGS(sys.argv) # scan command line arguments
|
| 89 |
+
|
| 90 |
+
The original registered :class:`~absl.flags.Flag` objects can be retrieved
|
| 91 |
+
through the use of the dictionary-like operator, ``__getitem__``::
|
| 92 |
+
|
| 93 |
+
x = FLAGS['longname'] # access the registered Flag object
|
| 94 |
+
|
| 95 |
+
The ``str()`` operator of a :class:`absl.flags.FlagValues` object provides
|
| 96 |
+
help for all of the registered :class:`~absl.flags.Flag` objects.
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
_HAS_DYNAMIC_ATTRIBUTES = True
|
| 100 |
+
|
| 101 |
+
# A note on collections.abc.Mapping:
|
| 102 |
+
# FlagValues defines __getitem__, __iter__, and __len__. It makes perfect
|
| 103 |
+
# sense to let it be a collections.abc.Mapping class. However, we are not
|
| 104 |
+
# able to do so. The mixin methods, e.g. keys, values, are not uncommon flag
|
| 105 |
+
# names. Those flag values would not be accessible via the FLAGS.xxx form.
|
| 106 |
+
|
| 107 |
+
__dict__: dict[str, Any]
|
| 108 |
+
|
| 109 |
+
def __init__(self):
|
| 110 |
+
# Since everything in this class is so heavily overloaded, the only
|
| 111 |
+
# way of defining and using fields is to access __dict__ directly.
|
| 112 |
+
|
| 113 |
+
# Dictionary: flag name (string) -> Flag object.
|
| 114 |
+
self.__dict__['__flags'] = {}
|
| 115 |
+
|
| 116 |
+
# Set: name of hidden flag (string).
|
| 117 |
+
# Holds flags that should not be directly accessible from Python.
|
| 118 |
+
self.__dict__['__hiddenflags'] = set()
|
| 119 |
+
|
| 120 |
+
# Dictionary: module name (string) -> list of Flag objects that are defined
|
| 121 |
+
# by that module.
|
| 122 |
+
self.__dict__['__flags_by_module'] = {}
|
| 123 |
+
# Dictionary: module id (int) -> list of Flag objects that are defined by
|
| 124 |
+
# that module.
|
| 125 |
+
self.__dict__['__flags_by_module_id'] = {}
|
| 126 |
+
# Dictionary: module name (string) -> list of Flag objects that are
|
| 127 |
+
# key for that module.
|
| 128 |
+
self.__dict__['__key_flags_by_module'] = {}
|
| 129 |
+
|
| 130 |
+
# Bool: True if flags were parsed.
|
| 131 |
+
self.__dict__['__flags_parsed'] = False
|
| 132 |
+
|
| 133 |
+
# Bool: True if unparse_flags() was called.
|
| 134 |
+
self.__dict__['__unparse_flags_called'] = False
|
| 135 |
+
|
| 136 |
+
# None or Method(name, value) to call from __setattr__ for an unknown flag.
|
| 137 |
+
self.__dict__['__set_unknown'] = None
|
| 138 |
+
|
| 139 |
+
# A set of banned flag names. This is to prevent users from accidentally
|
| 140 |
+
# defining a flag that has the same name as a method on this class.
|
| 141 |
+
# Users can still allow defining the flag by passing
|
| 142 |
+
# allow_using_method_names=True in DEFINE_xxx functions.
|
| 143 |
+
self.__dict__['__banned_flag_names'] = frozenset(dir(FlagValues))
|
| 144 |
+
|
| 145 |
+
# Bool: Whether to use GNU style scanning.
|
| 146 |
+
self.__dict__['__use_gnu_getopt'] = True
|
| 147 |
+
|
| 148 |
+
# Bool: Whether use_gnu_getopt has been explicitly set by the user.
|
| 149 |
+
self.__dict__['__use_gnu_getopt_explicitly_set'] = False
|
| 150 |
+
|
| 151 |
+
# Function: Takes a flag name as parameter, returns a tuple
|
| 152 |
+
# (is_retired, type_is_bool).
|
| 153 |
+
self.__dict__['__is_retired_flag_func'] = None
|
| 154 |
+
|
| 155 |
+
def set_gnu_getopt(self, gnu_getopt: bool = True) -> None:
|
| 156 |
+
"""Sets whether or not to use GNU style scanning.
|
| 157 |
+
|
| 158 |
+
GNU style allows mixing of flag and non-flag arguments. See
|
| 159 |
+
http://docs.python.org/library/getopt.html#getopt.gnu_getopt
|
| 160 |
+
|
| 161 |
+
Args:
|
| 162 |
+
gnu_getopt: bool, whether or not to use GNU style scanning.
|
| 163 |
+
"""
|
| 164 |
+
self.__dict__['__use_gnu_getopt'] = gnu_getopt
|
| 165 |
+
self.__dict__['__use_gnu_getopt_explicitly_set'] = True
|
| 166 |
+
|
| 167 |
+
def is_gnu_getopt(self) -> bool:
|
| 168 |
+
return self.__dict__['__use_gnu_getopt']
|
| 169 |
+
|
| 170 |
+
def _flags(self) -> dict[str, Flag]:
|
| 171 |
+
return self.__dict__['__flags']
|
| 172 |
+
|
| 173 |
+
def flags_by_module_dict(self) -> dict[str, list[Flag]]:
|
| 174 |
+
"""Returns the dictionary of module_name -> list of defined flags.
|
| 175 |
+
|
| 176 |
+
Returns:
|
| 177 |
+
A dictionary. Its keys are module names (strings). Its values
|
| 178 |
+
are lists of Flag objects.
|
| 179 |
+
"""
|
| 180 |
+
return self.__dict__['__flags_by_module']
|
| 181 |
+
|
| 182 |
+
def flags_by_module_id_dict(self) -> dict[int, list[Flag]]:
|
| 183 |
+
"""Returns the dictionary of module_id -> list of defined flags.
|
| 184 |
+
|
| 185 |
+
Returns:
|
| 186 |
+
A dictionary. Its keys are module IDs (ints). Its values
|
| 187 |
+
are lists of Flag objects.
|
| 188 |
+
"""
|
| 189 |
+
return self.__dict__['__flags_by_module_id']
|
| 190 |
+
|
| 191 |
+
def key_flags_by_module_dict(self) -> dict[str, list[Flag]]:
|
| 192 |
+
"""Returns the dictionary of module_name -> list of key flags.
|
| 193 |
+
|
| 194 |
+
Returns:
|
| 195 |
+
A dictionary. Its keys are module names (strings). Its values
|
| 196 |
+
are lists of Flag objects.
|
| 197 |
+
"""
|
| 198 |
+
return self.__dict__['__key_flags_by_module']
|
| 199 |
+
|
| 200 |
+
def register_flag_by_module(self, module_name: str, flag: Flag) -> None:
|
| 201 |
+
"""Records the module that defines a specific flag.
|
| 202 |
+
|
| 203 |
+
We keep track of which flag is defined by which module so that we
|
| 204 |
+
can later sort the flags by module.
|
| 205 |
+
|
| 206 |
+
Args:
|
| 207 |
+
module_name: str, the name of a Python module.
|
| 208 |
+
flag: Flag, the Flag instance that is key to the module.
|
| 209 |
+
"""
|
| 210 |
+
flags_by_module = self.flags_by_module_dict()
|
| 211 |
+
flags_by_module.setdefault(module_name, []).append(flag)
|
| 212 |
+
|
| 213 |
+
def register_flag_by_module_id(self, module_id: int, flag: Flag) -> None:
|
| 214 |
+
"""Records the module that defines a specific flag.
|
| 215 |
+
|
| 216 |
+
Args:
|
| 217 |
+
module_id: int, the ID of the Python module.
|
| 218 |
+
flag: Flag, the Flag instance that is key to the module.
|
| 219 |
+
"""
|
| 220 |
+
flags_by_module_id = self.flags_by_module_id_dict()
|
| 221 |
+
flags_by_module_id.setdefault(module_id, []).append(flag)
|
| 222 |
+
|
| 223 |
+
def register_key_flag_for_module(self, module_name: str, flag: Flag) -> None:
|
| 224 |
+
"""Specifies that a flag is a key flag for a module.
|
| 225 |
+
|
| 226 |
+
Args:
|
| 227 |
+
module_name: str, the name of a Python module.
|
| 228 |
+
flag: Flag, the Flag instance that is key to the module.
|
| 229 |
+
"""
|
| 230 |
+
key_flags_by_module = self.key_flags_by_module_dict()
|
| 231 |
+
# The list of key flags for the module named module_name.
|
| 232 |
+
key_flags = key_flags_by_module.setdefault(module_name, [])
|
| 233 |
+
# Add flag, but avoid duplicates.
|
| 234 |
+
if flag not in key_flags:
|
| 235 |
+
key_flags.append(flag)
|
| 236 |
+
|
| 237 |
+
def _flag_is_registered(self, flag_obj: Flag) -> bool:
|
| 238 |
+
"""Checks whether a Flag object is registered under long name or short name.
|
| 239 |
+
|
| 240 |
+
Args:
|
| 241 |
+
flag_obj: Flag, the Flag instance to check for.
|
| 242 |
+
|
| 243 |
+
Returns:
|
| 244 |
+
bool, True iff flag_obj is registered under long name or short name.
|
| 245 |
+
"""
|
| 246 |
+
flag_dict = self._flags()
|
| 247 |
+
# Check whether flag_obj is registered under its long name.
|
| 248 |
+
name = flag_obj.name
|
| 249 |
+
if name in flag_dict and flag_dict[name] == flag_obj:
|
| 250 |
+
return True
|
| 251 |
+
# Check whether flag_obj is registered under its short name.
|
| 252 |
+
short_name = flag_obj.short_name
|
| 253 |
+
if (
|
| 254 |
+
short_name is not None
|
| 255 |
+
and short_name in flag_dict
|
| 256 |
+
and flag_dict[short_name] == flag_obj
|
| 257 |
+
):
|
| 258 |
+
return True
|
| 259 |
+
return False
|
| 260 |
+
|
| 261 |
+
def _cleanup_unregistered_flag_from_module_dicts(
|
| 262 |
+
self, flag_obj: Flag
|
| 263 |
+
) -> None:
|
| 264 |
+
"""Cleans up unregistered flags from all module -> [flags] dictionaries.
|
| 265 |
+
|
| 266 |
+
If flag_obj is registered under either its long name or short name, it
|
| 267 |
+
won't be removed from the dictionaries.
|
| 268 |
+
|
| 269 |
+
Args:
|
| 270 |
+
flag_obj: Flag, the Flag instance to clean up for.
|
| 271 |
+
"""
|
| 272 |
+
if self._flag_is_registered(flag_obj):
|
| 273 |
+
return
|
| 274 |
+
# Materialize dict values to list to avoid concurrent modification.
|
| 275 |
+
for flags_in_module in [
|
| 276 |
+
*self.flags_by_module_dict().values(),
|
| 277 |
+
*self.flags_by_module_id_dict().values(),
|
| 278 |
+
*self.key_flags_by_module_dict().values(),
|
| 279 |
+
]:
|
| 280 |
+
# While (as opposed to if) takes care of multiple occurrences of a
|
| 281 |
+
# flag in the list for the same module.
|
| 282 |
+
while flag_obj in flags_in_module:
|
| 283 |
+
flags_in_module.remove(flag_obj)
|
| 284 |
+
|
| 285 |
+
def get_flags_for_module(self, module: str | Any) -> list[Flag]:
|
| 286 |
+
"""Returns the list of flags defined by a module.
|
| 287 |
+
|
| 288 |
+
Args:
|
| 289 |
+
module: module|str, the module to get flags from.
|
| 290 |
+
|
| 291 |
+
Returns:
|
| 292 |
+
[Flag], a new list of Flag instances. Caller may update this list as
|
| 293 |
+
desired: none of those changes will affect the internals of this
|
| 294 |
+
FlagValue instance.
|
| 295 |
+
"""
|
| 296 |
+
if not isinstance(module, str):
|
| 297 |
+
module = module.__name__
|
| 298 |
+
if module == '__main__':
|
| 299 |
+
module = sys.argv[0]
|
| 300 |
+
|
| 301 |
+
return list(self.flags_by_module_dict().get(module, []))
|
| 302 |
+
|
| 303 |
+
def get_key_flags_for_module(self, module: str | Any) -> list[Flag]:
|
| 304 |
+
"""Returns the list of key flags for a module.
|
| 305 |
+
|
| 306 |
+
Args:
|
| 307 |
+
module: module|str, the module to get key flags from.
|
| 308 |
+
|
| 309 |
+
Returns:
|
| 310 |
+
[Flag], a new list of Flag instances. Caller may update this list as
|
| 311 |
+
desired: none of those changes will affect the internals of this
|
| 312 |
+
FlagValue instance.
|
| 313 |
+
"""
|
| 314 |
+
if not isinstance(module, str):
|
| 315 |
+
module = module.__name__
|
| 316 |
+
if module == '__main__':
|
| 317 |
+
module = sys.argv[0]
|
| 318 |
+
|
| 319 |
+
# Any flag is a key flag for the module that defined it. NOTE:
|
| 320 |
+
# key_flags is a fresh list: we can update it without affecting the
|
| 321 |
+
# internals of this FlagValues object.
|
| 322 |
+
key_flags = self.get_flags_for_module(module)
|
| 323 |
+
|
| 324 |
+
# Take into account flags explicitly declared as key for a module.
|
| 325 |
+
for flag in self.key_flags_by_module_dict().get(module, []):
|
| 326 |
+
if flag not in key_flags:
|
| 327 |
+
key_flags.append(flag)
|
| 328 |
+
return key_flags
|
| 329 |
+
|
| 330 |
+
# TODO(yileiyang): Restrict default to Optional[str].
|
| 331 |
+
def find_module_defining_flag(
|
| 332 |
+
self, flagname: str, default: _T | None = None
|
| 333 |
+
) -> str | _T | None:
|
| 334 |
+
"""Return the name of the module defining this flag, or default.
|
| 335 |
+
|
| 336 |
+
Args:
|
| 337 |
+
flagname: str, name of the flag to lookup.
|
| 338 |
+
default: Value to return if flagname is not defined. Defaults to None.
|
| 339 |
+
|
| 340 |
+
Returns:
|
| 341 |
+
The name of the module which registered the flag with this name.
|
| 342 |
+
If no such module exists (i.e. no flag with this name exists),
|
| 343 |
+
we return default.
|
| 344 |
+
"""
|
| 345 |
+
registered_flag = self._flags().get(flagname)
|
| 346 |
+
if registered_flag is None:
|
| 347 |
+
return default
|
| 348 |
+
for module, flags in self.flags_by_module_dict().items():
|
| 349 |
+
for flag in flags:
|
| 350 |
+
# It must compare the flag with the one in _flags. This is because a
|
| 351 |
+
# flag might be overridden only for its long name (or short name),
|
| 352 |
+
# and only its short name (or long name) is considered registered.
|
| 353 |
+
if (
|
| 354 |
+
flag.name == registered_flag.name
|
| 355 |
+
and flag.short_name == registered_flag.short_name
|
| 356 |
+
):
|
| 357 |
+
return module
|
| 358 |
+
return default
|
| 359 |
+
|
| 360 |
+
# TODO(yileiyang): Restrict default to Optional[str].
|
| 361 |
+
def find_module_id_defining_flag(
|
| 362 |
+
self, flagname: str, default: _T | None = None
|
| 363 |
+
) -> int | _T | None:
|
| 364 |
+
"""Return the ID of the module defining this flag, or default.
|
| 365 |
+
|
| 366 |
+
Args:
|
| 367 |
+
flagname: str, name of the flag to lookup.
|
| 368 |
+
default: Value to return if flagname is not defined. Defaults to None.
|
| 369 |
+
|
| 370 |
+
Returns:
|
| 371 |
+
The ID of the module which registered the flag with this name.
|
| 372 |
+
If no such module exists (i.e. no flag with this name exists),
|
| 373 |
+
we return default.
|
| 374 |
+
"""
|
| 375 |
+
registered_flag = self._flags().get(flagname)
|
| 376 |
+
if registered_flag is None:
|
| 377 |
+
return default
|
| 378 |
+
for module_id, flags in self.flags_by_module_id_dict().items():
|
| 379 |
+
for flag in flags:
|
| 380 |
+
# It must compare the flag with the one in _flags. This is because a
|
| 381 |
+
# flag might be overridden only for its long name (or short name),
|
| 382 |
+
# and only its short name (or long name) is considered registered.
|
| 383 |
+
if (
|
| 384 |
+
flag.name == registered_flag.name
|
| 385 |
+
and flag.short_name == registered_flag.short_name
|
| 386 |
+
):
|
| 387 |
+
return module_id
|
| 388 |
+
return default
|
| 389 |
+
|
| 390 |
+
def _register_unknown_flag_setter(
|
| 391 |
+
self, setter: Callable[[str, Any], None]
|
| 392 |
+
) -> None:
|
| 393 |
+
"""Allow set default values for undefined flags.
|
| 394 |
+
|
| 395 |
+
Args:
|
| 396 |
+
setter: Method(name, value) to call to __setattr__ an unknown flag. Must
|
| 397 |
+
raise NameError or ValueError for invalid name/value.
|
| 398 |
+
"""
|
| 399 |
+
self.__dict__['__set_unknown'] = setter
|
| 400 |
+
|
| 401 |
+
def _set_unknown_flag(self, name: str, value: _T) -> _T:
|
| 402 |
+
"""Returns value if setting flag |name| to |value| returned True.
|
| 403 |
+
|
| 404 |
+
Args:
|
| 405 |
+
name: str, name of the flag to set.
|
| 406 |
+
value: Value to set.
|
| 407 |
+
|
| 408 |
+
Returns:
|
| 409 |
+
Flag value on successful call.
|
| 410 |
+
|
| 411 |
+
Raises:
|
| 412 |
+
UnrecognizedFlagError
|
| 413 |
+
IllegalFlagValueError
|
| 414 |
+
"""
|
| 415 |
+
setter = self.__dict__['__set_unknown']
|
| 416 |
+
if setter:
|
| 417 |
+
try:
|
| 418 |
+
setter(name, value)
|
| 419 |
+
return value
|
| 420 |
+
except (TypeError, ValueError): # Flag value is not valid.
|
| 421 |
+
raise _exceptions.IllegalFlagValueError(
|
| 422 |
+
f'"{value}" is not valid for --{name}'
|
| 423 |
+
)
|
| 424 |
+
except NameError: # Flag name is not valid.
|
| 425 |
+
pass
|
| 426 |
+
raise _exceptions.UnrecognizedFlagError(name, value)
|
| 427 |
+
|
| 428 |
+
def append_flag_values(self, flag_values: 'FlagValues') -> None:
|
| 429 |
+
"""Appends flags registered in another FlagValues instance.
|
| 430 |
+
|
| 431 |
+
Args:
|
| 432 |
+
flag_values: FlagValues, the FlagValues instance from which to copy flags.
|
| 433 |
+
"""
|
| 434 |
+
for flag_name, flag in flag_values._flags().items(): # pylint: disable=protected-access
|
| 435 |
+
# Each flags with short_name appears here twice (once under its
|
| 436 |
+
# normal name, and again with its short name). To prevent
|
| 437 |
+
# problems (DuplicateFlagError) with double flag registration, we
|
| 438 |
+
# perform a check to make sure that the entry we're looking at is
|
| 439 |
+
# for its normal name.
|
| 440 |
+
if flag_name == flag.name:
|
| 441 |
+
try:
|
| 442 |
+
self[flag_name] = flag
|
| 443 |
+
except _exceptions.DuplicateFlagError:
|
| 444 |
+
raise _exceptions.DuplicateFlagError.from_flag(
|
| 445 |
+
flag_name, self, other_flag_values=flag_values
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
def remove_flag_values(
|
| 449 |
+
self, flag_values: 'FlagValues | Iterable[str]'
|
| 450 |
+
) -> None:
|
| 451 |
+
"""Remove flags that were previously appended from another FlagValues.
|
| 452 |
+
|
| 453 |
+
Args:
|
| 454 |
+
flag_values: FlagValues, the FlagValues instance containing flags to
|
| 455 |
+
remove.
|
| 456 |
+
"""
|
| 457 |
+
for flag_name in flag_values:
|
| 458 |
+
self.__delattr__(flag_name)
|
| 459 |
+
|
| 460 |
+
def __setitem__(self, name: str, flag: Flag) -> None:
|
| 461 |
+
"""Registers a new flag variable."""
|
| 462 |
+
fl = self._flags()
|
| 463 |
+
if not isinstance(flag, _flag.Flag):
|
| 464 |
+
raise _exceptions.IllegalFlagValueError(
|
| 465 |
+
f'Expect Flag instances, found type {type(flag)}. '
|
| 466 |
+
"Maybe you didn't mean to use FlagValue.__setitem__?"
|
| 467 |
+
)
|
| 468 |
+
if not isinstance(name, str):
|
| 469 |
+
raise _exceptions.Error('Flag name must be a string')
|
| 470 |
+
if not name:
|
| 471 |
+
raise _exceptions.Error('Flag name cannot be empty')
|
| 472 |
+
if ' ' in name:
|
| 473 |
+
raise _exceptions.Error('Flag name cannot contain a space')
|
| 474 |
+
self._check_method_name_conflicts(name, flag)
|
| 475 |
+
if name in fl and not flag.allow_override and not fl[name].allow_override:
|
| 476 |
+
module, module_name = _helpers.get_calling_module_object_and_name()
|
| 477 |
+
if self.find_module_defining_flag(name) == module_name and (
|
| 478 |
+
id(module) != self.find_module_id_defining_flag(name)
|
| 479 |
+
or module_name in reload_detector.reloading_modules
|
| 480 |
+
):
|
| 481 |
+
# If the flag has already been defined by a module with the same name,
|
| 482 |
+
# but a different ID, we can stop here because it indicates that the
|
| 483 |
+
# module is simply being imported a subsequent time.
|
| 484 |
+
# In case the module is being reloaded (using `importlib.reload`), it'll
|
| 485 |
+
# have the same ID, so we detect it using reload_detector.
|
| 486 |
+
return
|
| 487 |
+
raise _exceptions.DuplicateFlagError.from_flag(name, self)
|
| 488 |
+
# If a new flag overrides an old one, we need to cleanup the old flag's
|
| 489 |
+
# modules if it's not registered.
|
| 490 |
+
flags_to_cleanup = set()
|
| 491 |
+
short_name: str | None = flag.short_name
|
| 492 |
+
if short_name is not None:
|
| 493 |
+
if (
|
| 494 |
+
short_name in fl
|
| 495 |
+
and not flag.allow_override
|
| 496 |
+
and not fl[short_name].allow_override
|
| 497 |
+
):
|
| 498 |
+
raise _exceptions.DuplicateFlagError.from_flag(short_name, self)
|
| 499 |
+
if short_name in fl and fl[short_name] != flag:
|
| 500 |
+
flags_to_cleanup.add(fl[short_name])
|
| 501 |
+
fl[short_name] = flag
|
| 502 |
+
if (
|
| 503 |
+
name not in fl # new flag
|
| 504 |
+
or fl[name].using_default_value
|
| 505 |
+
or not flag.using_default_value
|
| 506 |
+
):
|
| 507 |
+
if name in fl and fl[name] != flag:
|
| 508 |
+
flags_to_cleanup.add(fl[name])
|
| 509 |
+
fl[name] = flag
|
| 510 |
+
for f in flags_to_cleanup:
|
| 511 |
+
self._cleanup_unregistered_flag_from_module_dicts(f)
|
| 512 |
+
|
| 513 |
+
def __dir__(self) -> list[str]:
|
| 514 |
+
"""Returns list of names of all defined flags.
|
| 515 |
+
|
| 516 |
+
Useful for TAB-completion in ipython.
|
| 517 |
+
|
| 518 |
+
Returns:
|
| 519 |
+
[str], a list of names of all defined flags.
|
| 520 |
+
"""
|
| 521 |
+
return sorted(self._flags())
|
| 522 |
+
|
| 523 |
+
def __getitem__(self, name: str) -> Flag:
|
| 524 |
+
"""Returns the Flag object for the flag --name."""
|
| 525 |
+
return self._flags()[name]
|
| 526 |
+
|
| 527 |
+
def _hide_flag(self, name):
|
| 528 |
+
"""Marks the flag --name as hidden."""
|
| 529 |
+
self.__dict__['__hiddenflags'].add(name)
|
| 530 |
+
|
| 531 |
+
def __getattr__(self, name: str) -> Any:
|
| 532 |
+
"""Retrieves the 'value' attribute of the flag --name."""
|
| 533 |
+
flag_entry = self._flags().get(name)
|
| 534 |
+
if flag_entry is None:
|
| 535 |
+
raise AttributeError(name)
|
| 536 |
+
if name in self.__dict__['__hiddenflags']:
|
| 537 |
+
raise AttributeError(name)
|
| 538 |
+
|
| 539 |
+
if self.__dict__['__flags_parsed'] or flag_entry.present:
|
| 540 |
+
return flag_entry.value
|
| 541 |
+
else:
|
| 542 |
+
raise _exceptions.UnparsedFlagAccessError(
|
| 543 |
+
'Trying to access flag --%s before flags were parsed.' % name
|
| 544 |
+
)
|
| 545 |
+
|
| 546 |
+
def __setattr__(self, name: str, value: _T) -> _T:
|
| 547 |
+
"""Sets the 'value' attribute of the flag --name."""
|
| 548 |
+
self._set_attributes(**{name: value})
|
| 549 |
+
return value
|
| 550 |
+
|
| 551 |
+
def _set_attributes(self, **attributes: Any) -> None:
|
| 552 |
+
"""Sets multiple flag values together, triggers validators afterwards."""
|
| 553 |
+
fl = self._flags()
|
| 554 |
+
known_flag_vals = {}
|
| 555 |
+
known_flag_used_defaults = {}
|
| 556 |
+
try:
|
| 557 |
+
for name, value in attributes.items():
|
| 558 |
+
if name in self.__dict__['__hiddenflags']:
|
| 559 |
+
raise AttributeError(name)
|
| 560 |
+
flag_entry = fl.get(name)
|
| 561 |
+
if flag_entry is not None:
|
| 562 |
+
orig = flag_entry.value
|
| 563 |
+
flag_entry.value = value
|
| 564 |
+
known_flag_vals[name] = orig
|
| 565 |
+
else:
|
| 566 |
+
self._set_unknown_flag(name, value)
|
| 567 |
+
for name in known_flag_vals:
|
| 568 |
+
self._assert_validators(fl[name].validators)
|
| 569 |
+
known_flag_used_defaults[name] = fl[name].using_default_value
|
| 570 |
+
fl[name].using_default_value = False
|
| 571 |
+
except:
|
| 572 |
+
for name, orig in known_flag_vals.items():
|
| 573 |
+
fl[name].value = orig
|
| 574 |
+
for name, orig in known_flag_used_defaults.items():
|
| 575 |
+
fl[name].using_default_value = orig
|
| 576 |
+
# NOTE: We do not attempt to undo unknown flag side effects because we
|
| 577 |
+
# cannot reliably undo the user-configured behavior.
|
| 578 |
+
raise
|
| 579 |
+
|
| 580 |
+
def validate_all_flags(self) -> None:
|
| 581 |
+
"""Verifies whether all flags pass validation.
|
| 582 |
+
|
| 583 |
+
Raises:
|
| 584 |
+
AttributeError: Raised if validators work with a non-existing flag.
|
| 585 |
+
IllegalFlagValueError: Raised if validation fails for at least one
|
| 586 |
+
validator.
|
| 587 |
+
"""
|
| 588 |
+
all_validators = set()
|
| 589 |
+
for flag in self._flags().values():
|
| 590 |
+
all_validators.update(flag.validators)
|
| 591 |
+
self._assert_validators(all_validators)
|
| 592 |
+
|
| 593 |
+
def _assert_validators(
|
| 594 |
+
self, validators: Iterable[_validators_classes.Validator]
|
| 595 |
+
) -> None:
|
| 596 |
+
"""Asserts if all validators in the list are satisfied.
|
| 597 |
+
|
| 598 |
+
It asserts validators in the order they were created.
|
| 599 |
+
|
| 600 |
+
Args:
|
| 601 |
+
validators: Iterable(validators.Validator), validators to be verified.
|
| 602 |
+
|
| 603 |
+
Raises:
|
| 604 |
+
AttributeError: Raised if validators work with a non-existing flag.
|
| 605 |
+
IllegalFlagValueError: Raised if validation fails for at least one
|
| 606 |
+
validator.
|
| 607 |
+
"""
|
| 608 |
+
messages = []
|
| 609 |
+
bad_flags: set[str] = set()
|
| 610 |
+
for validator in sorted(
|
| 611 |
+
validators, key=lambda validator: validator.insertion_index
|
| 612 |
+
):
|
| 613 |
+
try:
|
| 614 |
+
if isinstance(validator, _validators_classes.SingleFlagValidator):
|
| 615 |
+
if validator.flag_name in bad_flags:
|
| 616 |
+
continue
|
| 617 |
+
elif isinstance(validator, _validators_classes.MultiFlagsValidator):
|
| 618 |
+
if bad_flags & set(validator.flag_names):
|
| 619 |
+
continue
|
| 620 |
+
validator.verify(self)
|
| 621 |
+
except _exceptions.ValidationError as e:
|
| 622 |
+
if isinstance(validator, _validators_classes.SingleFlagValidator):
|
| 623 |
+
bad_flags.add(validator.flag_name)
|
| 624 |
+
elif isinstance(validator, _validators_classes.MultiFlagsValidator):
|
| 625 |
+
bad_flags.update(set(validator.flag_names))
|
| 626 |
+
message = validator.print_flags_with_values(self)
|
| 627 |
+
messages.append('%s: %s' % (message, str(e)))
|
| 628 |
+
if messages:
|
| 629 |
+
raise _exceptions.IllegalFlagValueError('\n'.join(messages))
|
| 630 |
+
|
| 631 |
+
def __delattr__(self, flag_name: str) -> None:
|
| 632 |
+
"""Deletes a previously-defined flag from a flag object.
|
| 633 |
+
|
| 634 |
+
This method makes sure we can delete a flag by using
|
| 635 |
+
|
| 636 |
+
del FLAGS.<flag_name>
|
| 637 |
+
|
| 638 |
+
E.g.,
|
| 639 |
+
|
| 640 |
+
flags.DEFINE_integer('foo', 1, 'Integer flag.')
|
| 641 |
+
del flags.FLAGS.foo
|
| 642 |
+
|
| 643 |
+
If a flag is also registered by its the other name (long name or short
|
| 644 |
+
name), the other name won't be deleted.
|
| 645 |
+
|
| 646 |
+
Args:
|
| 647 |
+
flag_name: str, the name of the flag to be deleted.
|
| 648 |
+
|
| 649 |
+
Raises:
|
| 650 |
+
AttributeError: Raised when there is no registered flag named flag_name.
|
| 651 |
+
"""
|
| 652 |
+
fl = self._flags()
|
| 653 |
+
flag_entry = fl.get(flag_name)
|
| 654 |
+
if flag_entry is None:
|
| 655 |
+
raise AttributeError(flag_name)
|
| 656 |
+
del fl[flag_name]
|
| 657 |
+
|
| 658 |
+
self._cleanup_unregistered_flag_from_module_dicts(flag_entry)
|
| 659 |
+
|
| 660 |
+
def set_default(self, name: str, value: Any) -> None:
|
| 661 |
+
"""Changes the default value of the named flag object.
|
| 662 |
+
|
| 663 |
+
The flag's current value is also updated if the flag is currently using
|
| 664 |
+
the default value, i.e. not specified in the command line, and not set
|
| 665 |
+
by FLAGS.name = value.
|
| 666 |
+
|
| 667 |
+
Args:
|
| 668 |
+
name: str, the name of the flag to modify.
|
| 669 |
+
value: The new default value.
|
| 670 |
+
|
| 671 |
+
Raises:
|
| 672 |
+
UnrecognizedFlagError: Raised when there is no registered flag named name.
|
| 673 |
+
IllegalFlagValueError: Raised when value is not valid.
|
| 674 |
+
"""
|
| 675 |
+
fl = self._flags()
|
| 676 |
+
flag_entry = fl.get(name)
|
| 677 |
+
if flag_entry is None:
|
| 678 |
+
self._set_unknown_flag(name, value)
|
| 679 |
+
return
|
| 680 |
+
flag_entry._set_default(value) # pylint: disable=protected-access
|
| 681 |
+
self._assert_validators(flag_entry.validators)
|
| 682 |
+
|
| 683 |
+
def __contains__(self, name: str) -> bool:
|
| 684 |
+
"""Returns True if name is a value (flag) in the dict."""
|
| 685 |
+
return name in self._flags()
|
| 686 |
+
|
| 687 |
+
def __len__(self) -> int:
|
| 688 |
+
return len(self.__dict__['__flags'])
|
| 689 |
+
|
| 690 |
+
def __iter__(self) -> Iterator[str]:
|
| 691 |
+
return iter(self._flags())
|
| 692 |
+
|
| 693 |
+
def __call__(
|
| 694 |
+
self, argv: Sequence[str], known_only: bool = False
|
| 695 |
+
) -> list[str]:
|
| 696 |
+
"""Parses flags from argv; stores parsed flags into this FlagValues object.
|
| 697 |
+
|
| 698 |
+
All unparsed arguments are returned.
|
| 699 |
+
|
| 700 |
+
Args:
|
| 701 |
+
argv: a tuple/list of strings.
|
| 702 |
+
known_only: bool, if True, parse and remove known flags; return the rest
|
| 703 |
+
untouched. Unknown flags specified by --undefok are not returned.
|
| 704 |
+
|
| 705 |
+
Returns:
|
| 706 |
+
The list of arguments not parsed as options, including argv[0].
|
| 707 |
+
|
| 708 |
+
Raises:
|
| 709 |
+
Error: Raised on any parsing error.
|
| 710 |
+
TypeError: Raised on passing wrong type of arguments.
|
| 711 |
+
ValueError: Raised on flag value parsing error.
|
| 712 |
+
"""
|
| 713 |
+
if isinstance(argv, (str, bytes)):
|
| 714 |
+
raise TypeError(
|
| 715 |
+
'argv should be a tuple/list of strings, not bytes or string.'
|
| 716 |
+
)
|
| 717 |
+
if not argv:
|
| 718 |
+
raise ValueError(
|
| 719 |
+
'argv cannot be an empty list, and must contain the program name as '
|
| 720 |
+
'the first element.'
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
# This pre parses the argv list for --flagfile=<> options.
|
| 724 |
+
program_name = argv[0]
|
| 725 |
+
args = self.read_flags_from_files(argv[1:], force_gnu=False)
|
| 726 |
+
|
| 727 |
+
# Parse the arguments.
|
| 728 |
+
unknown_flags, unparsed_args = self._parse_args(args, known_only)
|
| 729 |
+
|
| 730 |
+
# Handle unknown flags by raising UnrecognizedFlagError.
|
| 731 |
+
# Note some users depend on us raising this particular error.
|
| 732 |
+
for name, value in unknown_flags:
|
| 733 |
+
suggestions = _helpers.get_flag_suggestions(name, list(self))
|
| 734 |
+
raise _exceptions.UnrecognizedFlagError(
|
| 735 |
+
name, value, suggestions=suggestions
|
| 736 |
+
)
|
| 737 |
+
|
| 738 |
+
self.mark_as_parsed()
|
| 739 |
+
self.validate_all_flags()
|
| 740 |
+
return [program_name] + unparsed_args
|
| 741 |
+
|
| 742 |
+
def __getstate__(self) -> Any:
|
| 743 |
+
raise TypeError("can't pickle FlagValues")
|
| 744 |
+
|
| 745 |
+
def __copy__(self) -> Any:
|
| 746 |
+
raise TypeError(
|
| 747 |
+
'FlagValues does not support shallow copies. '
|
| 748 |
+
'Use absl.testing.flagsaver or copy.deepcopy instead.'
|
| 749 |
+
)
|
| 750 |
+
|
| 751 |
+
def __deepcopy__(self, memo) -> Any:
|
| 752 |
+
result = object.__new__(type(self))
|
| 753 |
+
result.__dict__.update(copy.deepcopy(self.__dict__, memo))
|
| 754 |
+
return result
|
| 755 |
+
|
| 756 |
+
def _set_is_retired_flag_func(self, is_retired_flag_func):
|
| 757 |
+
"""Sets a function for checking retired flags.
|
| 758 |
+
|
| 759 |
+
Do not use it. This is a private absl API used to check retired flags
|
| 760 |
+
registered by the absl C++ flags library.
|
| 761 |
+
|
| 762 |
+
Args:
|
| 763 |
+
is_retired_flag_func: Callable(str) -> (bool, bool), a function takes flag
|
| 764 |
+
name as parameter, returns a tuple (is_retired, type_is_bool).
|
| 765 |
+
"""
|
| 766 |
+
self.__dict__['__is_retired_flag_func'] = is_retired_flag_func
|
| 767 |
+
|
| 768 |
+
def _parse_args(
|
| 769 |
+
self, args: list[str], known_only: bool
|
| 770 |
+
) -> tuple[list[tuple[str, Any]], list[str]]:
|
| 771 |
+
"""Helper function to do the main argument parsing.
|
| 772 |
+
|
| 773 |
+
This function goes through args and does the bulk of the flag parsing.
|
| 774 |
+
It will find the corresponding flag in our flag dictionary, and call its
|
| 775 |
+
.parse() method on the flag value.
|
| 776 |
+
|
| 777 |
+
Args:
|
| 778 |
+
args: [str], a list of strings with the arguments to parse.
|
| 779 |
+
known_only: bool, if True, parse and remove known flags; return the rest
|
| 780 |
+
untouched. Unknown flags specified by --undefok are not returned.
|
| 781 |
+
|
| 782 |
+
Returns:
|
| 783 |
+
A tuple with the following:
|
| 784 |
+
unknown_flags: List of (flag name, arg) for flags we don't know about.
|
| 785 |
+
unparsed_args: List of arguments we did not parse.
|
| 786 |
+
|
| 787 |
+
Raises:
|
| 788 |
+
Error: Raised on any parsing error.
|
| 789 |
+
ValueError: Raised on flag value parsing error.
|
| 790 |
+
"""
|
| 791 |
+
unparsed_names_and_args: list[tuple[str | None, str]] = []
|
| 792 |
+
undefok: set[str] = set()
|
| 793 |
+
retired_flag_func = self.__dict__['__is_retired_flag_func']
|
| 794 |
+
|
| 795 |
+
flag_dict = self._flags()
|
| 796 |
+
args_it = iter(args)
|
| 797 |
+
del args
|
| 798 |
+
for arg in args_it:
|
| 799 |
+
value = None
|
| 800 |
+
|
| 801 |
+
def get_value() -> str:
|
| 802 |
+
try:
|
| 803 |
+
return next(args_it) if value is None else value # pylint: disable=cell-var-from-loop
|
| 804 |
+
except StopIteration:
|
| 805 |
+
raise _exceptions.Error('Missing value for flag ' + arg) from None # pylint: disable=cell-var-from-loop
|
| 806 |
+
|
| 807 |
+
if not arg.startswith('-'):
|
| 808 |
+
# A non-argument: default is break, GNU is skip.
|
| 809 |
+
unparsed_names_and_args.append((None, arg))
|
| 810 |
+
if self.is_gnu_getopt():
|
| 811 |
+
continue
|
| 812 |
+
else:
|
| 813 |
+
break
|
| 814 |
+
|
| 815 |
+
if arg == '--':
|
| 816 |
+
if known_only:
|
| 817 |
+
unparsed_names_and_args.append((None, arg))
|
| 818 |
+
break
|
| 819 |
+
|
| 820 |
+
# At this point, arg must start with '-'.
|
| 821 |
+
if arg.startswith('--'):
|
| 822 |
+
arg_without_dashes = arg[2:]
|
| 823 |
+
else:
|
| 824 |
+
arg_without_dashes = arg[1:]
|
| 825 |
+
|
| 826 |
+
if '=' in arg_without_dashes:
|
| 827 |
+
name, value = arg_without_dashes.split('=', 1)
|
| 828 |
+
else:
|
| 829 |
+
name, value = arg_without_dashes, None
|
| 830 |
+
|
| 831 |
+
if not name:
|
| 832 |
+
# The argument is all dashes (including one dash).
|
| 833 |
+
unparsed_names_and_args.append((None, arg))
|
| 834 |
+
if self.is_gnu_getopt():
|
| 835 |
+
continue
|
| 836 |
+
else:
|
| 837 |
+
break
|
| 838 |
+
|
| 839 |
+
# --undefok is a special case.
|
| 840 |
+
if name == 'undefok':
|
| 841 |
+
value = get_value()
|
| 842 |
+
undefok.update(v.strip() for v in value.split(','))
|
| 843 |
+
undefok.update('no' + v.strip() for v in value.split(','))
|
| 844 |
+
continue
|
| 845 |
+
|
| 846 |
+
flag = flag_dict.get(name)
|
| 847 |
+
if flag is not None:
|
| 848 |
+
if flag.boolean and value is None:
|
| 849 |
+
value = 'true'
|
| 850 |
+
else:
|
| 851 |
+
value = get_value()
|
| 852 |
+
elif name.startswith('no') and len(name) > 2:
|
| 853 |
+
# Boolean flags can take the form of --noflag, with no value.
|
| 854 |
+
noflag = flag_dict.get(name[2:])
|
| 855 |
+
if noflag is not None and noflag.boolean:
|
| 856 |
+
if value is not None:
|
| 857 |
+
raise ValueError(arg + ' does not take an argument')
|
| 858 |
+
flag = noflag
|
| 859 |
+
value = 'false'
|
| 860 |
+
|
| 861 |
+
if retired_flag_func and flag is None:
|
| 862 |
+
is_retired, is_bool = retired_flag_func(name)
|
| 863 |
+
|
| 864 |
+
# If we didn't recognize that flag, but it starts with
|
| 865 |
+
# "no" then maybe it was a boolean flag specified in the
|
| 866 |
+
# --nofoo form.
|
| 867 |
+
if not is_retired and name.startswith('no'):
|
| 868 |
+
is_retired, is_bool = retired_flag_func(name[2:])
|
| 869 |
+
is_retired = is_retired and is_bool
|
| 870 |
+
|
| 871 |
+
if is_retired:
|
| 872 |
+
if not is_bool and value is None:
|
| 873 |
+
# This happens when a non-bool retired flag is specified
|
| 874 |
+
# in format of "--flag value".
|
| 875 |
+
get_value()
|
| 876 |
+
logging.error(
|
| 877 |
+
'Flag "%s" is retired and should no longer be specified. See '
|
| 878 |
+
'https://abseil.io/tips/90.',
|
| 879 |
+
name,
|
| 880 |
+
)
|
| 881 |
+
continue
|
| 882 |
+
|
| 883 |
+
if flag is not None:
|
| 884 |
+
# LINT.IfChange
|
| 885 |
+
flag.parse(value)
|
| 886 |
+
flag.using_default_value = False
|
| 887 |
+
# LINT.ThenChange(../testing/flagsaver.py:flag_override_parsing)
|
| 888 |
+
else:
|
| 889 |
+
unparsed_names_and_args.append((name, arg))
|
| 890 |
+
|
| 891 |
+
unknown_flags = []
|
| 892 |
+
unparsed_args = []
|
| 893 |
+
for arg_name, arg in unparsed_names_and_args:
|
| 894 |
+
if arg_name is None:
|
| 895 |
+
# Positional arguments.
|
| 896 |
+
unparsed_args.append(arg)
|
| 897 |
+
elif arg_name in undefok:
|
| 898 |
+
# Remove undefok flags.
|
| 899 |
+
continue
|
| 900 |
+
else:
|
| 901 |
+
# This is an unknown flag.
|
| 902 |
+
if known_only:
|
| 903 |
+
unparsed_args.append(arg)
|
| 904 |
+
else:
|
| 905 |
+
unknown_flags.append((arg_name, arg))
|
| 906 |
+
|
| 907 |
+
unparsed_args.extend(list(args_it))
|
| 908 |
+
return unknown_flags, unparsed_args
|
| 909 |
+
|
| 910 |
+
def is_parsed(self) -> bool:
|
| 911 |
+
"""Returns whether flags were parsed."""
|
| 912 |
+
return self.__dict__['__flags_parsed']
|
| 913 |
+
|
| 914 |
+
def mark_as_parsed(self) -> None:
|
| 915 |
+
"""Explicitly marks flags as parsed.
|
| 916 |
+
|
| 917 |
+
Use this when the caller knows that this FlagValues has been parsed as if
|
| 918 |
+
a ``__call__()`` invocation has happened. This is only a public method for
|
| 919 |
+
use by things like appcommands which do additional command like parsing.
|
| 920 |
+
"""
|
| 921 |
+
self.__dict__['__flags_parsed'] = True
|
| 922 |
+
|
| 923 |
+
def unparse_flags(self) -> None:
|
| 924 |
+
"""Unparses all flags to the point before any FLAGS(argv) was called."""
|
| 925 |
+
for f in self._flags().values():
|
| 926 |
+
f.unparse()
|
| 927 |
+
# We log this message before marking flags as unparsed to avoid a
|
| 928 |
+
# problem when the logging library causes flags access.
|
| 929 |
+
logging.info('unparse_flags() called; flags access will now raise errors.')
|
| 930 |
+
self.__dict__['__flags_parsed'] = False
|
| 931 |
+
self.__dict__['__unparse_flags_called'] = True
|
| 932 |
+
|
| 933 |
+
def flag_values_dict(self) -> dict[str, Any]:
|
| 934 |
+
"""Returns a dictionary that maps flag names to flag values."""
|
| 935 |
+
return {name: flag.value for name, flag in list(self._flags().items())}
|
| 936 |
+
|
| 937 |
+
def __str__(self):
|
| 938 |
+
"""Returns a help string for all known flags."""
|
| 939 |
+
return self.get_help()
|
| 940 |
+
|
| 941 |
+
def get_help(
|
| 942 |
+
self, prefix: str = '', include_special_flags: bool = True
|
| 943 |
+
) -> str:
|
| 944 |
+
"""Returns a help string for all known flags.
|
| 945 |
+
|
| 946 |
+
Args:
|
| 947 |
+
prefix: str, per-line output prefix.
|
| 948 |
+
include_special_flags: bool, whether to include description of
|
| 949 |
+
SPECIAL_FLAGS, i.e. --flagfile and --undefok.
|
| 950 |
+
|
| 951 |
+
Returns:
|
| 952 |
+
str, formatted help message.
|
| 953 |
+
"""
|
| 954 |
+
flags_by_module = self.flags_by_module_dict()
|
| 955 |
+
if flags_by_module:
|
| 956 |
+
modules = sorted(flags_by_module)
|
| 957 |
+
# Print the help for the main module first, if possible.
|
| 958 |
+
main_module = sys.argv[0]
|
| 959 |
+
if main_module in modules:
|
| 960 |
+
modules.remove(main_module)
|
| 961 |
+
modules = [main_module] + modules
|
| 962 |
+
return self._get_help_for_modules(modules, prefix, include_special_flags)
|
| 963 |
+
else:
|
| 964 |
+
output_lines: list[str] = []
|
| 965 |
+
# Just print one long list of flags.
|
| 966 |
+
values = list(self._flags().values())
|
| 967 |
+
if include_special_flags:
|
| 968 |
+
values.extend(_helpers.SPECIAL_FLAGS._flags().values()) # pylint: disable=protected-access
|
| 969 |
+
self._render_flag_list(values, output_lines, prefix)
|
| 970 |
+
return '\n'.join(output_lines)
|
| 971 |
+
|
| 972 |
+
def _get_help_for_modules(self, modules, prefix, include_special_flags):
|
| 973 |
+
"""Returns the help string for a list of modules.
|
| 974 |
+
|
| 975 |
+
Private to absl.flags package.
|
| 976 |
+
|
| 977 |
+
Args:
|
| 978 |
+
modules: List[str], a list of modules to get the help string for.
|
| 979 |
+
prefix: str, a string that is prepended to each generated help line.
|
| 980 |
+
include_special_flags: bool, whether to include description of
|
| 981 |
+
SPECIAL_FLAGS, i.e. --flagfile and --undefok.
|
| 982 |
+
"""
|
| 983 |
+
output_lines = []
|
| 984 |
+
for module in modules:
|
| 985 |
+
self._render_our_module_flags(module, output_lines, prefix)
|
| 986 |
+
if include_special_flags:
|
| 987 |
+
self._render_module_flags(
|
| 988 |
+
'absl.flags',
|
| 989 |
+
_helpers.SPECIAL_FLAGS._flags().values(), # pylint: disable=protected-access # pytype: disable=attribute-error
|
| 990 |
+
output_lines,
|
| 991 |
+
prefix,
|
| 992 |
+
)
|
| 993 |
+
return '\n'.join(output_lines)
|
| 994 |
+
|
| 995 |
+
def _render_module_flags(self, module, flags, output_lines, prefix=''):
|
| 996 |
+
"""Returns a help string for a given module."""
|
| 997 |
+
if not isinstance(module, str):
|
| 998 |
+
module = module.__name__
|
| 999 |
+
output_lines.append('\n%s%s:' % (prefix, module))
|
| 1000 |
+
self._render_flag_list(flags, output_lines, prefix + ' ')
|
| 1001 |
+
|
| 1002 |
+
def _render_our_module_flags(self, module, output_lines, prefix=''):
|
| 1003 |
+
"""Returns a help string for a given module."""
|
| 1004 |
+
flags = self.get_flags_for_module(module)
|
| 1005 |
+
if flags:
|
| 1006 |
+
self._render_module_flags(module, flags, output_lines, prefix)
|
| 1007 |
+
|
| 1008 |
+
def _render_our_module_key_flags(self, module, output_lines, prefix=''):
|
| 1009 |
+
"""Returns a help string for the key flags of a given module.
|
| 1010 |
+
|
| 1011 |
+
Args:
|
| 1012 |
+
module: module|str, the module to render key flags for.
|
| 1013 |
+
output_lines: [str], a list of strings. The generated help message lines
|
| 1014 |
+
will be appended to this list.
|
| 1015 |
+
prefix: str, a string that is prepended to each generated help line.
|
| 1016 |
+
"""
|
| 1017 |
+
key_flags = self.get_key_flags_for_module(module)
|
| 1018 |
+
if key_flags:
|
| 1019 |
+
self._render_module_flags(module, key_flags, output_lines, prefix)
|
| 1020 |
+
|
| 1021 |
+
def module_help(self, module: Any) -> str:
|
| 1022 |
+
"""Describes the key flags of a module.
|
| 1023 |
+
|
| 1024 |
+
Args:
|
| 1025 |
+
module: module|str, the module to describe the key flags for.
|
| 1026 |
+
|
| 1027 |
+
Returns:
|
| 1028 |
+
str, describing the key flags of a module.
|
| 1029 |
+
"""
|
| 1030 |
+
helplist: list[str] = []
|
| 1031 |
+
self._render_our_module_key_flags(module, helplist)
|
| 1032 |
+
return '\n'.join(helplist)
|
| 1033 |
+
|
| 1034 |
+
def main_module_help(self) -> str:
|
| 1035 |
+
"""Describes the key flags of the main module.
|
| 1036 |
+
|
| 1037 |
+
Returns:
|
| 1038 |
+
str, describing the key flags of the main module.
|
| 1039 |
+
"""
|
| 1040 |
+
return self.module_help(sys.argv[0])
|
| 1041 |
+
|
| 1042 |
+
def _render_flag_list(self, flaglist, output_lines, prefix=' '):
|
| 1043 |
+
fl = self._flags()
|
| 1044 |
+
special_fl = _helpers.SPECIAL_FLAGS._flags() # pylint: disable=protected-access # pytype: disable=attribute-error
|
| 1045 |
+
flaglist = [(flag.name, flag) for flag in flaglist]
|
| 1046 |
+
flaglist.sort()
|
| 1047 |
+
flagset = {}
|
| 1048 |
+
for name, flag in flaglist:
|
| 1049 |
+
# It's possible this flag got deleted or overridden since being
|
| 1050 |
+
# registered in the per-module flaglist. Check now against the
|
| 1051 |
+
# canonical source of current flag information, the _flags.
|
| 1052 |
+
if fl.get(name, None) != flag and special_fl.get(name, None) != flag:
|
| 1053 |
+
# a different flag is using this name now
|
| 1054 |
+
continue
|
| 1055 |
+
# only print help once
|
| 1056 |
+
if flag in flagset:
|
| 1057 |
+
continue
|
| 1058 |
+
flagset[flag] = 1
|
| 1059 |
+
flaghelp = ''
|
| 1060 |
+
if flag.short_name:
|
| 1061 |
+
flaghelp += '-%s,' % flag.short_name
|
| 1062 |
+
if flag.boolean:
|
| 1063 |
+
flaghelp += '--[no]%s:' % flag.name
|
| 1064 |
+
else:
|
| 1065 |
+
flaghelp += '--%s:' % flag.name
|
| 1066 |
+
flaghelp += ' '
|
| 1067 |
+
if flag.help:
|
| 1068 |
+
flaghelp += flag.help
|
| 1069 |
+
flaghelp = _helpers.text_wrap(
|
| 1070 |
+
flaghelp, indent=prefix + ' ', firstline_indent=prefix
|
| 1071 |
+
)
|
| 1072 |
+
if flag.default_as_str:
|
| 1073 |
+
flaghelp += '\n'
|
| 1074 |
+
flaghelp += _helpers.text_wrap(
|
| 1075 |
+
'(default: %s)' % flag.default_as_str, indent=prefix + ' '
|
| 1076 |
+
)
|
| 1077 |
+
if flag.parser.syntactic_help:
|
| 1078 |
+
flaghelp += '\n'
|
| 1079 |
+
flaghelp += _helpers.text_wrap(
|
| 1080 |
+
'(%s)' % flag.parser.syntactic_help, indent=prefix + ' '
|
| 1081 |
+
)
|
| 1082 |
+
output_lines.append(flaghelp)
|
| 1083 |
+
|
| 1084 |
+
def get_flag_value(self, name: str, default: Any) -> Any: # pylint: disable=invalid-name
|
| 1085 |
+
"""Returns the value of a flag (if not None) or a default value.
|
| 1086 |
+
|
| 1087 |
+
Args:
|
| 1088 |
+
name: str, the name of a flag.
|
| 1089 |
+
default: Default value to use if the flag value is None.
|
| 1090 |
+
|
| 1091 |
+
Returns:
|
| 1092 |
+
Requested flag value or default.
|
| 1093 |
+
"""
|
| 1094 |
+
|
| 1095 |
+
value = self.__getattr__(name)
|
| 1096 |
+
if value is not None: # Can't do if not value, b/c value might be '0' or ""
|
| 1097 |
+
return value
|
| 1098 |
+
else:
|
| 1099 |
+
return default
|
| 1100 |
+
|
| 1101 |
+
def _is_flag_file_directive(self, flag_string):
|
| 1102 |
+
"""Checks whether flag_string contain a --flagfile=<foo> directive."""
|
| 1103 |
+
if isinstance(flag_string, str):
|
| 1104 |
+
if flag_string.startswith('--flagfile='):
|
| 1105 |
+
return 1
|
| 1106 |
+
elif flag_string == '--flagfile':
|
| 1107 |
+
return 1
|
| 1108 |
+
elif flag_string.startswith('-flagfile='):
|
| 1109 |
+
return 1
|
| 1110 |
+
elif flag_string == '-flagfile':
|
| 1111 |
+
return 1
|
| 1112 |
+
else:
|
| 1113 |
+
return 0
|
| 1114 |
+
return 0
|
| 1115 |
+
|
| 1116 |
+
def _extract_filename(self, flagfile_str):
|
| 1117 |
+
"""Returns filename from a flagfile_str of form -[-]flagfile=filename.
|
| 1118 |
+
|
| 1119 |
+
The cases of --flagfile foo and -flagfile foo shouldn't be hitting
|
| 1120 |
+
this function, as they are dealt with in the level above this
|
| 1121 |
+
function.
|
| 1122 |
+
|
| 1123 |
+
Args:
|
| 1124 |
+
flagfile_str: str, the flagfile string.
|
| 1125 |
+
|
| 1126 |
+
Returns:
|
| 1127 |
+
str, the filename from a flagfile_str of form -[-]flagfile=filename.
|
| 1128 |
+
|
| 1129 |
+
Raises:
|
| 1130 |
+
Error: Raised when illegal --flagfile is provided.
|
| 1131 |
+
"""
|
| 1132 |
+
if flagfile_str.startswith('--flagfile='):
|
| 1133 |
+
return os.path.expanduser((flagfile_str[(len('--flagfile=')) :]).strip())
|
| 1134 |
+
elif flagfile_str.startswith('-flagfile='):
|
| 1135 |
+
return os.path.expanduser((flagfile_str[(len('-flagfile=')) :]).strip())
|
| 1136 |
+
else:
|
| 1137 |
+
raise _exceptions.Error('Hit illegal --flagfile type: %s' % flagfile_str)
|
| 1138 |
+
|
| 1139 |
+
def _get_flag_file_lines(self, filename, parsed_file_stack=None):
|
| 1140 |
+
"""Returns the useful (!=comments, etc) lines from a file with flags.
|
| 1141 |
+
|
| 1142 |
+
Args:
|
| 1143 |
+
filename: str, the name of the flag file.
|
| 1144 |
+
parsed_file_stack: [str], a list of the names of the files that we have
|
| 1145 |
+
recursively encountered at the current depth. MUTATED BY THIS FUNCTION
|
| 1146 |
+
(but the original value is preserved upon successfully returning from
|
| 1147 |
+
function call).
|
| 1148 |
+
|
| 1149 |
+
Returns:
|
| 1150 |
+
List of strings. See the note below.
|
| 1151 |
+
|
| 1152 |
+
NOTE(springer): This function checks for a nested --flagfile=<foo>
|
| 1153 |
+
tag and handles the lower file recursively. It returns a list of
|
| 1154 |
+
all the lines that _could_ contain command flags. This is
|
| 1155 |
+
EVERYTHING except whitespace lines and comments (lines starting
|
| 1156 |
+
with '#' or '//').
|
| 1157 |
+
"""
|
| 1158 |
+
# For consistency with the cpp version, ignore empty values.
|
| 1159 |
+
if not filename:
|
| 1160 |
+
return []
|
| 1161 |
+
if parsed_file_stack is None:
|
| 1162 |
+
parsed_file_stack = []
|
| 1163 |
+
# We do a little safety check for reparsing a file we've already encountered
|
| 1164 |
+
# at a previous depth.
|
| 1165 |
+
if filename in parsed_file_stack:
|
| 1166 |
+
sys.stderr.write(
|
| 1167 |
+
'Warning: Hit circular flagfile dependency. Ignoring flagfile: %s\n'
|
| 1168 |
+
% (filename,)
|
| 1169 |
+
)
|
| 1170 |
+
return []
|
| 1171 |
+
else:
|
| 1172 |
+
parsed_file_stack.append(filename)
|
| 1173 |
+
|
| 1174 |
+
line_list = [] # All line from flagfile.
|
| 1175 |
+
flag_line_list = [] # Subset of lines w/o comments, blanks, flagfile= tags.
|
| 1176 |
+
try:
|
| 1177 |
+
file_obj = open(filename)
|
| 1178 |
+
except OSError as e_msg:
|
| 1179 |
+
raise _exceptions.CantOpenFlagFileError(
|
| 1180 |
+
'ERROR:: Unable to open flagfile: %s' % e_msg
|
| 1181 |
+
)
|
| 1182 |
+
|
| 1183 |
+
with file_obj:
|
| 1184 |
+
line_list = file_obj.readlines()
|
| 1185 |
+
|
| 1186 |
+
# This is where we check each line in the file we just read.
|
| 1187 |
+
for line in line_list:
|
| 1188 |
+
if line.isspace():
|
| 1189 |
+
pass
|
| 1190 |
+
# Checks for comment (a line that starts with '#').
|
| 1191 |
+
elif line.startswith('#') or line.startswith('//'):
|
| 1192 |
+
pass
|
| 1193 |
+
# Checks for a nested "--flagfile=<bar>" flag in the current file.
|
| 1194 |
+
# If we find one, recursively parse down into that file.
|
| 1195 |
+
elif self._is_flag_file_directive(line):
|
| 1196 |
+
sub_filename = self._extract_filename(line)
|
| 1197 |
+
included_flags = self._get_flag_file_lines(
|
| 1198 |
+
sub_filename, parsed_file_stack=parsed_file_stack
|
| 1199 |
+
)
|
| 1200 |
+
flag_line_list.extend(included_flags)
|
| 1201 |
+
else:
|
| 1202 |
+
# Any line that's not a comment or a nested flagfile should get
|
| 1203 |
+
# copied into 2nd position. This leaves earlier arguments
|
| 1204 |
+
# further back in the list, thus giving them higher priority.
|
| 1205 |
+
flag_line_list.append(line.strip())
|
| 1206 |
+
|
| 1207 |
+
parsed_file_stack.pop()
|
| 1208 |
+
return flag_line_list
|
| 1209 |
+
|
| 1210 |
+
def read_flags_from_files(
|
| 1211 |
+
self, argv: Sequence[str], force_gnu: bool = True
|
| 1212 |
+
) -> list[str]:
|
| 1213 |
+
"""Processes command line args, but also allow args to be read from file.
|
| 1214 |
+
|
| 1215 |
+
Args:
|
| 1216 |
+
argv: [str], a list of strings, usually sys.argv[1:], which may contain
|
| 1217 |
+
one or more flagfile directives of the form --flagfile="./filename".
|
| 1218 |
+
Note that the name of the program (sys.argv[0]) should be omitted.
|
| 1219 |
+
force_gnu: bool, if False, --flagfile parsing obeys the
|
| 1220 |
+
FLAGS.is_gnu_getopt() value. If True, ignore the value and always follow
|
| 1221 |
+
gnu_getopt semantics.
|
| 1222 |
+
|
| 1223 |
+
Returns:
|
| 1224 |
+
A new list which has the original list combined with what we read
|
| 1225 |
+
from any flagfile(s).
|
| 1226 |
+
|
| 1227 |
+
Raises:
|
| 1228 |
+
IllegalFlagValueError: Raised when --flagfile is provided with no
|
| 1229 |
+
argument.
|
| 1230 |
+
|
| 1231 |
+
This function is called by FLAGS(argv).
|
| 1232 |
+
It scans the input list for a flag that looks like:
|
| 1233 |
+
--flagfile=<somefile>. Then it opens <somefile>, reads all valid key
|
| 1234 |
+
and value pairs and inserts them into the input list in exactly the
|
| 1235 |
+
place where the --flagfile arg is found.
|
| 1236 |
+
|
| 1237 |
+
Note that your application's flags are still defined the usual way
|
| 1238 |
+
using absl.flags DEFINE_flag() type functions.
|
| 1239 |
+
|
| 1240 |
+
Notes (assuming we're getting a commandline of some sort as our input):
|
| 1241 |
+
|
| 1242 |
+
* For duplicate flags, the last one we hit should "win".
|
| 1243 |
+
* Since flags that appear later win, a flagfile's settings can be "weak"
|
| 1244 |
+
if the --flagfile comes at the beginning of the argument sequence,
|
| 1245 |
+
and it can be "strong" if the --flagfile comes at the end.
|
| 1246 |
+
* A further "--flagfile=<otherfile.cfg>" CAN be nested in a flagfile.
|
| 1247 |
+
It will be expanded in exactly the spot where it is found.
|
| 1248 |
+
* In a flagfile, a line beginning with # or // is a comment.
|
| 1249 |
+
* Entirely blank lines _should_ be ignored.
|
| 1250 |
+
"""
|
| 1251 |
+
rest_of_args = argv
|
| 1252 |
+
new_argv = []
|
| 1253 |
+
while rest_of_args:
|
| 1254 |
+
current_arg = rest_of_args[0]
|
| 1255 |
+
rest_of_args = rest_of_args[1:]
|
| 1256 |
+
if self._is_flag_file_directive(current_arg):
|
| 1257 |
+
# This handles the case of -(-)flagfile foo. In this case the
|
| 1258 |
+
# next arg really is part of this one.
|
| 1259 |
+
if current_arg == '--flagfile' or current_arg == '-flagfile':
|
| 1260 |
+
if not rest_of_args:
|
| 1261 |
+
raise _exceptions.IllegalFlagValueError(
|
| 1262 |
+
'--flagfile with no argument'
|
| 1263 |
+
)
|
| 1264 |
+
flag_filename = os.path.expanduser(rest_of_args[0])
|
| 1265 |
+
rest_of_args = rest_of_args[1:]
|
| 1266 |
+
else:
|
| 1267 |
+
# This handles the case of (-)-flagfile=foo.
|
| 1268 |
+
flag_filename = self._extract_filename(current_arg)
|
| 1269 |
+
new_argv.extend(self._get_flag_file_lines(flag_filename))
|
| 1270 |
+
else:
|
| 1271 |
+
new_argv.append(current_arg)
|
| 1272 |
+
# Stop parsing after '--', like getopt and gnu_getopt.
|
| 1273 |
+
if current_arg == '--':
|
| 1274 |
+
break
|
| 1275 |
+
# Stop parsing after a non-flag, like getopt.
|
| 1276 |
+
if not current_arg.startswith('-'):
|
| 1277 |
+
if not force_gnu and not self.__dict__['__use_gnu_getopt']:
|
| 1278 |
+
break
|
| 1279 |
+
else:
|
| 1280 |
+
if (
|
| 1281 |
+
'=' not in current_arg
|
| 1282 |
+
and rest_of_args
|
| 1283 |
+
and not rest_of_args[0].startswith('-')
|
| 1284 |
+
):
|
| 1285 |
+
# If this is an occurrence of a legitimate --x y, skip the value
|
| 1286 |
+
# so that it won't be mistaken for a standalone arg.
|
| 1287 |
+
fl = self._flags()
|
| 1288 |
+
name = current_arg.lstrip('-')
|
| 1289 |
+
if name in fl and not fl[name].boolean:
|
| 1290 |
+
current_arg = rest_of_args[0]
|
| 1291 |
+
rest_of_args = rest_of_args[1:]
|
| 1292 |
+
new_argv.append(current_arg)
|
| 1293 |
+
|
| 1294 |
+
if rest_of_args:
|
| 1295 |
+
new_argv.extend(rest_of_args)
|
| 1296 |
+
|
| 1297 |
+
return new_argv
|
| 1298 |
+
|
| 1299 |
+
def flags_into_string(self) -> str:
|
| 1300 |
+
"""Returns a string with the flags assignments from this FlagValues object.
|
| 1301 |
+
|
| 1302 |
+
This function ignores flags whose value is None. Each flag
|
| 1303 |
+
assignment is separated by a newline.
|
| 1304 |
+
|
| 1305 |
+
NOTE: MUST mirror the behavior of the C++ CommandlineFlagsIntoString
|
| 1306 |
+
from https://github.com/gflags/gflags.
|
| 1307 |
+
|
| 1308 |
+
Returns:
|
| 1309 |
+
str, the string with the flags assignments from this FlagValues object.
|
| 1310 |
+
The flags are ordered by (module_name, flag_name).
|
| 1311 |
+
"""
|
| 1312 |
+
module_flags = sorted(self.flags_by_module_dict().items())
|
| 1313 |
+
s = ''
|
| 1314 |
+
for unused_module_name, flags in module_flags:
|
| 1315 |
+
flags = sorted(flags, key=lambda f: f.name)
|
| 1316 |
+
for flag in flags:
|
| 1317 |
+
if flag.value is not None:
|
| 1318 |
+
s += flag.serialize() + '\n'
|
| 1319 |
+
return s
|
| 1320 |
+
|
| 1321 |
+
def append_flags_into_file(self, filename: str) -> None:
|
| 1322 |
+
"""Appends all flags assignments from this FlagInfo object to a file.
|
| 1323 |
+
|
| 1324 |
+
Output will be in the format of a flagfile.
|
| 1325 |
+
|
| 1326 |
+
NOTE: MUST mirror the behavior of the C++ AppendFlagsIntoFile
|
| 1327 |
+
from https://github.com/gflags/gflags.
|
| 1328 |
+
|
| 1329 |
+
Args:
|
| 1330 |
+
filename: str, name of the file.
|
| 1331 |
+
"""
|
| 1332 |
+
with open(filename, 'a') as out_file:
|
| 1333 |
+
out_file.write(self.flags_into_string())
|
| 1334 |
+
|
| 1335 |
+
def write_help_in_xml_format(self, outfile: TextIO | None = None) -> None:
|
| 1336 |
+
"""Outputs flag documentation in XML format.
|
| 1337 |
+
|
| 1338 |
+
NOTE: We use element names that are consistent with those used by
|
| 1339 |
+
the C++ command-line flag library, from
|
| 1340 |
+
https://github.com/gflags/gflags.
|
| 1341 |
+
We also use a few new elements (e.g., <key>), but we do not
|
| 1342 |
+
interfere / overlap with existing XML elements used by the C++
|
| 1343 |
+
library. Please maintain this consistency.
|
| 1344 |
+
|
| 1345 |
+
Args:
|
| 1346 |
+
outfile: File object we write to. Default None means sys.stdout.
|
| 1347 |
+
"""
|
| 1348 |
+
doc = minidom.Document()
|
| 1349 |
+
all_flag = doc.createElement('AllFlags')
|
| 1350 |
+
doc.appendChild(all_flag)
|
| 1351 |
+
|
| 1352 |
+
all_flag.appendChild(
|
| 1353 |
+
_helpers.create_xml_dom_element(
|
| 1354 |
+
doc, 'program', os.path.basename(sys.argv[0])
|
| 1355 |
+
)
|
| 1356 |
+
)
|
| 1357 |
+
|
| 1358 |
+
usage_doc = sys.modules['__main__'].__doc__
|
| 1359 |
+
if not usage_doc:
|
| 1360 |
+
usage_doc = '\nUSAGE: %s [flags]\n' % sys.argv[0]
|
| 1361 |
+
else:
|
| 1362 |
+
usage_doc = usage_doc.replace('%s', sys.argv[0])
|
| 1363 |
+
all_flag.appendChild(
|
| 1364 |
+
_helpers.create_xml_dom_element(doc, 'usage', usage_doc)
|
| 1365 |
+
)
|
| 1366 |
+
|
| 1367 |
+
# Get list of key flags for the main module.
|
| 1368 |
+
key_flags = self.get_key_flags_for_module(sys.argv[0])
|
| 1369 |
+
|
| 1370 |
+
flags_by_module = self.flags_by_module_dict()
|
| 1371 |
+
# Sort flags by declaring module name and next by flag name.
|
| 1372 |
+
for module_name in sorted(flags_by_module.keys()):
|
| 1373 |
+
flag_list = [(f.name, f) for f in flags_by_module[module_name]]
|
| 1374 |
+
flag_list.sort()
|
| 1375 |
+
for unused_flag_name, flag in flag_list:
|
| 1376 |
+
is_key = flag in key_flags
|
| 1377 |
+
all_flag.appendChild(
|
| 1378 |
+
flag._create_xml_dom_element( # pylint: disable=protected-access
|
| 1379 |
+
doc, module_name, is_key=is_key
|
| 1380 |
+
)
|
| 1381 |
+
)
|
| 1382 |
+
|
| 1383 |
+
outfile = outfile or sys.stdout
|
| 1384 |
+
outfile.write(
|
| 1385 |
+
doc.toprettyxml(indent=' ', encoding='utf-8').decode('utf-8')
|
| 1386 |
+
)
|
| 1387 |
+
outfile.flush()
|
| 1388 |
+
|
| 1389 |
+
def _check_method_name_conflicts(self, name: str, flag: Flag):
|
| 1390 |
+
if flag.allow_using_method_names:
|
| 1391 |
+
return
|
| 1392 |
+
short_name = flag.short_name
|
| 1393 |
+
flag_names = {name} if short_name is None else {name, short_name}
|
| 1394 |
+
for flag_name in flag_names:
|
| 1395 |
+
if flag_name in self.__dict__['__banned_flag_names']:
|
| 1396 |
+
raise _exceptions.FlagNameConflictsWithMethodError(
|
| 1397 |
+
'Cannot define a flag named "{name}". It conflicts with a method '
|
| 1398 |
+
'on class "{class_name}". To allow defining it, use '
|
| 1399 |
+
'allow_using_method_names and access the flag value with '
|
| 1400 |
+
"FLAGS['{name}'].value. FLAGS.{name} returns the method, "
|
| 1401 |
+
'not the flag value.'.format(
|
| 1402 |
+
name=flag_name, class_name=type(self).__name__
|
| 1403 |
+
)
|
| 1404 |
+
)
|
| 1405 |
+
|
| 1406 |
+
|
| 1407 |
+
FLAGS = FlagValues()
|
| 1408 |
+
|
| 1409 |
+
|
| 1410 |
+
class FlagHolder(Generic[_T_co]):
|
| 1411 |
+
"""Holds a defined flag.
|
| 1412 |
+
|
| 1413 |
+
This facilitates a cleaner api around global state. Instead of::
|
| 1414 |
+
|
| 1415 |
+
flags.DEFINE_integer('foo', ...)
|
| 1416 |
+
flags.DEFINE_integer('bar', ...)
|
| 1417 |
+
|
| 1418 |
+
def method():
|
| 1419 |
+
# prints parsed value of 'bar' flag
|
| 1420 |
+
print(flags.FLAGS.foo)
|
| 1421 |
+
# runtime error due to typo or possibly bad coding style.
|
| 1422 |
+
print(flags.FLAGS.baz)
|
| 1423 |
+
|
| 1424 |
+
it encourages code like::
|
| 1425 |
+
|
| 1426 |
+
_FOO_FLAG = flags.DEFINE_integer('foo', ...)
|
| 1427 |
+
_BAR_FLAG = flags.DEFINE_integer('bar', ...)
|
| 1428 |
+
|
| 1429 |
+
def method():
|
| 1430 |
+
print(_FOO_FLAG.value)
|
| 1431 |
+
print(_BAR_FLAG.value)
|
| 1432 |
+
|
| 1433 |
+
since the name of the flag appears only once in the source code.
|
| 1434 |
+
"""
|
| 1435 |
+
|
| 1436 |
+
value: _T_co
|
| 1437 |
+
|
| 1438 |
+
def __init__(
|
| 1439 |
+
self,
|
| 1440 |
+
flag_values: FlagValues,
|
| 1441 |
+
flag: Flag[_T_co],
|
| 1442 |
+
ensure_non_none_value: bool = False,
|
| 1443 |
+
):
|
| 1444 |
+
"""Constructs a FlagHolder instance providing typesafe access to flag.
|
| 1445 |
+
|
| 1446 |
+
Args:
|
| 1447 |
+
flag_values: The container the flag is registered to.
|
| 1448 |
+
flag: The flag object for this flag.
|
| 1449 |
+
ensure_non_none_value: Is the value of the flag allowed to be None.
|
| 1450 |
+
"""
|
| 1451 |
+
self._flagvalues = flag_values
|
| 1452 |
+
# We take the entire flag object, but only keep the name. Why?
|
| 1453 |
+
# - We want FlagHolder[T] to be generic container
|
| 1454 |
+
# - flag_values contains all flags, so has no reference to T.
|
| 1455 |
+
# - typecheckers don't like to see a generic class where none of the ctor
|
| 1456 |
+
# arguments refer to the generic type.
|
| 1457 |
+
self._name = flag.name
|
| 1458 |
+
# We intentionally do NOT check if the default value is None.
|
| 1459 |
+
# This allows future use of this for "required flags with None default"
|
| 1460 |
+
self._ensure_non_none_value = ensure_non_none_value
|
| 1461 |
+
|
| 1462 |
+
def __eq__(self, other):
|
| 1463 |
+
raise TypeError(
|
| 1464 |
+
"unsupported operand type(s) for ==: '{0}' and '{1}' "
|
| 1465 |
+
"(did you mean to use '{0}.value' instead?)".format(
|
| 1466 |
+
type(self).__name__, type(other).__name__
|
| 1467 |
+
)
|
| 1468 |
+
)
|
| 1469 |
+
|
| 1470 |
+
def __bool__(self):
|
| 1471 |
+
raise TypeError(
|
| 1472 |
+
"bool() not supported for instances of type '{0}' "
|
| 1473 |
+
"(did you mean to use '{0}.value' instead?)".format(type(self).__name__)
|
| 1474 |
+
)
|
| 1475 |
+
|
| 1476 |
+
__nonzero__ = __bool__
|
| 1477 |
+
|
| 1478 |
+
@property
|
| 1479 |
+
def name(self) -> str:
|
| 1480 |
+
return self._name
|
| 1481 |
+
|
| 1482 |
+
@property # type: ignore[no-redef]
|
| 1483 |
+
def value(self) -> _T_co:
|
| 1484 |
+
"""Returns the value of the flag.
|
| 1485 |
+
|
| 1486 |
+
If ``_ensure_non_none_value`` is ``True``, then return value is not
|
| 1487 |
+
``None``.
|
| 1488 |
+
|
| 1489 |
+
Raises:
|
| 1490 |
+
UnparsedFlagAccessError: if flag parsing has not finished.
|
| 1491 |
+
IllegalFlagValueError: if value is None unexpectedly.
|
| 1492 |
+
"""
|
| 1493 |
+
val = getattr(self._flagvalues, self._name)
|
| 1494 |
+
if self._ensure_non_none_value and val is None:
|
| 1495 |
+
raise _exceptions.IllegalFlagValueError(
|
| 1496 |
+
'Unexpected None value for flag %s' % self._name
|
| 1497 |
+
)
|
| 1498 |
+
return val
|
| 1499 |
+
|
| 1500 |
+
@property
|
| 1501 |
+
def default(self) -> _T_co:
|
| 1502 |
+
"""Returns the default value of the flag."""
|
| 1503 |
+
return self._flagvalues[self._name].default # type: ignore[return-value]
|
| 1504 |
+
|
| 1505 |
+
@property
|
| 1506 |
+
def present(self) -> bool:
|
| 1507 |
+
"""Returns True if the flag was parsed from command-line flags."""
|
| 1508 |
+
return bool(self._flagvalues[self._name].present)
|
| 1509 |
+
|
| 1510 |
+
def serialize(self) -> str:
|
| 1511 |
+
"""Returns a serialized representation of the flag."""
|
| 1512 |
+
return self._flagvalues[self._name].serialize()
|
| 1513 |
+
|
| 1514 |
+
|
| 1515 |
+
def resolve_flag_ref(
|
| 1516 |
+
flag_ref: str | FlagHolder, flag_values: FlagValues
|
| 1517 |
+
) -> tuple[str, FlagValues]:
|
| 1518 |
+
"""Helper to validate and resolve a flag reference argument."""
|
| 1519 |
+
if isinstance(flag_ref, FlagHolder):
|
| 1520 |
+
new_flag_values = flag_ref._flagvalues # pylint: disable=protected-access
|
| 1521 |
+
if flag_values != FLAGS and flag_values != new_flag_values:
|
| 1522 |
+
raise ValueError(
|
| 1523 |
+
'flag_values must not be customized when operating on a FlagHolder'
|
| 1524 |
+
)
|
| 1525 |
+
return flag_ref.name, new_flag_values
|
| 1526 |
+
return flag_ref, flag_values
|
| 1527 |
+
|
| 1528 |
+
|
| 1529 |
+
def resolve_flag_refs(
|
| 1530 |
+
flag_refs: Sequence[str | FlagHolder], flag_values: FlagValues
|
| 1531 |
+
) -> tuple[list[str], FlagValues]:
|
| 1532 |
+
"""Helper to validate and resolve flag reference list arguments."""
|
| 1533 |
+
fv = None
|
| 1534 |
+
names = []
|
| 1535 |
+
for ref in flag_refs:
|
| 1536 |
+
if isinstance(ref, FlagHolder):
|
| 1537 |
+
newfv = ref._flagvalues # pylint: disable=protected-access
|
| 1538 |
+
name = ref.name
|
| 1539 |
+
else:
|
| 1540 |
+
newfv = flag_values
|
| 1541 |
+
name = ref
|
| 1542 |
+
if fv and fv != newfv:
|
| 1543 |
+
raise ValueError(
|
| 1544 |
+
'multiple FlagValues instances used in invocation. '
|
| 1545 |
+
'FlagHolders must be registered to the same FlagValues instance as '
|
| 1546 |
+
'do flag names, if provided.'
|
| 1547 |
+
)
|
| 1548 |
+
fv = newfv
|
| 1549 |
+
names.append(name)
|
| 1550 |
+
if fv is None:
|
| 1551 |
+
raise ValueError('flag_refs argument must not be empty')
|
| 1552 |
+
return names, fv
|
.venv/lib/python3.13/site-packages/absl/flags/_helpers.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Internal helper functions for Abseil Python flags library."""
|
| 16 |
+
|
| 17 |
+
from collections.abc import Iterable, Sequence
|
| 18 |
+
import re
|
| 19 |
+
import shutil
|
| 20 |
+
import sys
|
| 21 |
+
import textwrap
|
| 22 |
+
import types
|
| 23 |
+
from typing import Any, NamedTuple
|
| 24 |
+
from xml.dom import minidom
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
_DEFAULT_HELP_WIDTH = 80 # Default width of help output.
|
| 28 |
+
# Minimal "sane" width of help output. We assume that any value below 40 is
|
| 29 |
+
# unreasonable.
|
| 30 |
+
_MIN_HELP_WIDTH = 40
|
| 31 |
+
|
| 32 |
+
# Define the allowed error rate in an input string to get suggestions.
|
| 33 |
+
#
|
| 34 |
+
# We lean towards a high threshold because we tend to be matching a phrase,
|
| 35 |
+
# and the simple algorithm used here is geared towards correcting word
|
| 36 |
+
# spellings.
|
| 37 |
+
#
|
| 38 |
+
# For manual testing, consider "<command> --list" which produced a large number
|
| 39 |
+
# of spurious suggestions when we used "least_errors > 0.5" instead of
|
| 40 |
+
# "least_erros >= 0.5".
|
| 41 |
+
_SUGGESTION_ERROR_RATE_THRESHOLD = 0.50
|
| 42 |
+
|
| 43 |
+
# Characters that cannot appear or are highly discouraged in an XML 1.0
|
| 44 |
+
# document. (See http://www.w3.org/TR/REC-xml/#charsets or
|
| 45 |
+
# https://en.wikipedia.org/wiki/Valid_characters_in_XML#XML_1.0)
|
| 46 |
+
_ILLEGAL_XML_CHARS_REGEX = re.compile(
|
| 47 |
+
'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]'
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
# This is a set of module ids for the modules that disclaim key flags.
|
| 51 |
+
# This module is explicitly added to this set so that we never consider it to
|
| 52 |
+
# define key flag.
|
| 53 |
+
disclaim_module_ids: set[int] = {id(sys.modules[__name__])}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# Define special flags here so that help may be generated for them.
|
| 57 |
+
# NOTE: Please do NOT use SPECIAL_FLAGS from outside flags module.
|
| 58 |
+
# Initialized inside flagvalues.py.
|
| 59 |
+
# NOTE: This cannot be annotated as its actual FlagValues type since this would
|
| 60 |
+
# create a circular dependency.
|
| 61 |
+
SPECIAL_FLAGS: Any = None
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# This points to the flags module, initialized in flags/__init__.py.
|
| 65 |
+
# This should only be used in adopt_module_key_flags to take SPECIAL_FLAGS into
|
| 66 |
+
# account.
|
| 67 |
+
FLAGS_MODULE: types.ModuleType | None = None
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class _ModuleObjectAndName(NamedTuple):
|
| 71 |
+
"""Module object and name.
|
| 72 |
+
|
| 73 |
+
Fields:
|
| 74 |
+
- module: object, module object.
|
| 75 |
+
- module_name: str, module name.
|
| 76 |
+
"""
|
| 77 |
+
module: types.ModuleType
|
| 78 |
+
module_name: str
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def get_module_object_and_name(
|
| 82 |
+
globals_dict: dict[str, Any],
|
| 83 |
+
) -> _ModuleObjectAndName | None:
|
| 84 |
+
"""Returns the module that defines a global environment, and its name.
|
| 85 |
+
|
| 86 |
+
Args:
|
| 87 |
+
globals_dict: A dictionary that should correspond to an environment
|
| 88 |
+
providing the values of the globals.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
_ModuleObjectAndName - pair of module object & module name.
|
| 92 |
+
Returns None if the module could not be identified.
|
| 93 |
+
"""
|
| 94 |
+
try:
|
| 95 |
+
name = globals_dict['__name__']
|
| 96 |
+
module = sys.modules[name]
|
| 97 |
+
except KeyError:
|
| 98 |
+
return None
|
| 99 |
+
# Pick a more informative name for the main module.
|
| 100 |
+
return _ModuleObjectAndName(
|
| 101 |
+
module, sys.argv[0] if name == '__main__' else name
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def get_calling_module_object_and_name() -> _ModuleObjectAndName:
|
| 106 |
+
"""Returns the module that's calling into this module.
|
| 107 |
+
|
| 108 |
+
We generally use this function to get the name of the module calling a
|
| 109 |
+
DEFINE_foo... function.
|
| 110 |
+
|
| 111 |
+
Returns:
|
| 112 |
+
The module object that called into this one.
|
| 113 |
+
|
| 114 |
+
Raises:
|
| 115 |
+
AssertionError: Raised when no calling module could be identified.
|
| 116 |
+
"""
|
| 117 |
+
for depth in range(1, sys.getrecursionlimit()):
|
| 118 |
+
# sys._getframe is the right thing to use here, as it's the best
|
| 119 |
+
# way to walk up the call stack.
|
| 120 |
+
globals_for_frame = sys._getframe(depth).f_globals # pylint: disable=protected-access
|
| 121 |
+
module = get_module_object_and_name(globals_for_frame)
|
| 122 |
+
if module is not None and id(module.module) not in disclaim_module_ids:
|
| 123 |
+
return module
|
| 124 |
+
raise AssertionError('No module was found')
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def get_calling_module() -> str:
|
| 128 |
+
"""Returns the name of the module that's calling into this module."""
|
| 129 |
+
return get_calling_module_object_and_name().module_name
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def create_xml_dom_element(
|
| 133 |
+
doc: minidom.Document, name: str, value: Any
|
| 134 |
+
) -> minidom.Element:
|
| 135 |
+
"""Returns an XML DOM element with name and text value.
|
| 136 |
+
|
| 137 |
+
Args:
|
| 138 |
+
doc: minidom.Document, the DOM document it should create nodes from.
|
| 139 |
+
name: str, the tag of XML element.
|
| 140 |
+
value: object, whose string representation will be used
|
| 141 |
+
as the value of the XML element. Illegal or highly discouraged xml 1.0
|
| 142 |
+
characters are stripped.
|
| 143 |
+
|
| 144 |
+
Returns:
|
| 145 |
+
An instance of minidom.Element.
|
| 146 |
+
"""
|
| 147 |
+
s = str(value)
|
| 148 |
+
if isinstance(value, bool):
|
| 149 |
+
# Display boolean values as the C++ flag library does: no caps.
|
| 150 |
+
s = s.lower()
|
| 151 |
+
# Remove illegal xml characters.
|
| 152 |
+
s = _ILLEGAL_XML_CHARS_REGEX.sub('', s)
|
| 153 |
+
|
| 154 |
+
e = doc.createElement(name)
|
| 155 |
+
e.appendChild(doc.createTextNode(s))
|
| 156 |
+
return e
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def get_help_width() -> int:
|
| 160 |
+
"""Returns the integer width of help lines that is used in TextWrap."""
|
| 161 |
+
size = shutil.get_terminal_size(fallback=(_DEFAULT_HELP_WIDTH, 1))
|
| 162 |
+
return size.columns
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def get_flag_suggestions(
|
| 166 |
+
attempt: str, longopt_list: Sequence[str]
|
| 167 |
+
) -> list[str]:
|
| 168 |
+
"""Returns helpful similar matches for an invalid flag."""
|
| 169 |
+
# Don't suggest on very short strings, or if no longopts are specified.
|
| 170 |
+
if len(attempt) <= 2 or not longopt_list:
|
| 171 |
+
return []
|
| 172 |
+
|
| 173 |
+
option_names = [v.split('=')[0] for v in longopt_list]
|
| 174 |
+
|
| 175 |
+
# Find close approximations in flag prefixes.
|
| 176 |
+
# This also handles the case where the flag is spelled right but ambiguous.
|
| 177 |
+
distances = [(_damerau_levenshtein(attempt, option[0:len(attempt)]), option)
|
| 178 |
+
for option in option_names]
|
| 179 |
+
# t[0] is distance, and sorting by t[1] allows us to have stable output.
|
| 180 |
+
distances.sort()
|
| 181 |
+
|
| 182 |
+
least_errors, _ = distances[0]
|
| 183 |
+
# Don't suggest excessively bad matches.
|
| 184 |
+
if least_errors >= _SUGGESTION_ERROR_RATE_THRESHOLD * len(attempt):
|
| 185 |
+
return []
|
| 186 |
+
|
| 187 |
+
suggestions = []
|
| 188 |
+
for errors, name in distances:
|
| 189 |
+
if errors == least_errors:
|
| 190 |
+
suggestions.append(name)
|
| 191 |
+
else:
|
| 192 |
+
break
|
| 193 |
+
return suggestions
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def _damerau_levenshtein(a, b):
|
| 197 |
+
"""Returns Damerau-Levenshtein edit distance from a to b."""
|
| 198 |
+
memo = {}
|
| 199 |
+
|
| 200 |
+
def distance(x, y):
|
| 201 |
+
"""Recursively defined string distance with memoization."""
|
| 202 |
+
if (x, y) in memo:
|
| 203 |
+
return memo[x, y]
|
| 204 |
+
if not x:
|
| 205 |
+
d = len(y)
|
| 206 |
+
elif not y:
|
| 207 |
+
d = len(x)
|
| 208 |
+
else:
|
| 209 |
+
d = min(
|
| 210 |
+
distance(x[1:], y) + 1, # correct an insertion error
|
| 211 |
+
distance(x, y[1:]) + 1, # correct a deletion error
|
| 212 |
+
distance(x[1:], y[1:]) + (x[0] != y[0])) # correct a wrong character
|
| 213 |
+
if len(x) >= 2 and len(y) >= 2 and x[0] == y[1] and x[1] == y[0]:
|
| 214 |
+
# Correct a transposition.
|
| 215 |
+
t = distance(x[2:], y[2:]) + 1
|
| 216 |
+
if d > t:
|
| 217 |
+
d = t
|
| 218 |
+
|
| 219 |
+
memo[x, y] = d
|
| 220 |
+
return d
|
| 221 |
+
return distance(a, b)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def text_wrap(
|
| 225 |
+
text: str,
|
| 226 |
+
length: int | None = None,
|
| 227 |
+
indent: str = '',
|
| 228 |
+
firstline_indent: str | None = None,
|
| 229 |
+
) -> str:
|
| 230 |
+
"""Wraps a given text to a maximum line length and returns it.
|
| 231 |
+
|
| 232 |
+
It turns lines that only contain whitespace into empty lines, keeps new lines,
|
| 233 |
+
and expands tabs using 4 spaces.
|
| 234 |
+
|
| 235 |
+
Args:
|
| 236 |
+
text: Text to wrap.
|
| 237 |
+
length: Maximum length of a line, includes indentation. If this is `None`
|
| 238 |
+
then use `get_help_width()`.
|
| 239 |
+
indent: Indent for all but first line.
|
| 240 |
+
firstline_indent: Indent for first line. If `None`, fall back to `indent`.
|
| 241 |
+
|
| 242 |
+
Returns:
|
| 243 |
+
The wrapped text.
|
| 244 |
+
|
| 245 |
+
Raises:
|
| 246 |
+
ValueError: Raised if indent or firstline_indent not shorter than length.
|
| 247 |
+
"""
|
| 248 |
+
# Get defaults where callee used None
|
| 249 |
+
if length is None:
|
| 250 |
+
length = get_help_width()
|
| 251 |
+
if indent is None:
|
| 252 |
+
indent = ''
|
| 253 |
+
if firstline_indent is None:
|
| 254 |
+
firstline_indent = indent
|
| 255 |
+
|
| 256 |
+
if len(indent) >= length:
|
| 257 |
+
raise ValueError('Length of indent exceeds length')
|
| 258 |
+
if len(firstline_indent) >= length:
|
| 259 |
+
raise ValueError('Length of first line indent exceeds length')
|
| 260 |
+
|
| 261 |
+
text = text.expandtabs(4)
|
| 262 |
+
|
| 263 |
+
result = []
|
| 264 |
+
# Create one wrapper for the first paragraph and one for subsequent
|
| 265 |
+
# paragraphs that does not have the initial wrapping.
|
| 266 |
+
wrapper = textwrap.TextWrapper(
|
| 267 |
+
width=length, initial_indent=firstline_indent, subsequent_indent=indent)
|
| 268 |
+
subsequent_wrapper = textwrap.TextWrapper(
|
| 269 |
+
width=length, initial_indent=indent, subsequent_indent=indent)
|
| 270 |
+
|
| 271 |
+
# textwrap does not have any special treatment for newlines. From the docs:
|
| 272 |
+
# "...newlines may appear in the middle of a line and cause strange output.
|
| 273 |
+
# For this reason, text should be split into paragraphs (using
|
| 274 |
+
# str.splitlines() or similar) which are wrapped separately."
|
| 275 |
+
for paragraph in (p.strip() for p in text.splitlines()):
|
| 276 |
+
if paragraph:
|
| 277 |
+
result.extend(wrapper.wrap(paragraph))
|
| 278 |
+
else:
|
| 279 |
+
result.append('') # Keep empty lines.
|
| 280 |
+
# Replace initial wrapper with wrapper for subsequent paragraphs.
|
| 281 |
+
wrapper = subsequent_wrapper
|
| 282 |
+
|
| 283 |
+
return '\n'.join(result)
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def flag_dict_to_args(
|
| 287 |
+
flag_map: dict[str, Any], multi_flags: set[str] | None = None
|
| 288 |
+
) -> Iterable[str]:
|
| 289 |
+
"""Convert a dict of values into process call parameters.
|
| 290 |
+
|
| 291 |
+
This method is used to convert a dictionary into a sequence of parameters
|
| 292 |
+
for a binary that parses arguments using this module.
|
| 293 |
+
|
| 294 |
+
Args:
|
| 295 |
+
flag_map: dict, a mapping where the keys are flag names (strings).
|
| 296 |
+
values are treated according to their type:
|
| 297 |
+
|
| 298 |
+
* If value is ``None``, then only the name is emitted.
|
| 299 |
+
* If value is ``True``, then only the name is emitted.
|
| 300 |
+
* If value is ``False``, then only the name prepended with 'no' is
|
| 301 |
+
emitted.
|
| 302 |
+
* If value is a string then ``--name=value`` is emitted.
|
| 303 |
+
* If value is a collection, this will emit
|
| 304 |
+
``--name=value1,value2,value3``, unless the flag name is in
|
| 305 |
+
``multi_flags``, in which case this will emit
|
| 306 |
+
``--name=value1 --name=value2 --name=value3``.
|
| 307 |
+
* Everything else is converted to string an passed as such.
|
| 308 |
+
|
| 309 |
+
multi_flags: set, names (strings) of flags that should be treated as
|
| 310 |
+
multi-flags.
|
| 311 |
+
Yields:
|
| 312 |
+
sequence of string suitable for a subprocess execution.
|
| 313 |
+
"""
|
| 314 |
+
for key, value in flag_map.items():
|
| 315 |
+
if value is None:
|
| 316 |
+
yield '--%s' % key
|
| 317 |
+
elif isinstance(value, bool):
|
| 318 |
+
if value:
|
| 319 |
+
yield '--%s' % key
|
| 320 |
+
else:
|
| 321 |
+
yield '--no%s' % key
|
| 322 |
+
elif isinstance(value, (bytes, str)):
|
| 323 |
+
# We don't want strings to be handled like python collections.
|
| 324 |
+
yield '--%s=%s' % (key, value) # type: ignore[str-bytes-safe]
|
| 325 |
+
else:
|
| 326 |
+
# Now we attempt to deal with collections.
|
| 327 |
+
try:
|
| 328 |
+
if multi_flags and key in multi_flags:
|
| 329 |
+
for item in value:
|
| 330 |
+
yield '--%s=%s' % (key, str(item))
|
| 331 |
+
else:
|
| 332 |
+
yield '--%s=%s' % (key, ','.join(str(item) for item in value))
|
| 333 |
+
except TypeError:
|
| 334 |
+
# Default case.
|
| 335 |
+
yield '--%s=%s' % (key, value)
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def trim_docstring(docstring: str) -> str:
|
| 339 |
+
"""Removes indentation from triple-quoted strings.
|
| 340 |
+
|
| 341 |
+
This is the function specified in PEP 257 to handle docstrings:
|
| 342 |
+
https://www.python.org/dev/peps/pep-0257/.
|
| 343 |
+
|
| 344 |
+
Args:
|
| 345 |
+
docstring: str, a python docstring.
|
| 346 |
+
|
| 347 |
+
Returns:
|
| 348 |
+
str, docstring with indentation removed.
|
| 349 |
+
"""
|
| 350 |
+
if not docstring:
|
| 351 |
+
return ''
|
| 352 |
+
|
| 353 |
+
# If you've got a line longer than this you have other problems...
|
| 354 |
+
max_indent = 1 << 29
|
| 355 |
+
|
| 356 |
+
# Convert tabs to spaces (following the normal Python rules)
|
| 357 |
+
# and split into a list of lines:
|
| 358 |
+
lines = docstring.expandtabs().splitlines()
|
| 359 |
+
|
| 360 |
+
# Determine minimum indentation (first line doesn't count):
|
| 361 |
+
indent = max_indent
|
| 362 |
+
for line in lines[1:]:
|
| 363 |
+
stripped = line.lstrip()
|
| 364 |
+
if stripped:
|
| 365 |
+
indent = min(indent, len(line) - len(stripped))
|
| 366 |
+
# Remove indentation (first line is special):
|
| 367 |
+
trimmed = [lines[0].strip()]
|
| 368 |
+
if indent < max_indent:
|
| 369 |
+
for line in lines[1:]:
|
| 370 |
+
trimmed.append(line[indent:].rstrip())
|
| 371 |
+
# Strip off trailing and leading blank lines:
|
| 372 |
+
while trimmed and not trimmed[-1]:
|
| 373 |
+
trimmed.pop()
|
| 374 |
+
while trimmed and not trimmed[0]:
|
| 375 |
+
trimmed.pop(0)
|
| 376 |
+
# Return a single string:
|
| 377 |
+
return '\n'.join(trimmed)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def doc_to_help(doc: str) -> str:
|
| 381 |
+
"""Takes a __doc__ string and reformats it as help."""
|
| 382 |
+
|
| 383 |
+
# Get rid of starting and ending white space. Using lstrip() or even
|
| 384 |
+
# strip() could drop more than maximum of first line and right space
|
| 385 |
+
# of last line.
|
| 386 |
+
doc = doc.strip()
|
| 387 |
+
|
| 388 |
+
# Get rid of all empty lines.
|
| 389 |
+
whitespace_only_line = re.compile('^[ \t]+$', re.M)
|
| 390 |
+
doc = whitespace_only_line.sub('', doc)
|
| 391 |
+
|
| 392 |
+
# Cut out common space at line beginnings.
|
| 393 |
+
doc = trim_docstring(doc)
|
| 394 |
+
|
| 395 |
+
# Just like this module's comment, comments tend to be aligned somehow.
|
| 396 |
+
# In other words they all start with the same amount of white space.
|
| 397 |
+
# 1) keep double new lines;
|
| 398 |
+
# 2) keep ws after new lines if not empty line;
|
| 399 |
+
# 3) all other new lines shall be changed to a space;
|
| 400 |
+
# Solution: Match new lines between non white space and replace with space.
|
| 401 |
+
doc = re.sub(r'(?<=\S)\n(?=\S)', ' ', doc, flags=re.M)
|
| 402 |
+
|
| 403 |
+
return doc
|
.venv/lib/python3.13/site-packages/absl/flags/_validators.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Module to enforce different constraints on flags.
|
| 16 |
+
|
| 17 |
+
Flags validators can be registered using following functions / decorators::
|
| 18 |
+
|
| 19 |
+
flags.register_validator
|
| 20 |
+
@flags.validator
|
| 21 |
+
flags.register_multi_flags_validator
|
| 22 |
+
@flags.multi_flags_validator
|
| 23 |
+
|
| 24 |
+
Three convenience functions are also provided for common flag constraints::
|
| 25 |
+
|
| 26 |
+
flags.mark_flag_as_required
|
| 27 |
+
flags.mark_flags_as_required
|
| 28 |
+
flags.mark_flags_as_mutual_exclusive
|
| 29 |
+
flags.mark_bool_flags_as_mutual_exclusive
|
| 30 |
+
|
| 31 |
+
See their docstring in this module for a usage manual.
|
| 32 |
+
|
| 33 |
+
Do NOT import this module directly. Import the flags package and use the
|
| 34 |
+
aliases defined at the package level instead.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
import warnings
|
| 38 |
+
|
| 39 |
+
from absl.flags import _exceptions
|
| 40 |
+
from absl.flags import _flagvalues
|
| 41 |
+
from absl.flags import _validators_classes
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def register_validator(flag_name,
|
| 45 |
+
checker,
|
| 46 |
+
message='Flag validation failed',
|
| 47 |
+
flag_values=_flagvalues.FLAGS):
|
| 48 |
+
"""Adds a constraint, which will be enforced during program execution.
|
| 49 |
+
|
| 50 |
+
The constraint is validated when flags are initially parsed, and after each
|
| 51 |
+
change of the corresponding flag's value.
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
flag_name: str | FlagHolder, name or holder of the flag to be checked.
|
| 55 |
+
Positional-only parameter.
|
| 56 |
+
checker: callable, a function to validate the flag.
|
| 57 |
+
|
| 58 |
+
* input - A single positional argument: The value of the corresponding
|
| 59 |
+
flag (string, boolean, etc. This value will be passed to checker
|
| 60 |
+
by the library).
|
| 61 |
+
* output - bool, True if validator constraint is satisfied.
|
| 62 |
+
If constraint is not satisfied, it should either ``return False`` or
|
| 63 |
+
``raise flags.ValidationError(desired_error_message)``.
|
| 64 |
+
|
| 65 |
+
message: str, error text to be shown to the user if checker returns False.
|
| 66 |
+
If checker raises flags.ValidationError, message from the raised
|
| 67 |
+
error will be shown.
|
| 68 |
+
flag_values: flags.FlagValues, optional FlagValues instance to validate
|
| 69 |
+
against.
|
| 70 |
+
|
| 71 |
+
Raises:
|
| 72 |
+
AttributeError: Raised when flag_name is not registered as a valid flag
|
| 73 |
+
name.
|
| 74 |
+
ValueError: Raised when flag_values is non-default and does not match the
|
| 75 |
+
FlagValues of the provided FlagHolder instance.
|
| 76 |
+
"""
|
| 77 |
+
flag_name, flag_values = _flagvalues.resolve_flag_ref(flag_name, flag_values)
|
| 78 |
+
v = _validators_classes.SingleFlagValidator(flag_name, checker, message)
|
| 79 |
+
_add_validator(flag_values, v)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def validator(flag_name, message='Flag validation failed',
|
| 83 |
+
flag_values=_flagvalues.FLAGS):
|
| 84 |
+
"""A function decorator for defining a flag validator.
|
| 85 |
+
|
| 86 |
+
Registers the decorated function as a validator for flag_name, e.g.::
|
| 87 |
+
|
| 88 |
+
@flags.validator('foo')
|
| 89 |
+
def _CheckFoo(foo):
|
| 90 |
+
...
|
| 91 |
+
|
| 92 |
+
See :func:`register_validator` for the specification of checker function.
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
flag_name: str | FlagHolder, name or holder of the flag to be checked.
|
| 96 |
+
Positional-only parameter.
|
| 97 |
+
message: str, error text to be shown to the user if checker returns False.
|
| 98 |
+
If checker raises flags.ValidationError, message from the raised
|
| 99 |
+
error will be shown.
|
| 100 |
+
flag_values: flags.FlagValues, optional FlagValues instance to validate
|
| 101 |
+
against.
|
| 102 |
+
Returns:
|
| 103 |
+
A function decorator that registers its function argument as a validator.
|
| 104 |
+
Raises:
|
| 105 |
+
AttributeError: Raised when flag_name is not registered as a valid flag
|
| 106 |
+
name.
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
def decorate(function):
|
| 110 |
+
register_validator(flag_name, function,
|
| 111 |
+
message=message,
|
| 112 |
+
flag_values=flag_values)
|
| 113 |
+
return function
|
| 114 |
+
return decorate
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def register_multi_flags_validator(flag_names,
|
| 118 |
+
multi_flags_checker,
|
| 119 |
+
message='Flags validation failed',
|
| 120 |
+
flag_values=_flagvalues.FLAGS):
|
| 121 |
+
"""Adds a constraint to multiple flags.
|
| 122 |
+
|
| 123 |
+
The constraint is validated when flags are initially parsed, and after each
|
| 124 |
+
change of the corresponding flag's value.
|
| 125 |
+
|
| 126 |
+
Args:
|
| 127 |
+
flag_names: [str | FlagHolder], a list of the flag names or holders to be
|
| 128 |
+
checked. Positional-only parameter.
|
| 129 |
+
multi_flags_checker: callable, a function to validate the flag.
|
| 130 |
+
|
| 131 |
+
* input - dict, with keys() being flag_names, and value for each key
|
| 132 |
+
being the value of the corresponding flag (string, boolean, etc).
|
| 133 |
+
* output - bool, True if validator constraint is satisfied.
|
| 134 |
+
If constraint is not satisfied, it should either return False or
|
| 135 |
+
raise flags.ValidationError.
|
| 136 |
+
|
| 137 |
+
message: str, error text to be shown to the user if checker returns False.
|
| 138 |
+
If checker raises flags.ValidationError, message from the raised
|
| 139 |
+
error will be shown.
|
| 140 |
+
flag_values: flags.FlagValues, optional FlagValues instance to validate
|
| 141 |
+
against.
|
| 142 |
+
|
| 143 |
+
Raises:
|
| 144 |
+
AttributeError: Raised when a flag is not registered as a valid flag name.
|
| 145 |
+
ValueError: Raised when multiple FlagValues are used in the same
|
| 146 |
+
invocation. This can occur when FlagHolders have different `_flagvalues`
|
| 147 |
+
or when str-type flag_names entries are present and the `flag_values`
|
| 148 |
+
argument does not match that of provided FlagHolder(s).
|
| 149 |
+
"""
|
| 150 |
+
flag_names, flag_values = _flagvalues.resolve_flag_refs(
|
| 151 |
+
flag_names, flag_values)
|
| 152 |
+
v = _validators_classes.MultiFlagsValidator(
|
| 153 |
+
flag_names, multi_flags_checker, message)
|
| 154 |
+
_add_validator(flag_values, v)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def multi_flags_validator(flag_names,
|
| 158 |
+
message='Flag validation failed',
|
| 159 |
+
flag_values=_flagvalues.FLAGS):
|
| 160 |
+
"""A function decorator for defining a multi-flag validator.
|
| 161 |
+
|
| 162 |
+
Registers the decorated function as a validator for flag_names, e.g.::
|
| 163 |
+
|
| 164 |
+
@flags.multi_flags_validator(['foo', 'bar'])
|
| 165 |
+
def _CheckFooBar(flags_dict):
|
| 166 |
+
...
|
| 167 |
+
|
| 168 |
+
See :func:`register_multi_flags_validator` for the specification of checker
|
| 169 |
+
function.
|
| 170 |
+
|
| 171 |
+
Args:
|
| 172 |
+
flag_names: [str | FlagHolder], a list of the flag names or holders to be
|
| 173 |
+
checked. Positional-only parameter.
|
| 174 |
+
message: str, error text to be shown to the user if checker returns False.
|
| 175 |
+
If checker raises flags.ValidationError, message from the raised
|
| 176 |
+
error will be shown.
|
| 177 |
+
flag_values: flags.FlagValues, optional FlagValues instance to validate
|
| 178 |
+
against.
|
| 179 |
+
|
| 180 |
+
Returns:
|
| 181 |
+
A function decorator that registers its function argument as a validator.
|
| 182 |
+
|
| 183 |
+
Raises:
|
| 184 |
+
AttributeError: Raised when a flag is not registered as a valid flag name.
|
| 185 |
+
"""
|
| 186 |
+
|
| 187 |
+
def decorate(function):
|
| 188 |
+
register_multi_flags_validator(flag_names,
|
| 189 |
+
function,
|
| 190 |
+
message=message,
|
| 191 |
+
flag_values=flag_values)
|
| 192 |
+
return function
|
| 193 |
+
|
| 194 |
+
return decorate
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def mark_flag_as_required(flag_name, flag_values=_flagvalues.FLAGS):
|
| 198 |
+
"""Ensures that flag is not None during program execution.
|
| 199 |
+
|
| 200 |
+
Registers a flag validator, which will follow usual validator rules.
|
| 201 |
+
Important note: validator will pass for any non-``None`` value, such as
|
| 202 |
+
``False``, ``0`` (zero), ``''`` (empty string) and so on.
|
| 203 |
+
|
| 204 |
+
If your module might be imported by others, and you only wish to make the flag
|
| 205 |
+
required when the module is directly executed, call this method like this::
|
| 206 |
+
|
| 207 |
+
if __name__ == '__main__':
|
| 208 |
+
flags.mark_flag_as_required('your_flag_name')
|
| 209 |
+
app.run()
|
| 210 |
+
|
| 211 |
+
Args:
|
| 212 |
+
flag_name: str | FlagHolder, name or holder of the flag.
|
| 213 |
+
Positional-only parameter.
|
| 214 |
+
flag_values: flags.FlagValues, optional :class:`~absl.flags.FlagValues`
|
| 215 |
+
instance where the flag is defined.
|
| 216 |
+
Raises:
|
| 217 |
+
AttributeError: Raised when flag_name is not registered as a valid flag
|
| 218 |
+
name.
|
| 219 |
+
ValueError: Raised when flag_values is non-default and does not match the
|
| 220 |
+
FlagValues of the provided FlagHolder instance.
|
| 221 |
+
"""
|
| 222 |
+
flag_name, flag_values = _flagvalues.resolve_flag_ref(flag_name, flag_values)
|
| 223 |
+
if flag_values[flag_name].default is not None:
|
| 224 |
+
warnings.warn(
|
| 225 |
+
'Flag --%s has a non-None default value; therefore, '
|
| 226 |
+
'mark_flag_as_required will pass even if flag is not specified in the '
|
| 227 |
+
'command line!' % flag_name,
|
| 228 |
+
stacklevel=2)
|
| 229 |
+
register_validator(
|
| 230 |
+
flag_name,
|
| 231 |
+
lambda value: value is not None,
|
| 232 |
+
message=f'Flag --{flag_name} must have a value other than None.',
|
| 233 |
+
flag_values=flag_values,
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def mark_flags_as_required(flag_names, flag_values=_flagvalues.FLAGS):
|
| 238 |
+
"""Ensures that flags are not None during program execution.
|
| 239 |
+
|
| 240 |
+
If your module might be imported by others, and you only wish to make the flag
|
| 241 |
+
required when the module is directly executed, call this method like this::
|
| 242 |
+
|
| 243 |
+
if __name__ == '__main__':
|
| 244 |
+
flags.mark_flags_as_required(['flag1', 'flag2', 'flag3'])
|
| 245 |
+
app.run()
|
| 246 |
+
|
| 247 |
+
Args:
|
| 248 |
+
flag_names: Sequence[str | FlagHolder], names or holders of the flags.
|
| 249 |
+
flag_values: flags.FlagValues, optional FlagValues instance where the flags
|
| 250 |
+
are defined.
|
| 251 |
+
Raises:
|
| 252 |
+
AttributeError: If any of flag name has not already been defined as a flag.
|
| 253 |
+
"""
|
| 254 |
+
for flag_name in flag_names:
|
| 255 |
+
mark_flag_as_required(flag_name, flag_values)
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def mark_flags_as_mutual_exclusive(flag_names, required=False,
|
| 259 |
+
flag_values=_flagvalues.FLAGS):
|
| 260 |
+
"""Ensures that only one flag among flag_names is not None.
|
| 261 |
+
|
| 262 |
+
Important note: This validator checks if flag values are ``None``, and it does
|
| 263 |
+
not distinguish between default and explicit values. Therefore, this validator
|
| 264 |
+
does not make sense when applied to flags with default values other than None,
|
| 265 |
+
including other false values (e.g. ``False``, ``0``, ``''``, ``[]``). That
|
| 266 |
+
includes multi flags with a default value of ``[]`` instead of None.
|
| 267 |
+
|
| 268 |
+
Args:
|
| 269 |
+
flag_names: [str | FlagHolder], names or holders of flags.
|
| 270 |
+
Positional-only parameter.
|
| 271 |
+
required: bool. If true, exactly one of the flags must have a value other
|
| 272 |
+
than None. Otherwise, at most one of the flags can have a value other
|
| 273 |
+
than None, and it is valid for all of the flags to be None.
|
| 274 |
+
flag_values: flags.FlagValues, optional FlagValues instance where the flags
|
| 275 |
+
are defined.
|
| 276 |
+
|
| 277 |
+
Raises:
|
| 278 |
+
ValueError: Raised when multiple FlagValues are used in the same
|
| 279 |
+
invocation. This can occur when FlagHolders have different `_flagvalues`
|
| 280 |
+
or when str-type flag_names entries are present and the `flag_values`
|
| 281 |
+
argument does not match that of provided FlagHolder(s).
|
| 282 |
+
"""
|
| 283 |
+
flag_names, flag_values = _flagvalues.resolve_flag_refs(
|
| 284 |
+
flag_names, flag_values)
|
| 285 |
+
for flag_name in flag_names:
|
| 286 |
+
if flag_values[flag_name].default is not None:
|
| 287 |
+
warnings.warn(
|
| 288 |
+
'Flag --{} has a non-None default value. That does not make sense '
|
| 289 |
+
'with mark_flags_as_mutual_exclusive, which checks whether the '
|
| 290 |
+
'listed flags have a value other than None.'.format(flag_name),
|
| 291 |
+
stacklevel=2)
|
| 292 |
+
|
| 293 |
+
def validate_mutual_exclusion(flags_dict):
|
| 294 |
+
flag_count = sum(1 for val in flags_dict.values() if val is not None)
|
| 295 |
+
if flag_count == 1 or (not required and flag_count == 0):
|
| 296 |
+
return True
|
| 297 |
+
raise _exceptions.ValidationError(
|
| 298 |
+
'{} one of ({}) must have a value other than None.'.format(
|
| 299 |
+
'Exactly' if required else 'At most', ', '.join(flag_names)))
|
| 300 |
+
|
| 301 |
+
register_multi_flags_validator(
|
| 302 |
+
flag_names, validate_mutual_exclusion, flag_values=flag_values)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def mark_bool_flags_as_mutual_exclusive(flag_names, required=False,
|
| 306 |
+
flag_values=_flagvalues.FLAGS):
|
| 307 |
+
"""Ensures that only one flag among flag_names is True.
|
| 308 |
+
|
| 309 |
+
Args:
|
| 310 |
+
flag_names: [str | FlagHolder], names or holders of flags.
|
| 311 |
+
Positional-only parameter.
|
| 312 |
+
required: bool. If true, exactly one flag must be True. Otherwise, at most
|
| 313 |
+
one flag can be True, and it is valid for all flags to be False.
|
| 314 |
+
flag_values: flags.FlagValues, optional FlagValues instance where the flags
|
| 315 |
+
are defined.
|
| 316 |
+
|
| 317 |
+
Raises:
|
| 318 |
+
ValueError: Raised when multiple FlagValues are used in the same
|
| 319 |
+
invocation. This can occur when FlagHolders have different `_flagvalues`
|
| 320 |
+
or when str-type flag_names entries are present and the `flag_values`
|
| 321 |
+
argument does not match that of provided FlagHolder(s).
|
| 322 |
+
"""
|
| 323 |
+
flag_names, flag_values = _flagvalues.resolve_flag_refs(
|
| 324 |
+
flag_names, flag_values)
|
| 325 |
+
for flag_name in flag_names:
|
| 326 |
+
if not flag_values[flag_name].boolean:
|
| 327 |
+
raise _exceptions.ValidationError(
|
| 328 |
+
'Flag --{} is not Boolean, which is required for flags used in '
|
| 329 |
+
'mark_bool_flags_as_mutual_exclusive.'.format(flag_name))
|
| 330 |
+
|
| 331 |
+
def validate_boolean_mutual_exclusion(flags_dict):
|
| 332 |
+
flag_count = sum(bool(val) for val in flags_dict.values())
|
| 333 |
+
if flag_count == 1 or (not required and flag_count == 0):
|
| 334 |
+
return True
|
| 335 |
+
raise _exceptions.ValidationError(
|
| 336 |
+
'{} one of ({}) must be True.'.format(
|
| 337 |
+
'Exactly' if required else 'At most', ', '.join(flag_names)))
|
| 338 |
+
|
| 339 |
+
register_multi_flags_validator(
|
| 340 |
+
flag_names, validate_boolean_mutual_exclusion, flag_values=flag_values)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def _add_validator(fv, validator_instance):
|
| 344 |
+
"""Register new flags validator to be checked.
|
| 345 |
+
|
| 346 |
+
Args:
|
| 347 |
+
fv: flags.FlagValues, the FlagValues instance to add the validator.
|
| 348 |
+
validator_instance: validators.Validator, the validator to add.
|
| 349 |
+
Raises:
|
| 350 |
+
KeyError: Raised when validators work with a non-existing flag.
|
| 351 |
+
"""
|
| 352 |
+
for flag_name in validator_instance.get_flags_names():
|
| 353 |
+
fv[flag_name].validators.append(validator_instance)
|
.venv/lib/python3.13/site-packages/absl/flags/_validators_classes.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2021 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Defines *private* classes used for flag validators.
|
| 16 |
+
|
| 17 |
+
Do NOT import this module. DO NOT use anything from this module. They are
|
| 18 |
+
private APIs.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from absl.flags import _exceptions
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Validator:
|
| 25 |
+
"""Base class for flags validators.
|
| 26 |
+
|
| 27 |
+
Users should NOT overload these classes, and use flags.Register...
|
| 28 |
+
methods instead.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
# Used to assign each validator an unique insertion_index
|
| 32 |
+
validators_count = 0
|
| 33 |
+
|
| 34 |
+
def __init__(self, checker, message):
|
| 35 |
+
"""Constructor to create all validators.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
checker: function to verify the constraint.
|
| 39 |
+
Input of this method varies, see SingleFlagValidator and
|
| 40 |
+
multi_flags_validator for a detailed description.
|
| 41 |
+
message: str, error message to be shown to the user.
|
| 42 |
+
"""
|
| 43 |
+
self.checker = checker
|
| 44 |
+
self.message = message
|
| 45 |
+
Validator.validators_count += 1
|
| 46 |
+
# Used to assert validators in the order they were registered.
|
| 47 |
+
self.insertion_index = Validator.validators_count
|
| 48 |
+
|
| 49 |
+
def verify(self, flag_values):
|
| 50 |
+
"""Verifies that constraint is satisfied.
|
| 51 |
+
|
| 52 |
+
flags library calls this method to verify Validator's constraint.
|
| 53 |
+
|
| 54 |
+
Args:
|
| 55 |
+
flag_values: flags.FlagValues, the FlagValues instance to get flags from.
|
| 56 |
+
Raises:
|
| 57 |
+
Error: Raised if constraint is not satisfied.
|
| 58 |
+
"""
|
| 59 |
+
param = self._get_input_to_checker_function(flag_values)
|
| 60 |
+
if not self.checker(param):
|
| 61 |
+
raise _exceptions.ValidationError(self.message)
|
| 62 |
+
|
| 63 |
+
def get_flags_names(self):
|
| 64 |
+
"""Returns the names of the flags checked by this validator.
|
| 65 |
+
|
| 66 |
+
Returns:
|
| 67 |
+
[string], names of the flags.
|
| 68 |
+
"""
|
| 69 |
+
raise NotImplementedError('This method should be overloaded')
|
| 70 |
+
|
| 71 |
+
def print_flags_with_values(self, flag_values):
|
| 72 |
+
raise NotImplementedError('This method should be overloaded')
|
| 73 |
+
|
| 74 |
+
def _get_input_to_checker_function(self, flag_values):
|
| 75 |
+
"""Given flag values, returns the input to be given to checker.
|
| 76 |
+
|
| 77 |
+
Args:
|
| 78 |
+
flag_values: flags.FlagValues, containing all flags.
|
| 79 |
+
Returns:
|
| 80 |
+
The input to be given to checker. The return type depends on the specific
|
| 81 |
+
validator.
|
| 82 |
+
"""
|
| 83 |
+
raise NotImplementedError('This method should be overloaded')
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class SingleFlagValidator(Validator):
|
| 87 |
+
"""Validator behind register_validator() method.
|
| 88 |
+
|
| 89 |
+
Validates that a single flag passes its checker function. The checker function
|
| 90 |
+
takes the flag value and returns True (if value looks fine) or, if flag value
|
| 91 |
+
is not valid, either returns False or raises an Exception.
|
| 92 |
+
"""
|
| 93 |
+
|
| 94 |
+
def __init__(self, flag_name, checker, message):
|
| 95 |
+
"""Constructor.
|
| 96 |
+
|
| 97 |
+
Args:
|
| 98 |
+
flag_name: string, name of the flag.
|
| 99 |
+
checker: function to verify the validator.
|
| 100 |
+
input - value of the corresponding flag (string, boolean, etc).
|
| 101 |
+
output - bool, True if validator constraint is satisfied.
|
| 102 |
+
If constraint is not satisfied, it should either return False or
|
| 103 |
+
raise flags.ValidationError(desired_error_message).
|
| 104 |
+
message: str, error message to be shown to the user if validator's
|
| 105 |
+
condition is not satisfied.
|
| 106 |
+
"""
|
| 107 |
+
super().__init__(checker, message)
|
| 108 |
+
self.flag_name = flag_name
|
| 109 |
+
|
| 110 |
+
def get_flags_names(self):
|
| 111 |
+
return [self.flag_name]
|
| 112 |
+
|
| 113 |
+
def print_flags_with_values(self, flag_values):
|
| 114 |
+
return 'flag --%s=%s' % (self.flag_name, flag_values[self.flag_name].value)
|
| 115 |
+
|
| 116 |
+
def _get_input_to_checker_function(self, flag_values):
|
| 117 |
+
"""Given flag values, returns the input to be given to checker.
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
flag_values: flags.FlagValues, the FlagValues instance to get flags from.
|
| 121 |
+
Returns:
|
| 122 |
+
object, the input to be given to checker.
|
| 123 |
+
"""
|
| 124 |
+
return flag_values[self.flag_name].value
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class MultiFlagsValidator(Validator):
|
| 128 |
+
"""Validator behind register_multi_flags_validator method.
|
| 129 |
+
|
| 130 |
+
Validates that flag values pass their common checker function. The checker
|
| 131 |
+
function takes flag values and returns True (if values look fine) or,
|
| 132 |
+
if values are not valid, either returns False or raises an Exception.
|
| 133 |
+
"""
|
| 134 |
+
|
| 135 |
+
def __init__(self, flag_names, checker, message):
|
| 136 |
+
"""Constructor.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
flag_names: [str], containing names of the flags used by checker.
|
| 140 |
+
checker: function to verify the validator.
|
| 141 |
+
input - dict, with keys() being flag_names, and value for each
|
| 142 |
+
key being the value of the corresponding flag (string, boolean,
|
| 143 |
+
etc).
|
| 144 |
+
output - bool, True if validator constraint is satisfied.
|
| 145 |
+
If constraint is not satisfied, it should either return False or
|
| 146 |
+
raise flags.ValidationError(desired_error_message).
|
| 147 |
+
message: str, error message to be shown to the user if validator's
|
| 148 |
+
condition is not satisfied
|
| 149 |
+
"""
|
| 150 |
+
super().__init__(checker, message)
|
| 151 |
+
self.flag_names = flag_names
|
| 152 |
+
|
| 153 |
+
def _get_input_to_checker_function(self, flag_values):
|
| 154 |
+
"""Given flag values, returns the input to be given to checker.
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
flag_values: flags.FlagValues, the FlagValues instance to get flags from.
|
| 158 |
+
Returns:
|
| 159 |
+
dict, with keys() being self.flag_names, and value for each key
|
| 160 |
+
being the value of the corresponding flag (string, boolean, etc).
|
| 161 |
+
"""
|
| 162 |
+
return {key: flag_values[key].value for key in self.flag_names}
|
| 163 |
+
|
| 164 |
+
def print_flags_with_values(self, flag_values):
|
| 165 |
+
prefix = 'flags '
|
| 166 |
+
flags_with_values = []
|
| 167 |
+
for key in self.flag_names:
|
| 168 |
+
flags_with_values.append('%s=%s' % (key, flag_values[key].value))
|
| 169 |
+
return prefix + ', '.join(flags_with_values)
|
| 170 |
+
|
| 171 |
+
def get_flags_names(self):
|
| 172 |
+
return self.flag_names
|
.venv/lib/python3.13/site-packages/absl/flags/argparse_flags.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2018 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""This module provides argparse integration with absl.flags.
|
| 16 |
+
|
| 17 |
+
``argparse_flags.ArgumentParser`` is a drop-in replacement for
|
| 18 |
+
:class:`argparse.ArgumentParser`. It takes care of collecting and defining absl
|
| 19 |
+
flags in :mod:`argparse`.
|
| 20 |
+
|
| 21 |
+
Here is a simple example::
|
| 22 |
+
|
| 23 |
+
# Assume the following absl.flags is defined in another module:
|
| 24 |
+
#
|
| 25 |
+
# from absl import flags
|
| 26 |
+
# flags.DEFINE_string('echo', None, 'The echo message.')
|
| 27 |
+
#
|
| 28 |
+
parser = argparse_flags.ArgumentParser(
|
| 29 |
+
description='A demo of absl.flags and argparse integration.')
|
| 30 |
+
parser.add_argument('--header', help='Header message to print.')
|
| 31 |
+
|
| 32 |
+
# The parser will also accept the absl flag `--echo`.
|
| 33 |
+
# The `header` value is available as `args.header` just like a regular
|
| 34 |
+
# argparse flag. The absl flag `--echo` continues to be available via
|
| 35 |
+
# `absl.flags.FLAGS` if you want to access it.
|
| 36 |
+
args = parser.parse_args()
|
| 37 |
+
|
| 38 |
+
# Example usages:
|
| 39 |
+
# ./program --echo='A message.' --header='A header'
|
| 40 |
+
# ./program --header 'A header' --echo 'A message.'
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
Here is another example demonstrates subparsers::
|
| 44 |
+
|
| 45 |
+
parser = argparse_flags.ArgumentParser(description='A subcommands demo.')
|
| 46 |
+
parser.add_argument('--header', help='The header message to print.')
|
| 47 |
+
|
| 48 |
+
subparsers = parser.add_subparsers(help='The command to execute.')
|
| 49 |
+
|
| 50 |
+
roll_dice_parser = subparsers.add_parser(
|
| 51 |
+
'roll_dice', help='Roll a dice.',
|
| 52 |
+
# By default, absl flags can also be specified after the sub-command.
|
| 53 |
+
# To only allow them before sub-command, pass
|
| 54 |
+
# `inherited_absl_flags=None`.
|
| 55 |
+
inherited_absl_flags=None)
|
| 56 |
+
roll_dice_parser.add_argument('--num_faces', type=int, default=6)
|
| 57 |
+
roll_dice_parser.set_defaults(command=roll_dice)
|
| 58 |
+
|
| 59 |
+
shuffle_parser = subparsers.add_parser('shuffle', help='Shuffle inputs.')
|
| 60 |
+
shuffle_parser.add_argument(
|
| 61 |
+
'inputs', metavar='I', nargs='+', help='Inputs to shuffle.')
|
| 62 |
+
shuffle_parser.set_defaults(command=shuffle)
|
| 63 |
+
|
| 64 |
+
args = parser.parse_args(argv[1:])
|
| 65 |
+
args.command(args)
|
| 66 |
+
|
| 67 |
+
# Example usages:
|
| 68 |
+
# ./program --echo='A message.' roll_dice --num_faces=6
|
| 69 |
+
# ./program shuffle --echo='A message.' 1 2 3 4
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
There are several differences between :mod:`absl.flags` and
|
| 73 |
+
:mod:`~absl.flags.argparse_flags`:
|
| 74 |
+
|
| 75 |
+
1. Flags defined with absl.flags are parsed differently when using the
|
| 76 |
+
argparse parser. Notably:
|
| 77 |
+
|
| 78 |
+
1) absl.flags allows both single-dash and double-dash for any flag, and
|
| 79 |
+
doesn't distinguish them; argparse_flags only allows double-dash for
|
| 80 |
+
flag's regular name, and single-dash for flag's ``short_name``.
|
| 81 |
+
2) Boolean flags in absl.flags can be specified with ``--bool``,
|
| 82 |
+
``--nobool``, as well as ``--bool=true/false`` (though not recommended);
|
| 83 |
+
in argparse_flags, it only allows ``--bool``, ``--nobool``.
|
| 84 |
+
|
| 85 |
+
2. Help related flag differences:
|
| 86 |
+
|
| 87 |
+
1) absl.flags does not define help flags, absl.app does that; argparse_flags
|
| 88 |
+
defines help flags unless passed with ``add_help=False``.
|
| 89 |
+
2) absl.app supports ``--helpxml``; argparse_flags does not.
|
| 90 |
+
3) argparse_flags supports ``-h``; absl.app does not.
|
| 91 |
+
"""
|
| 92 |
+
|
| 93 |
+
import argparse
|
| 94 |
+
import sys
|
| 95 |
+
|
| 96 |
+
from absl import flags
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
_BUILT_IN_FLAGS = frozenset({
|
| 100 |
+
'help',
|
| 101 |
+
'helpshort',
|
| 102 |
+
'helpfull',
|
| 103 |
+
'helpxml',
|
| 104 |
+
'flagfile',
|
| 105 |
+
'undefok',
|
| 106 |
+
})
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class ArgumentParser(argparse.ArgumentParser):
|
| 110 |
+
"""Custom ArgumentParser class to support special absl flags."""
|
| 111 |
+
|
| 112 |
+
def __init__(self, **kwargs):
|
| 113 |
+
"""Initializes ArgumentParser.
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
**kwargs: same as argparse.ArgumentParser, except:
|
| 117 |
+
1. It also accepts `inherited_absl_flags`: the absl flags to inherit.
|
| 118 |
+
The default is the global absl.flags.FLAGS instance. Pass None to
|
| 119 |
+
ignore absl flags.
|
| 120 |
+
2. The `prefix_chars` argument must be the default value '-'.
|
| 121 |
+
|
| 122 |
+
Raises:
|
| 123 |
+
ValueError: Raised when prefix_chars is not '-'.
|
| 124 |
+
"""
|
| 125 |
+
prefix_chars = kwargs.get('prefix_chars', '-')
|
| 126 |
+
if prefix_chars != '-':
|
| 127 |
+
raise ValueError(
|
| 128 |
+
'argparse_flags.ArgumentParser only supports "-" as the prefix '
|
| 129 |
+
'character, found "{}".'.format(prefix_chars))
|
| 130 |
+
|
| 131 |
+
# Remove inherited_absl_flags before calling super.
|
| 132 |
+
self._inherited_absl_flags = kwargs.pop('inherited_absl_flags', flags.FLAGS)
|
| 133 |
+
# Now call super to initialize argparse.ArgumentParser before calling
|
| 134 |
+
# add_argument in _define_absl_flags.
|
| 135 |
+
super().__init__(**kwargs)
|
| 136 |
+
|
| 137 |
+
if self.add_help:
|
| 138 |
+
# -h and --help are defined in super.
|
| 139 |
+
# Also add the --helpshort and --helpfull flags.
|
| 140 |
+
self.add_argument(
|
| 141 |
+
# Action 'help' defines a similar flag to -h/--help.
|
| 142 |
+
'--helpshort', action='help',
|
| 143 |
+
default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
| 144 |
+
self.add_argument(
|
| 145 |
+
'--helpfull', action=_HelpFullAction,
|
| 146 |
+
default=argparse.SUPPRESS, help='show full help message and exit')
|
| 147 |
+
|
| 148 |
+
if self._inherited_absl_flags is not None:
|
| 149 |
+
self.add_argument(
|
| 150 |
+
'--undefok', default=argparse.SUPPRESS, help=argparse.SUPPRESS)
|
| 151 |
+
self._define_absl_flags(self._inherited_absl_flags)
|
| 152 |
+
|
| 153 |
+
def parse_known_args(self, args=None, namespace=None):
|
| 154 |
+
if args is None:
|
| 155 |
+
args = sys.argv[1:]
|
| 156 |
+
if self._inherited_absl_flags is not None:
|
| 157 |
+
# Handle --flagfile.
|
| 158 |
+
# Explicitly specify force_gnu=True, since argparse behaves like
|
| 159 |
+
# gnu_getopt: flags can be specified after positional arguments.
|
| 160 |
+
args = self._inherited_absl_flags.read_flags_from_files(
|
| 161 |
+
args, force_gnu=True)
|
| 162 |
+
|
| 163 |
+
undefok_missing = object()
|
| 164 |
+
undefok = getattr(namespace, 'undefok', undefok_missing)
|
| 165 |
+
|
| 166 |
+
namespace, args = super().parse_known_args(args, namespace)
|
| 167 |
+
|
| 168 |
+
# For Python <= 2.7.8: https://bugs.python.org/issue9351, a bug where
|
| 169 |
+
# sub-parsers don't preserve existing namespace attributes.
|
| 170 |
+
# Restore the undefok attribute if a sub-parser dropped it.
|
| 171 |
+
if undefok is not undefok_missing:
|
| 172 |
+
namespace.undefok = undefok
|
| 173 |
+
|
| 174 |
+
if self._inherited_absl_flags is not None:
|
| 175 |
+
# Handle --undefok. At this point, `args` only contains unknown flags,
|
| 176 |
+
# so it won't strip defined flags that are also specified with --undefok.
|
| 177 |
+
# For Python <= 2.7.8: https://bugs.python.org/issue9351, a bug where
|
| 178 |
+
# sub-parsers don't preserve existing namespace attributes. The undefok
|
| 179 |
+
# attribute might not exist because a subparser dropped it.
|
| 180 |
+
if hasattr(namespace, 'undefok'):
|
| 181 |
+
args = _strip_undefok_args(namespace.undefok, args)
|
| 182 |
+
# absl flags are not exposed in the Namespace object. See Namespace:
|
| 183 |
+
# https://docs.python.org/3/library/argparse.html#argparse.Namespace.
|
| 184 |
+
del namespace.undefok
|
| 185 |
+
self._inherited_absl_flags.mark_as_parsed()
|
| 186 |
+
try:
|
| 187 |
+
self._inherited_absl_flags.validate_all_flags()
|
| 188 |
+
except flags.IllegalFlagValueError as e:
|
| 189 |
+
self.error(str(e))
|
| 190 |
+
|
| 191 |
+
return namespace, args
|
| 192 |
+
|
| 193 |
+
def _define_absl_flags(self, absl_flags):
|
| 194 |
+
"""Defines flags from absl_flags."""
|
| 195 |
+
key_flags = set(absl_flags.get_key_flags_for_module(sys.argv[0]))
|
| 196 |
+
for name in absl_flags:
|
| 197 |
+
if name in _BUILT_IN_FLAGS:
|
| 198 |
+
# Do not inherit built-in flags.
|
| 199 |
+
continue
|
| 200 |
+
flag_instance = absl_flags[name]
|
| 201 |
+
# Each flags with short_name appears in FLAGS twice, so only define
|
| 202 |
+
# when the dictionary key is equal to the regular name.
|
| 203 |
+
if name == flag_instance.name:
|
| 204 |
+
# Suppress the flag in the help short message if it's not a main
|
| 205 |
+
# module's key flag.
|
| 206 |
+
suppress = flag_instance not in key_flags
|
| 207 |
+
self._define_absl_flag(flag_instance, suppress)
|
| 208 |
+
|
| 209 |
+
def _define_absl_flag(self, flag_instance, suppress):
|
| 210 |
+
"""Defines a flag from the flag_instance."""
|
| 211 |
+
flag_name = flag_instance.name
|
| 212 |
+
short_name = flag_instance.short_name
|
| 213 |
+
argument_names = ['--' + flag_name]
|
| 214 |
+
if short_name:
|
| 215 |
+
argument_names.insert(0, '-' + short_name)
|
| 216 |
+
if suppress:
|
| 217 |
+
helptext = argparse.SUPPRESS
|
| 218 |
+
else:
|
| 219 |
+
# argparse help string uses %-formatting. Escape the literal %'s.
|
| 220 |
+
helptext = flag_instance.help.replace('%', '%%')
|
| 221 |
+
if flag_instance.boolean:
|
| 222 |
+
# Only add the `no` form to the long name.
|
| 223 |
+
argument_names.append('--no' + flag_name)
|
| 224 |
+
self.add_argument(
|
| 225 |
+
*argument_names, action=_BooleanFlagAction, help=helptext,
|
| 226 |
+
metavar=flag_instance.name.upper(),
|
| 227 |
+
flag_instance=flag_instance)
|
| 228 |
+
else:
|
| 229 |
+
self.add_argument(
|
| 230 |
+
*argument_names, action=_FlagAction, help=helptext,
|
| 231 |
+
metavar=flag_instance.name.upper(),
|
| 232 |
+
flag_instance=flag_instance)
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class _FlagAction(argparse.Action):
|
| 236 |
+
"""Action class for Abseil non-boolean flags."""
|
| 237 |
+
|
| 238 |
+
def __init__(
|
| 239 |
+
self,
|
| 240 |
+
option_strings,
|
| 241 |
+
dest,
|
| 242 |
+
help, # pylint: disable=redefined-builtin
|
| 243 |
+
metavar,
|
| 244 |
+
flag_instance,
|
| 245 |
+
default=argparse.SUPPRESS):
|
| 246 |
+
"""Initializes _FlagAction.
|
| 247 |
+
|
| 248 |
+
Args:
|
| 249 |
+
option_strings: See argparse.Action.
|
| 250 |
+
dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS.
|
| 251 |
+
help: See argparse.Action.
|
| 252 |
+
metavar: See argparse.Action.
|
| 253 |
+
flag_instance: absl.flags.Flag, the absl flag instance.
|
| 254 |
+
default: Ignored. The flag always uses dest=argparse.SUPPRESS so it
|
| 255 |
+
doesn't affect the parsing result.
|
| 256 |
+
"""
|
| 257 |
+
del dest
|
| 258 |
+
self._flag_instance = flag_instance
|
| 259 |
+
super().__init__(
|
| 260 |
+
option_strings=option_strings,
|
| 261 |
+
dest=argparse.SUPPRESS,
|
| 262 |
+
help=help,
|
| 263 |
+
metavar=metavar,
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
def __call__(self, parser, namespace, values, option_string=None):
|
| 267 |
+
"""See https://docs.python.org/3/library/argparse.html#action-classes."""
|
| 268 |
+
self._flag_instance.parse(values)
|
| 269 |
+
self._flag_instance.using_default_value = False
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
class _BooleanFlagAction(argparse.Action):
|
| 273 |
+
"""Action class for Abseil boolean flags."""
|
| 274 |
+
|
| 275 |
+
def __init__(
|
| 276 |
+
self,
|
| 277 |
+
option_strings,
|
| 278 |
+
dest,
|
| 279 |
+
help, # pylint: disable=redefined-builtin
|
| 280 |
+
metavar,
|
| 281 |
+
flag_instance,
|
| 282 |
+
default=argparse.SUPPRESS):
|
| 283 |
+
"""Initializes _BooleanFlagAction.
|
| 284 |
+
|
| 285 |
+
Args:
|
| 286 |
+
option_strings: See argparse.Action.
|
| 287 |
+
dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS.
|
| 288 |
+
help: See argparse.Action.
|
| 289 |
+
metavar: See argparse.Action.
|
| 290 |
+
flag_instance: absl.flags.Flag, the absl flag instance.
|
| 291 |
+
default: Ignored. The flag always uses dest=argparse.SUPPRESS so it
|
| 292 |
+
doesn't affect the parsing result.
|
| 293 |
+
"""
|
| 294 |
+
del dest, default
|
| 295 |
+
self._flag_instance = flag_instance
|
| 296 |
+
flag_names = [self._flag_instance.name]
|
| 297 |
+
if self._flag_instance.short_name:
|
| 298 |
+
flag_names.append(self._flag_instance.short_name)
|
| 299 |
+
self._flag_names = frozenset(flag_names)
|
| 300 |
+
super().__init__(
|
| 301 |
+
option_strings=option_strings,
|
| 302 |
+
dest=argparse.SUPPRESS,
|
| 303 |
+
nargs=0, # Does not accept values, only `--bool` or `--nobool`.
|
| 304 |
+
help=help,
|
| 305 |
+
metavar=metavar,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
def __call__(self, parser, namespace, values, option_string=None):
|
| 309 |
+
"""See https://docs.python.org/3/library/argparse.html#action-classes."""
|
| 310 |
+
if not isinstance(values, list) or values:
|
| 311 |
+
raise ValueError('values must be an empty list.')
|
| 312 |
+
if option_string.startswith('--'):
|
| 313 |
+
option = option_string[2:]
|
| 314 |
+
else:
|
| 315 |
+
option = option_string[1:]
|
| 316 |
+
if option in self._flag_names:
|
| 317 |
+
self._flag_instance.parse('true')
|
| 318 |
+
else:
|
| 319 |
+
if not option.startswith('no') or option[2:] not in self._flag_names:
|
| 320 |
+
raise ValueError('invalid option_string: ' + option_string)
|
| 321 |
+
self._flag_instance.parse('false')
|
| 322 |
+
self._flag_instance.using_default_value = False
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
class _HelpFullAction(argparse.Action):
|
| 326 |
+
"""Action class for --helpfull flag."""
|
| 327 |
+
|
| 328 |
+
def __init__(self, option_strings, dest, default, help): # pylint: disable=redefined-builtin
|
| 329 |
+
"""Initializes _HelpFullAction.
|
| 330 |
+
|
| 331 |
+
Args:
|
| 332 |
+
option_strings: See argparse.Action.
|
| 333 |
+
dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS.
|
| 334 |
+
default: Ignored.
|
| 335 |
+
help: See argparse.Action.
|
| 336 |
+
"""
|
| 337 |
+
del dest, default
|
| 338 |
+
super().__init__(
|
| 339 |
+
option_strings=option_strings,
|
| 340 |
+
dest=argparse.SUPPRESS,
|
| 341 |
+
default=argparse.SUPPRESS,
|
| 342 |
+
nargs=0,
|
| 343 |
+
help=help,
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
def __call__(self, parser, namespace, values, option_string=None):
|
| 347 |
+
"""See https://docs.python.org/3/library/argparse.html#action-classes."""
|
| 348 |
+
# This only prints flags when help is not argparse.SUPPRESS.
|
| 349 |
+
# It includes user defined argparse flags, as well as main module's
|
| 350 |
+
# key absl flags. Other absl flags use argparse.SUPPRESS, so they aren't
|
| 351 |
+
# printed here.
|
| 352 |
+
parser.print_help()
|
| 353 |
+
|
| 354 |
+
absl_flags = parser._inherited_absl_flags # pylint: disable=protected-access
|
| 355 |
+
if absl_flags is not None:
|
| 356 |
+
modules = sorted(absl_flags.flags_by_module_dict())
|
| 357 |
+
main_module = sys.argv[0]
|
| 358 |
+
if main_module in modules:
|
| 359 |
+
# The main module flags are already printed in parser.print_help().
|
| 360 |
+
modules.remove(main_module)
|
| 361 |
+
print(absl_flags._get_help_for_modules( # pylint: disable=protected-access
|
| 362 |
+
modules, prefix='', include_special_flags=True))
|
| 363 |
+
parser.exit()
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def _strip_undefok_args(undefok, args):
|
| 367 |
+
"""Returns a new list of args after removing flags in --undefok."""
|
| 368 |
+
if undefok:
|
| 369 |
+
undefok_names = {name.strip() for name in undefok.split(',')}
|
| 370 |
+
undefok_names |= {'no' + name for name in undefok_names}
|
| 371 |
+
# Remove undefok flags.
|
| 372 |
+
args = [arg for arg in args if not _is_undefok(arg, undefok_names)]
|
| 373 |
+
return args
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def _is_undefok(arg, undefok_names):
|
| 377 |
+
"""Returns whether we can ignore arg based on a set of undefok flag names."""
|
| 378 |
+
if not arg.startswith('-'):
|
| 379 |
+
return False
|
| 380 |
+
if arg.startswith('--'):
|
| 381 |
+
arg_without_dash = arg[2:]
|
| 382 |
+
else:
|
| 383 |
+
arg_without_dash = arg[1:]
|
| 384 |
+
if '=' in arg_without_dash:
|
| 385 |
+
name, _ = arg_without_dash.split('=', 1)
|
| 386 |
+
else:
|
| 387 |
+
name = arg_without_dash
|
| 388 |
+
if name in undefok_names:
|
| 389 |
+
return True
|
| 390 |
+
return False
|
.venv/lib/python3.13/site-packages/absl/logging/__init__.py
ADDED
|
@@ -0,0 +1,1335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Abseil Python logging module implemented on top of standard logging.
|
| 16 |
+
|
| 17 |
+
Simple usage::
|
| 18 |
+
|
| 19 |
+
from absl import logging
|
| 20 |
+
|
| 21 |
+
logging.info('Interesting Stuff')
|
| 22 |
+
logging.info('Interesting Stuff with Arguments: %d', 42)
|
| 23 |
+
|
| 24 |
+
logging.set_verbosity(logging.INFO)
|
| 25 |
+
logging.log(logging.DEBUG, 'This will *not* be printed')
|
| 26 |
+
logging.set_verbosity(logging.DEBUG)
|
| 27 |
+
logging.log(logging.DEBUG, 'This will be printed')
|
| 28 |
+
|
| 29 |
+
logging.warning('Worrying Stuff')
|
| 30 |
+
logging.error('Alarming Stuff')
|
| 31 |
+
logging.fatal('AAAAHHHHH!!!!') # Process exits.
|
| 32 |
+
|
| 33 |
+
Usage note: Do not pre-format the strings in your program code.
|
| 34 |
+
Instead, let the logging module perform argument interpolation.
|
| 35 |
+
This saves cycles because strings that don't need to be printed
|
| 36 |
+
are never formatted. Note that this module does not attempt to
|
| 37 |
+
interpolate arguments when no arguments are given. In other words::
|
| 38 |
+
|
| 39 |
+
logging.info('Interesting Stuff: %s')
|
| 40 |
+
|
| 41 |
+
does not raise an exception because logging.info() has only one
|
| 42 |
+
argument, the message string.
|
| 43 |
+
|
| 44 |
+
"Lazy" evaluation for debugging
|
| 45 |
+
-------------------------------
|
| 46 |
+
|
| 47 |
+
If you do something like this::
|
| 48 |
+
|
| 49 |
+
logging.debug('Thing: %s', thing.ExpensiveOp())
|
| 50 |
+
|
| 51 |
+
then the ExpensiveOp will be evaluated even if nothing
|
| 52 |
+
is printed to the log. To avoid this, use the level_debug() function::
|
| 53 |
+
|
| 54 |
+
if logging.level_debug():
|
| 55 |
+
logging.debug('Thing: %s', thing.ExpensiveOp())
|
| 56 |
+
|
| 57 |
+
Per file level logging is supported by logging.vlog() and
|
| 58 |
+
logging.vlog_is_on(). For example::
|
| 59 |
+
|
| 60 |
+
if logging.vlog_is_on(2):
|
| 61 |
+
logging.vlog(2, very_expensive_debug_message())
|
| 62 |
+
|
| 63 |
+
Notes on Unicode
|
| 64 |
+
----------------
|
| 65 |
+
|
| 66 |
+
The log output is encoded as UTF-8. Don't pass data in other encodings in
|
| 67 |
+
bytes() instances -- instead pass unicode string instances when you need to
|
| 68 |
+
(for both the format string and arguments).
|
| 69 |
+
|
| 70 |
+
Note on critical and fatal:
|
| 71 |
+
Standard logging module defines fatal as an alias to critical, but it's not
|
| 72 |
+
documented, and it does NOT actually terminate the program.
|
| 73 |
+
This module only defines fatal but not critical, and it DOES terminate the
|
| 74 |
+
program.
|
| 75 |
+
|
| 76 |
+
The differences in behavior are historical and unfortunate.
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
import collections
|
| 80 |
+
from collections.abc import Mapping
|
| 81 |
+
import getpass
|
| 82 |
+
import inspect
|
| 83 |
+
import io
|
| 84 |
+
import itertools
|
| 85 |
+
import logging
|
| 86 |
+
import os
|
| 87 |
+
import socket
|
| 88 |
+
import struct
|
| 89 |
+
import sys
|
| 90 |
+
import tempfile
|
| 91 |
+
import threading
|
| 92 |
+
import time
|
| 93 |
+
import timeit
|
| 94 |
+
import traceback
|
| 95 |
+
import warnings
|
| 96 |
+
|
| 97 |
+
from absl import flags
|
| 98 |
+
from absl.logging import converter
|
| 99 |
+
|
| 100 |
+
# pylint: disable=g-import-not-at-top
|
| 101 |
+
try:
|
| 102 |
+
from typing import NoReturn
|
| 103 |
+
except ImportError:
|
| 104 |
+
pass
|
| 105 |
+
|
| 106 |
+
# pylint: enable=g-import-not-at-top
|
| 107 |
+
|
| 108 |
+
FLAGS = flags.FLAGS
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
# Logging levels.
|
| 112 |
+
FATAL = converter.ABSL_FATAL
|
| 113 |
+
ERROR = converter.ABSL_ERROR
|
| 114 |
+
WARNING = converter.ABSL_WARNING
|
| 115 |
+
WARN = converter.ABSL_WARNING # Deprecated name.
|
| 116 |
+
INFO = converter.ABSL_INFO
|
| 117 |
+
DEBUG = converter.ABSL_DEBUG
|
| 118 |
+
|
| 119 |
+
# Regex to match/parse log line prefixes.
|
| 120 |
+
ABSL_LOGGING_PREFIX_REGEX = (
|
| 121 |
+
r'^(?P<severity>[IWEF])'
|
| 122 |
+
r'(?P<month>\d\d)(?P<day>\d\d) '
|
| 123 |
+
r'(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)'
|
| 124 |
+
r'\.(?P<microsecond>\d\d\d\d\d\d) +'
|
| 125 |
+
r'(?P<thread_id>-?\d+) '
|
| 126 |
+
r'(?P<filename>[a-zA-Z<][\w._<>-]+):(?P<line>\d+)')
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# Mask to convert integer thread ids to unsigned quantities for logging purposes
|
| 130 |
+
_THREAD_ID_MASK = 2 ** (struct.calcsize('L') * 8) - 1
|
| 131 |
+
|
| 132 |
+
# Extra property set on the LogRecord created by ABSLLogger when its level is
|
| 133 |
+
# CRITICAL/FATAL.
|
| 134 |
+
_ABSL_LOG_FATAL = '_absl_log_fatal'
|
| 135 |
+
# Extra prefix added to the log message when a non-absl logger logs a
|
| 136 |
+
# CRITICAL/FATAL message.
|
| 137 |
+
_CRITICAL_PREFIX = 'CRITICAL - '
|
| 138 |
+
|
| 139 |
+
# Used by findCaller to skip callers from */logging/__init__.py.
|
| 140 |
+
_LOGGING_FILE_PREFIX = os.path.join('logging', '__init__.')
|
| 141 |
+
|
| 142 |
+
# The ABSL logger instance, initialized in _initialize().
|
| 143 |
+
_absl_logger = None
|
| 144 |
+
# The ABSL handler instance, initialized in _initialize().
|
| 145 |
+
_absl_handler = None
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
_CPP_NAME_TO_LEVELS = {
|
| 149 |
+
'debug': '0', # Abseil C++ has no DEBUG level, mapping it to INFO here.
|
| 150 |
+
'info': '0',
|
| 151 |
+
'warning': '1',
|
| 152 |
+
'warn': '1',
|
| 153 |
+
'error': '2',
|
| 154 |
+
'fatal': '3'
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
_CPP_LEVEL_TO_NAMES = {
|
| 158 |
+
'0': 'info',
|
| 159 |
+
'1': 'warning',
|
| 160 |
+
'2': 'error',
|
| 161 |
+
'3': 'fatal',
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
class _VerbosityFlag(flags.Flag):
|
| 166 |
+
"""Flag class for -v/--verbosity."""
|
| 167 |
+
|
| 168 |
+
def __init__(self, *args, **kwargs):
|
| 169 |
+
super().__init__(
|
| 170 |
+
flags.IntegerParser(), flags.ArgumentSerializer(), *args, **kwargs
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
@property
|
| 174 |
+
def value(self):
|
| 175 |
+
return self._value
|
| 176 |
+
|
| 177 |
+
@value.setter
|
| 178 |
+
def value(self, v):
|
| 179 |
+
self._value = v
|
| 180 |
+
self._update_logging_levels()
|
| 181 |
+
|
| 182 |
+
def _update_logging_levels(self):
|
| 183 |
+
"""Updates absl logging levels to the current verbosity.
|
| 184 |
+
|
| 185 |
+
Visibility: module-private
|
| 186 |
+
"""
|
| 187 |
+
if not _absl_logger:
|
| 188 |
+
return
|
| 189 |
+
|
| 190 |
+
if self._value <= converter.ABSL_DEBUG:
|
| 191 |
+
standard_verbosity = converter.absl_to_standard(self._value)
|
| 192 |
+
else:
|
| 193 |
+
# --verbosity is set to higher than 1 for vlog.
|
| 194 |
+
standard_verbosity = logging.DEBUG - (self._value - 1)
|
| 195 |
+
|
| 196 |
+
# Also update root level when absl_handler is used.
|
| 197 |
+
if _absl_handler in logging.root.handlers:
|
| 198 |
+
# Make absl logger inherit from the root logger. absl logger might have
|
| 199 |
+
# a non-NOTSET value if logging.set_verbosity() is called at import time.
|
| 200 |
+
_absl_logger.setLevel(logging.NOTSET)
|
| 201 |
+
logging.root.setLevel(standard_verbosity)
|
| 202 |
+
else:
|
| 203 |
+
_absl_logger.setLevel(standard_verbosity)
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
class _LoggerLevelsFlag(flags.Flag):
|
| 207 |
+
"""Flag class for --logger_levels."""
|
| 208 |
+
|
| 209 |
+
def __init__(self, *args, **kwargs):
|
| 210 |
+
super().__init__(
|
| 211 |
+
_LoggerLevelsParser(), _LoggerLevelsSerializer(), *args, **kwargs
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
@property
|
| 215 |
+
def value(self):
|
| 216 |
+
# For lack of an immutable type, be defensive and return a copy.
|
| 217 |
+
# Modifications to the dict aren't supported and won't have any affect.
|
| 218 |
+
# While Py3 could use MappingProxyType, that isn't deepcopy friendly, so
|
| 219 |
+
# just return a copy.
|
| 220 |
+
return self._value.copy()
|
| 221 |
+
|
| 222 |
+
@value.setter
|
| 223 |
+
def value(self, v):
|
| 224 |
+
self._value = {} if v is None else v
|
| 225 |
+
self._update_logger_levels()
|
| 226 |
+
|
| 227 |
+
def _update_logger_levels(self):
|
| 228 |
+
# Visibility: module-private.
|
| 229 |
+
# This is called by absl.app.run() during initialization.
|
| 230 |
+
for name, level in self._value.items():
|
| 231 |
+
logging.getLogger(name).setLevel(level)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
class _LoggerLevelsParser(flags.ArgumentParser):
|
| 235 |
+
"""Parser for --logger_levels flag."""
|
| 236 |
+
|
| 237 |
+
def parse(self, value):
|
| 238 |
+
if isinstance(value, Mapping):
|
| 239 |
+
return value
|
| 240 |
+
|
| 241 |
+
pairs = [pair.strip() for pair in value.split(',') if pair.strip()]
|
| 242 |
+
|
| 243 |
+
# Preserve the order so that serialization is deterministic.
|
| 244 |
+
levels = collections.OrderedDict()
|
| 245 |
+
for name_level in pairs:
|
| 246 |
+
name, level = name_level.split(':', 1)
|
| 247 |
+
name = name.strip()
|
| 248 |
+
level = level.strip()
|
| 249 |
+
levels[name] = level
|
| 250 |
+
return levels
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
class _LoggerLevelsSerializer:
|
| 254 |
+
"""Serializer for --logger_levels flag."""
|
| 255 |
+
|
| 256 |
+
def serialize(self, value):
|
| 257 |
+
if isinstance(value, str):
|
| 258 |
+
return value
|
| 259 |
+
return ','.join(f'{name}:{level}' for name, level in value.items())
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
class _StderrthresholdFlag(flags.Flag):
|
| 263 |
+
"""Flag class for --stderrthreshold."""
|
| 264 |
+
|
| 265 |
+
def __init__(self, *args, **kwargs):
|
| 266 |
+
super().__init__(
|
| 267 |
+
flags.ArgumentParser(), flags.ArgumentSerializer(), *args, **kwargs
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
@property
|
| 271 |
+
def value(self):
|
| 272 |
+
return self._value
|
| 273 |
+
|
| 274 |
+
@value.setter
|
| 275 |
+
def value(self, v):
|
| 276 |
+
if v in _CPP_LEVEL_TO_NAMES:
|
| 277 |
+
# --stderrthreshold also accepts numeric strings whose values are
|
| 278 |
+
# Abseil C++ log levels.
|
| 279 |
+
cpp_value = int(v)
|
| 280 |
+
v = _CPP_LEVEL_TO_NAMES[v] # Normalize to strings.
|
| 281 |
+
elif v.lower() in _CPP_NAME_TO_LEVELS:
|
| 282 |
+
v = v.lower()
|
| 283 |
+
if v == 'warn':
|
| 284 |
+
v = 'warning' # Use 'warning' as the canonical name.
|
| 285 |
+
cpp_value = int(_CPP_NAME_TO_LEVELS[v])
|
| 286 |
+
else:
|
| 287 |
+
raise ValueError(
|
| 288 |
+
'--stderrthreshold must be one of (case-insensitive) '
|
| 289 |
+
"'debug', 'info', 'warning', 'error', 'fatal', "
|
| 290 |
+
"or '0', '1', '2', '3', not '%s'" % v)
|
| 291 |
+
|
| 292 |
+
self._value = v
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
LOGTOSTDERR = flags.DEFINE_boolean(
|
| 296 |
+
'logtostderr',
|
| 297 |
+
False,
|
| 298 |
+
'Should only log to stderr?',
|
| 299 |
+
allow_override_cpp=True,
|
| 300 |
+
)
|
| 301 |
+
ALSOLOGTOSTDERR = flags.DEFINE_boolean(
|
| 302 |
+
'alsologtostderr',
|
| 303 |
+
False,
|
| 304 |
+
'also log to stderr?',
|
| 305 |
+
allow_override_cpp=True,
|
| 306 |
+
)
|
| 307 |
+
LOG_DIR = flags.DEFINE_string(
|
| 308 |
+
'log_dir',
|
| 309 |
+
os.getenv('TEST_TMPDIR', ''),
|
| 310 |
+
'directory to write logfiles into',
|
| 311 |
+
allow_override_cpp=True,
|
| 312 |
+
)
|
| 313 |
+
VERBOSITY = flags.DEFINE_flag(
|
| 314 |
+
_VerbosityFlag(
|
| 315 |
+
'verbosity',
|
| 316 |
+
-1,
|
| 317 |
+
(
|
| 318 |
+
'Logging verbosity level. Messages logged at this level or lower'
|
| 319 |
+
' will be included. Set to 1 for debug logging. If the flag was not'
|
| 320 |
+
' set or supplied, the value will be changed from the default of -1'
|
| 321 |
+
' (warning) to 0 (info) after flags are parsed.'
|
| 322 |
+
),
|
| 323 |
+
short_name='v',
|
| 324 |
+
allow_hide_cpp=True,
|
| 325 |
+
)
|
| 326 |
+
)
|
| 327 |
+
LOGGER_LEVELS = flags.DEFINE_flag(
|
| 328 |
+
_LoggerLevelsFlag(
|
| 329 |
+
'logger_levels',
|
| 330 |
+
{},
|
| 331 |
+
(
|
| 332 |
+
'Specify log level of loggers. The format is a CSV list of '
|
| 333 |
+
'`name:level`. Where `name` is the logger name used with '
|
| 334 |
+
'`logging.getLogger()`, and `level` is a level name (INFO, DEBUG, '
|
| 335 |
+
'etc). e.g. `myapp.foo:INFO,other.logger:DEBUG`'
|
| 336 |
+
),
|
| 337 |
+
)
|
| 338 |
+
)
|
| 339 |
+
STDERRTHRESHOLD = flags.DEFINE_flag(
|
| 340 |
+
_StderrthresholdFlag(
|
| 341 |
+
'stderrthreshold',
|
| 342 |
+
'fatal',
|
| 343 |
+
(
|
| 344 |
+
'log messages at this level, or more severe, to stderr in '
|
| 345 |
+
'addition to the logfile. Possible values are '
|
| 346 |
+
"'debug', 'info', 'warning', 'error', and 'fatal'. "
|
| 347 |
+
'Obsoletes --alsologtostderr. Using --alsologtostderr '
|
| 348 |
+
'cancels the effect of this flag. Please also note that '
|
| 349 |
+
'this flag is subject to --verbosity and requires logfile '
|
| 350 |
+
'not be stderr.'
|
| 351 |
+
),
|
| 352 |
+
allow_hide_cpp=True,
|
| 353 |
+
)
|
| 354 |
+
)
|
| 355 |
+
SHOWPREFIXFORINFO = flags.DEFINE_boolean(
|
| 356 |
+
'showprefixforinfo',
|
| 357 |
+
True,
|
| 358 |
+
(
|
| 359 |
+
'If False, do not prepend prefix to info messages '
|
| 360 |
+
"when it's logged to stderr, "
|
| 361 |
+
'--verbosity is set to INFO level, '
|
| 362 |
+
'and python logging is used.'
|
| 363 |
+
),
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
def get_verbosity():
|
| 368 |
+
"""Returns the logging verbosity."""
|
| 369 |
+
return FLAGS['verbosity'].value
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def set_verbosity(v):
|
| 373 |
+
"""Sets the logging verbosity.
|
| 374 |
+
|
| 375 |
+
Causes all messages of level <= v to be logged,
|
| 376 |
+
and all messages of level > v to be silently discarded.
|
| 377 |
+
|
| 378 |
+
Args:
|
| 379 |
+
v: int|str, the verbosity level as an integer or string. Legal string values
|
| 380 |
+
are those that can be coerced to an integer as well as case-insensitive
|
| 381 |
+
'debug', 'info', 'warning', 'error', and 'fatal'.
|
| 382 |
+
"""
|
| 383 |
+
try:
|
| 384 |
+
new_level = int(v)
|
| 385 |
+
except ValueError:
|
| 386 |
+
new_level = converter.ABSL_NAMES[v.upper()]
|
| 387 |
+
FLAGS.verbosity = new_level
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def set_stderrthreshold(s):
|
| 391 |
+
"""Sets the stderr threshold to the value passed in.
|
| 392 |
+
|
| 393 |
+
Args:
|
| 394 |
+
s: str|int, valid strings values are case-insensitive 'debug',
|
| 395 |
+
'info', 'warning', 'error', and 'fatal'; valid integer values are
|
| 396 |
+
logging.DEBUG|INFO|WARNING|ERROR|FATAL.
|
| 397 |
+
|
| 398 |
+
Raises:
|
| 399 |
+
ValueError: Raised when s is an invalid value.
|
| 400 |
+
"""
|
| 401 |
+
if s in converter.ABSL_LEVELS:
|
| 402 |
+
FLAGS.stderrthreshold = converter.ABSL_LEVELS[s]
|
| 403 |
+
elif isinstance(s, str) and s.upper() in converter.ABSL_NAMES:
|
| 404 |
+
FLAGS.stderrthreshold = s
|
| 405 |
+
else:
|
| 406 |
+
raise ValueError(
|
| 407 |
+
'set_stderrthreshold only accepts integer absl logging level '
|
| 408 |
+
'from -3 to 1, or case-insensitive string values '
|
| 409 |
+
"'debug', 'info', 'warning', 'error', and 'fatal'. "
|
| 410 |
+
'But found "{}" ({}).'.format(s, type(s)))
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
def fatal(msg, *args, **kwargs):
|
| 414 |
+
# type: (Any, Any, Any) -> NoReturn
|
| 415 |
+
"""Logs a fatal message."""
|
| 416 |
+
log(FATAL, msg, *args, **kwargs)
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
def error(msg, *args, **kwargs):
|
| 420 |
+
"""Logs an error message."""
|
| 421 |
+
log(ERROR, msg, *args, **kwargs)
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
def warning(msg, *args, **kwargs):
|
| 425 |
+
"""Logs a warning message."""
|
| 426 |
+
log(WARNING, msg, *args, **kwargs)
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
def warn(msg, *args, **kwargs):
|
| 430 |
+
"""Deprecated, use 'warning' instead."""
|
| 431 |
+
warnings.warn("The 'warn' function is deprecated, use 'warning' instead",
|
| 432 |
+
DeprecationWarning, 2)
|
| 433 |
+
log(WARNING, msg, *args, **kwargs)
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def info(msg, *args, **kwargs):
|
| 437 |
+
"""Logs an info message."""
|
| 438 |
+
log(INFO, msg, *args, **kwargs)
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
def debug(msg, *args, **kwargs):
|
| 442 |
+
"""Logs a debug message."""
|
| 443 |
+
log(DEBUG, msg, *args, **kwargs)
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def exception(msg, *args, exc_info=True, **kwargs):
|
| 447 |
+
"""Logs an exception, with traceback and message."""
|
| 448 |
+
error(msg, *args, exc_info=exc_info, **kwargs)
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
def _fast_stack_trace():
|
| 452 |
+
"""A fast stack trace that gets us the minimal information we need.
|
| 453 |
+
|
| 454 |
+
Compared to using `get_absl_logger().findCaller(stack_info=True)`, this
|
| 455 |
+
function is ~100x faster.
|
| 456 |
+
|
| 457 |
+
Returns:
|
| 458 |
+
A tuple of tuples of (filename, line_number, last_instruction_offset).
|
| 459 |
+
"""
|
| 460 |
+
cur_stack = inspect.currentframe()
|
| 461 |
+
if cur_stack is None or cur_stack.f_back is None:
|
| 462 |
+
return tuple()
|
| 463 |
+
# We drop the first frame, which is this function itself.
|
| 464 |
+
cur_stack = cur_stack.f_back
|
| 465 |
+
call_stack = []
|
| 466 |
+
while cur_stack.f_back:
|
| 467 |
+
cur_stack = cur_stack.f_back
|
| 468 |
+
call_stack.append(
|
| 469 |
+
(cur_stack.f_code.co_filename, cur_stack.f_lineno, cur_stack.f_lasti)
|
| 470 |
+
)
|
| 471 |
+
return tuple(call_stack)
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
# Counter to keep track of number of log entries per token.
|
| 475 |
+
_log_counter_per_token = {}
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def _get_next_log_count_per_token(token):
|
| 479 |
+
"""Wrapper for _log_counter_per_token. Thread-safe.
|
| 480 |
+
|
| 481 |
+
Args:
|
| 482 |
+
token: The token for which to look up the count.
|
| 483 |
+
|
| 484 |
+
Returns:
|
| 485 |
+
The number of times this function has been called with
|
| 486 |
+
*token* as an argument (starting at 0).
|
| 487 |
+
"""
|
| 488 |
+
# Can't use a defaultdict because defaultdict isn't atomic, whereas
|
| 489 |
+
# setdefault is.
|
| 490 |
+
return next(_log_counter_per_token.setdefault(token, itertools.count()))
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
def log_every_n(level, msg, n, *args, use_call_stack=False, **kwargs):
|
| 494 |
+
"""Logs ``msg % args`` at level 'level' once per 'n' times.
|
| 495 |
+
|
| 496 |
+
Logs the 1st call, (N+1)st call, (2N+1)st call, etc.
|
| 497 |
+
Not threadsafe.
|
| 498 |
+
|
| 499 |
+
Args:
|
| 500 |
+
level: int, the absl logging level at which to log.
|
| 501 |
+
msg: str, the message to be logged.
|
| 502 |
+
n: int, the number of times this should be called before it is logged.
|
| 503 |
+
*args: The args to be substituted into the msg.
|
| 504 |
+
use_call_stack: bool, whether to include the call stack when counting the
|
| 505 |
+
number of times the message is logged.
|
| 506 |
+
**kwargs: May contain exc_info to add exception traceback to message.
|
| 507 |
+
"""
|
| 508 |
+
caller_info = get_absl_logger().findCaller()
|
| 509 |
+
if use_call_stack:
|
| 510 |
+
# To reduce storage costs, we hash the call stack.
|
| 511 |
+
caller_info = (*caller_info[0:3], hash(_fast_stack_trace()))
|
| 512 |
+
count = _get_next_log_count_per_token(caller_info)
|
| 513 |
+
log_if(level, msg, not (count % n), *args, **kwargs)
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
# Keeps track of the last log time of the given token.
|
| 517 |
+
# Note: must be a dict since set/get is atomic in CPython.
|
| 518 |
+
# Note: entries are never released as their number is expected to be low.
|
| 519 |
+
_log_timer_per_token = {}
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def _seconds_have_elapsed(token, num_seconds):
|
| 523 |
+
"""Tests if 'num_seconds' have passed since 'token' was requested.
|
| 524 |
+
|
| 525 |
+
Not strictly thread-safe - may log with the wrong frequency if called
|
| 526 |
+
concurrently from multiple threads. Accuracy depends on resolution of
|
| 527 |
+
'timeit.default_timer()'.
|
| 528 |
+
|
| 529 |
+
Always returns True on the first call for a given 'token'.
|
| 530 |
+
|
| 531 |
+
Args:
|
| 532 |
+
token: The token for which to look up the count.
|
| 533 |
+
num_seconds: The number of seconds to test for.
|
| 534 |
+
|
| 535 |
+
Returns:
|
| 536 |
+
Whether it has been >= 'num_seconds' since 'token' was last requested.
|
| 537 |
+
"""
|
| 538 |
+
now = timeit.default_timer()
|
| 539 |
+
then = _log_timer_per_token.get(token, None)
|
| 540 |
+
if then is None or (now - then) >= num_seconds:
|
| 541 |
+
_log_timer_per_token[token] = now
|
| 542 |
+
return True
|
| 543 |
+
else:
|
| 544 |
+
return False
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def log_every_n_seconds(
|
| 548 |
+
level, msg, n_seconds, *args, use_call_stack=False, **kwargs
|
| 549 |
+
):
|
| 550 |
+
"""Logs ``msg % args`` at level ``level`` iff ``n_seconds`` elapsed since last call.
|
| 551 |
+
|
| 552 |
+
Logs the first call, logs subsequent calls if 'n' seconds have elapsed since
|
| 553 |
+
the last logging call from the same call site (file + line). Not thread-safe.
|
| 554 |
+
|
| 555 |
+
Args:
|
| 556 |
+
level: int, the absl logging level at which to log.
|
| 557 |
+
msg: str, the message to be logged.
|
| 558 |
+
n_seconds: float or int, seconds which should elapse before logging again.
|
| 559 |
+
*args: The args to be substituted into the msg.
|
| 560 |
+
use_call_stack: bool, whether to include the call stack when counting the
|
| 561 |
+
number of times the message is logged.
|
| 562 |
+
**kwargs: May contain exc_info to add exception traceback to message.
|
| 563 |
+
"""
|
| 564 |
+
caller_info = get_absl_logger().findCaller()
|
| 565 |
+
if use_call_stack:
|
| 566 |
+
# To reduce storage costs, we hash the call stack.
|
| 567 |
+
caller_info = (*caller_info[0:3], hash(_fast_stack_trace()))
|
| 568 |
+
should_log = _seconds_have_elapsed(caller_info, n_seconds)
|
| 569 |
+
log_if(level, msg, should_log, *args, **kwargs)
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
def log_first_n(level, msg, n, *args, use_call_stack=False, **kwargs):
|
| 573 |
+
"""Logs ``msg % args`` at level ``level`` only first ``n`` times.
|
| 574 |
+
|
| 575 |
+
Not threadsafe.
|
| 576 |
+
|
| 577 |
+
Args:
|
| 578 |
+
level: int, the absl logging level at which to log.
|
| 579 |
+
msg: str, the message to be logged.
|
| 580 |
+
n: int, the maximal number of times the message is logged.
|
| 581 |
+
*args: The args to be substituted into the msg.
|
| 582 |
+
use_call_stack: bool, whether to include the call stack when counting the
|
| 583 |
+
number of times the message is logged.
|
| 584 |
+
**kwargs: May contain exc_info to add exception traceback to message.
|
| 585 |
+
"""
|
| 586 |
+
caller_info = get_absl_logger().findCaller()
|
| 587 |
+
if use_call_stack:
|
| 588 |
+
# To reduce storage costs, we hash the call stack.
|
| 589 |
+
caller_info = (*caller_info[0:3], hash(_fast_stack_trace()))
|
| 590 |
+
count = _get_next_log_count_per_token(caller_info)
|
| 591 |
+
log_if(level, msg, count < n, *args, **kwargs)
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
def log_if(level, msg, condition, *args, **kwargs):
|
| 595 |
+
"""Logs ``msg % args`` at level ``level`` only if condition is fulfilled."""
|
| 596 |
+
if condition:
|
| 597 |
+
log(level, msg, *args, **kwargs)
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
def log(level, msg, *args, **kwargs):
|
| 601 |
+
"""Logs ``msg % args`` at absl logging level ``level``.
|
| 602 |
+
|
| 603 |
+
If no args are given just print msg, ignoring any interpolation specifiers.
|
| 604 |
+
|
| 605 |
+
Args:
|
| 606 |
+
level: int, the absl logging level at which to log the message
|
| 607 |
+
(logging.DEBUG|INFO|WARNING|ERROR|FATAL). While some C++ verbose logging
|
| 608 |
+
level constants are also supported, callers should prefer explicit
|
| 609 |
+
logging.vlog() calls for such purpose.
|
| 610 |
+
|
| 611 |
+
msg: str, the message to be logged.
|
| 612 |
+
*args: The args to be substituted into the msg.
|
| 613 |
+
**kwargs: May contain exc_info to add exception traceback to message.
|
| 614 |
+
"""
|
| 615 |
+
if level > converter.ABSL_DEBUG:
|
| 616 |
+
# Even though this function supports level that is greater than 1, users
|
| 617 |
+
# should use logging.vlog instead for such cases.
|
| 618 |
+
# Treat this as vlog, 1 is equivalent to DEBUG.
|
| 619 |
+
standard_level = converter.STANDARD_DEBUG - (level - 1)
|
| 620 |
+
else:
|
| 621 |
+
if level < converter.ABSL_FATAL:
|
| 622 |
+
level = converter.ABSL_FATAL
|
| 623 |
+
standard_level = converter.absl_to_standard(level)
|
| 624 |
+
|
| 625 |
+
# Match standard logging's behavior. Before use_absl_handler() and
|
| 626 |
+
# logging is configured, there is no handler attached on _absl_logger nor
|
| 627 |
+
# logging.root. So logs go no where.
|
| 628 |
+
if not logging.root.handlers:
|
| 629 |
+
logging.basicConfig()
|
| 630 |
+
|
| 631 |
+
_absl_logger.log(standard_level, msg, *args, **kwargs)
|
| 632 |
+
|
| 633 |
+
|
| 634 |
+
def vlog(level, msg, *args, **kwargs):
|
| 635 |
+
"""Log ``msg % args`` at C++ vlog level ``level``.
|
| 636 |
+
|
| 637 |
+
Args:
|
| 638 |
+
level: int, the C++ verbose logging level at which to log the message,
|
| 639 |
+
e.g. 1, 2, 3, 4... While absl level constants are also supported,
|
| 640 |
+
callers should prefer logging.log|debug|info|... calls for such purpose.
|
| 641 |
+
msg: str, the message to be logged.
|
| 642 |
+
*args: The args to be substituted into the msg.
|
| 643 |
+
**kwargs: May contain exc_info to add exception traceback to message.
|
| 644 |
+
"""
|
| 645 |
+
log(level, msg, *args, **kwargs)
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
def vlog_is_on(level):
|
| 649 |
+
"""Checks if vlog is enabled for the given level in caller's source file.
|
| 650 |
+
|
| 651 |
+
Args:
|
| 652 |
+
level: int, the C++ verbose logging level at which to log the message,
|
| 653 |
+
e.g. 1, 2, 3, 4... While absl level constants are also supported,
|
| 654 |
+
callers should prefer level_debug|level_info|... calls for
|
| 655 |
+
checking those.
|
| 656 |
+
|
| 657 |
+
Returns:
|
| 658 |
+
True if logging is turned on for that level.
|
| 659 |
+
"""
|
| 660 |
+
|
| 661 |
+
if level > converter.ABSL_DEBUG:
|
| 662 |
+
# Even though this function supports level that is greater than 1, users
|
| 663 |
+
# should use logging.vlog instead for such cases.
|
| 664 |
+
# Treat this as vlog, 1 is equivalent to DEBUG.
|
| 665 |
+
standard_level = converter.STANDARD_DEBUG - (level - 1)
|
| 666 |
+
else:
|
| 667 |
+
if level < converter.ABSL_FATAL:
|
| 668 |
+
level = converter.ABSL_FATAL
|
| 669 |
+
standard_level = converter.absl_to_standard(level)
|
| 670 |
+
return _absl_logger.isEnabledFor(standard_level)
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def flush():
|
| 674 |
+
"""Flushes all log files."""
|
| 675 |
+
get_absl_handler().flush()
|
| 676 |
+
|
| 677 |
+
|
| 678 |
+
def level_debug():
|
| 679 |
+
"""Returns True if debug logging is turned on."""
|
| 680 |
+
return get_verbosity() >= DEBUG
|
| 681 |
+
|
| 682 |
+
|
| 683 |
+
def level_info():
|
| 684 |
+
"""Returns True if info logging is turned on."""
|
| 685 |
+
return get_verbosity() >= INFO
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
def level_warning():
|
| 689 |
+
"""Returns True if warning logging is turned on."""
|
| 690 |
+
return get_verbosity() >= WARNING
|
| 691 |
+
|
| 692 |
+
|
| 693 |
+
level_warn = level_warning # Deprecated function.
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
def level_error():
|
| 697 |
+
"""Returns True if error logging is turned on."""
|
| 698 |
+
return get_verbosity() >= ERROR
|
| 699 |
+
|
| 700 |
+
|
| 701 |
+
def get_log_file_name(level=INFO):
|
| 702 |
+
"""Returns the name of the log file.
|
| 703 |
+
|
| 704 |
+
For Python logging, only one file is used and level is ignored. And it returns
|
| 705 |
+
empty string if it logs to stderr/stdout or the log stream has no `name`
|
| 706 |
+
attribute.
|
| 707 |
+
|
| 708 |
+
Args:
|
| 709 |
+
level: int, the absl.logging level.
|
| 710 |
+
|
| 711 |
+
Raises:
|
| 712 |
+
ValueError: Raised when `level` has an invalid value.
|
| 713 |
+
"""
|
| 714 |
+
if level not in converter.ABSL_LEVELS:
|
| 715 |
+
raise ValueError(f'Invalid absl.logging level {level}')
|
| 716 |
+
stream = get_absl_handler().python_handler.stream
|
| 717 |
+
if (stream == sys.stderr or stream == sys.stdout or
|
| 718 |
+
not hasattr(stream, 'name')):
|
| 719 |
+
return ''
|
| 720 |
+
else:
|
| 721 |
+
return stream.name
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
def find_log_dir_and_names(program_name=None, log_dir=None):
|
| 725 |
+
"""Computes the directory and filename prefix for log file.
|
| 726 |
+
|
| 727 |
+
Args:
|
| 728 |
+
program_name: str|None, the filename part of the path to the program that is
|
| 729 |
+
running without its extension. e.g: if your program is called
|
| 730 |
+
``usr/bin/foobar.py`` this method should probably be called with
|
| 731 |
+
``program_name='foobar`` However, this is just a convention, you can pass
|
| 732 |
+
in any string you want, and it will be used as part of the log filename.
|
| 733 |
+
If you don't pass in anything, the default behavior is as described in the
|
| 734 |
+
example. In python standard logging mode, the program_name will be
|
| 735 |
+
prepended with ``py_`` if it is the ``program_name`` argument is omitted.
|
| 736 |
+
log_dir: str|None, the desired log directory.
|
| 737 |
+
|
| 738 |
+
Returns:
|
| 739 |
+
(log_dir, file_prefix, symlink_prefix)
|
| 740 |
+
|
| 741 |
+
Raises:
|
| 742 |
+
FileNotFoundError: raised when it cannot find a log directory.
|
| 743 |
+
"""
|
| 744 |
+
if not program_name:
|
| 745 |
+
# Strip the extension (foobar.par becomes foobar, and
|
| 746 |
+
# fubar.py becomes fubar). We do this so that the log
|
| 747 |
+
# file names are similar to C++ log file names.
|
| 748 |
+
program_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
|
| 749 |
+
|
| 750 |
+
# Prepend py_ to files so that python code gets a unique file, and
|
| 751 |
+
# so that C++ libraries do not try to write to the same log files as us.
|
| 752 |
+
program_name = 'py_%s' % program_name
|
| 753 |
+
|
| 754 |
+
actual_log_dir = find_log_dir(log_dir=log_dir)
|
| 755 |
+
|
| 756 |
+
try:
|
| 757 |
+
username = getpass.getuser()
|
| 758 |
+
except KeyError:
|
| 759 |
+
# This can happen, e.g. when running under docker w/o passwd file.
|
| 760 |
+
if hasattr(os, 'getuid'):
|
| 761 |
+
# Windows doesn't have os.getuid
|
| 762 |
+
username = str(os.getuid())
|
| 763 |
+
else:
|
| 764 |
+
username = 'unknown'
|
| 765 |
+
hostname = socket.gethostname()
|
| 766 |
+
file_prefix = '%s.%s.%s.log' % (program_name, hostname, username)
|
| 767 |
+
|
| 768 |
+
return actual_log_dir, file_prefix, program_name
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
def find_log_dir(log_dir=None):
|
| 772 |
+
"""Returns the most suitable directory to put log files into.
|
| 773 |
+
|
| 774 |
+
Args:
|
| 775 |
+
log_dir: str|None, if specified, the logfile(s) will be created in that
|
| 776 |
+
directory. Otherwise if the --log_dir command-line flag is provided, the
|
| 777 |
+
logfile will be created in that directory. Otherwise the logfile will be
|
| 778 |
+
created in a standard location.
|
| 779 |
+
|
| 780 |
+
Raises:
|
| 781 |
+
FileNotFoundError: raised when it cannot find a log directory.
|
| 782 |
+
"""
|
| 783 |
+
# Get a list of possible log dirs (will try to use them in order).
|
| 784 |
+
# NOTE: Google's internal implementation has a special handling for Google
|
| 785 |
+
# machines, which uses a list of directories. Hence the following uses `dirs`
|
| 786 |
+
# instead of a single directory.
|
| 787 |
+
if log_dir:
|
| 788 |
+
# log_dir was explicitly specified as an arg, so use it and it alone.
|
| 789 |
+
dirs = [log_dir]
|
| 790 |
+
elif FLAGS['log_dir'].value:
|
| 791 |
+
# log_dir flag was provided, so use it and it alone (this mimics the
|
| 792 |
+
# behavior of the same flag in logging.cc).
|
| 793 |
+
dirs = [FLAGS['log_dir'].value]
|
| 794 |
+
else:
|
| 795 |
+
dirs = [tempfile.gettempdir()]
|
| 796 |
+
|
| 797 |
+
# Find the first usable log dir.
|
| 798 |
+
for d in dirs:
|
| 799 |
+
if os.path.isdir(d) and os.access(d, os.W_OK):
|
| 800 |
+
return d
|
| 801 |
+
raise FileNotFoundError(
|
| 802 |
+
"Can't find a writable directory for logs, tried %s" % dirs)
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
def get_absl_log_prefix(record):
|
| 806 |
+
"""Returns the absl log prefix for the log record.
|
| 807 |
+
|
| 808 |
+
Args:
|
| 809 |
+
record: logging.LogRecord, the record to get prefix for.
|
| 810 |
+
"""
|
| 811 |
+
created_tuple = time.localtime(record.created)
|
| 812 |
+
created_microsecond = int(record.created % 1.0 * 1e6)
|
| 813 |
+
|
| 814 |
+
critical_prefix = ''
|
| 815 |
+
level = record.levelno
|
| 816 |
+
if _is_non_absl_fatal_record(record):
|
| 817 |
+
# When the level is FATAL, but not logged from absl, lower the level so
|
| 818 |
+
# it's treated as ERROR.
|
| 819 |
+
level = logging.ERROR
|
| 820 |
+
critical_prefix = _CRITICAL_PREFIX
|
| 821 |
+
severity = converter.get_initial_for_level(level)
|
| 822 |
+
|
| 823 |
+
return '%c%02d%02d %02d:%02d:%02d.%06d %5d %s:%d] %s' % (
|
| 824 |
+
severity,
|
| 825 |
+
created_tuple.tm_mon,
|
| 826 |
+
created_tuple.tm_mday,
|
| 827 |
+
created_tuple.tm_hour,
|
| 828 |
+
created_tuple.tm_min,
|
| 829 |
+
created_tuple.tm_sec,
|
| 830 |
+
created_microsecond,
|
| 831 |
+
_get_thread_id(),
|
| 832 |
+
record.filename,
|
| 833 |
+
record.lineno,
|
| 834 |
+
critical_prefix)
|
| 835 |
+
|
| 836 |
+
|
| 837 |
+
def skip_log_prefix(func):
|
| 838 |
+
"""Skips reporting the prefix of a given function or name by :class:`~absl.logging.ABSLLogger`.
|
| 839 |
+
|
| 840 |
+
This is a convenience wrapper function / decorator for
|
| 841 |
+
:meth:`~absl.logging.ABSLLogger.register_frame_to_skip`.
|
| 842 |
+
|
| 843 |
+
If a callable function is provided, only that function will be skipped.
|
| 844 |
+
If a function name is provided, all functions with the same name in the
|
| 845 |
+
file that this is called in will be skipped.
|
| 846 |
+
|
| 847 |
+
This can be used as a decorator of the intended function to be skipped.
|
| 848 |
+
|
| 849 |
+
Args:
|
| 850 |
+
func: Callable function or its name as a string.
|
| 851 |
+
|
| 852 |
+
Returns:
|
| 853 |
+
func (the input, unchanged).
|
| 854 |
+
|
| 855 |
+
Raises:
|
| 856 |
+
ValueError: The input is callable but does not have a function code object.
|
| 857 |
+
TypeError: The input is neither callable nor a string.
|
| 858 |
+
"""
|
| 859 |
+
if callable(func):
|
| 860 |
+
func_code = getattr(func, '__code__', None)
|
| 861 |
+
if func_code is None:
|
| 862 |
+
raise ValueError('Input callable does not have a function code object.')
|
| 863 |
+
file_name = func_code.co_filename
|
| 864 |
+
func_name = func_code.co_name
|
| 865 |
+
func_lineno = func_code.co_firstlineno
|
| 866 |
+
elif isinstance(func, str):
|
| 867 |
+
file_name = get_absl_logger().findCaller()[0]
|
| 868 |
+
func_name = func
|
| 869 |
+
func_lineno = None
|
| 870 |
+
else:
|
| 871 |
+
raise TypeError('Input is neither callable nor a string.')
|
| 872 |
+
ABSLLogger.register_frame_to_skip(file_name, func_name, func_lineno)
|
| 873 |
+
return func
|
| 874 |
+
|
| 875 |
+
|
| 876 |
+
def _is_non_absl_fatal_record(log_record):
|
| 877 |
+
return (log_record.levelno >= logging.FATAL and
|
| 878 |
+
not log_record.__dict__.get(_ABSL_LOG_FATAL, False))
|
| 879 |
+
|
| 880 |
+
|
| 881 |
+
def _is_absl_fatal_record(log_record):
|
| 882 |
+
return (log_record.levelno >= logging.FATAL and
|
| 883 |
+
log_record.__dict__.get(_ABSL_LOG_FATAL, False))
|
| 884 |
+
|
| 885 |
+
|
| 886 |
+
# Indicates if we still need to warn about pre-init logs going to stderr.
|
| 887 |
+
_warn_preinit_stderr = True
|
| 888 |
+
|
| 889 |
+
|
| 890 |
+
class PythonHandler(logging.StreamHandler):
|
| 891 |
+
"""The handler class used by Abseil Python logging implementation."""
|
| 892 |
+
|
| 893 |
+
def __init__(self, stream=None, formatter=None):
|
| 894 |
+
super().__init__(stream)
|
| 895 |
+
self.setFormatter(formatter or PythonFormatter())
|
| 896 |
+
|
| 897 |
+
def start_logging_to_file(self, program_name=None, log_dir=None):
|
| 898 |
+
"""Starts logging messages to files instead of standard error."""
|
| 899 |
+
FLAGS.logtostderr = False
|
| 900 |
+
|
| 901 |
+
actual_log_dir, file_prefix, symlink_prefix = find_log_dir_and_names(
|
| 902 |
+
program_name=program_name, log_dir=log_dir)
|
| 903 |
+
|
| 904 |
+
basename = '%s.INFO.%s.%d' % (
|
| 905 |
+
file_prefix,
|
| 906 |
+
time.strftime('%Y%m%d-%H%M%S', time.localtime(time.time())),
|
| 907 |
+
os.getpid())
|
| 908 |
+
filename = os.path.join(actual_log_dir, basename)
|
| 909 |
+
|
| 910 |
+
self.stream = open(filename, 'a', encoding='utf-8')
|
| 911 |
+
|
| 912 |
+
# os.symlink is not available on Windows Python 2.
|
| 913 |
+
if getattr(os, 'symlink', None):
|
| 914 |
+
# Create a symlink to the log file with a canonical name.
|
| 915 |
+
symlink = os.path.join(actual_log_dir, symlink_prefix + '.INFO')
|
| 916 |
+
try:
|
| 917 |
+
if os.path.islink(symlink):
|
| 918 |
+
os.unlink(symlink)
|
| 919 |
+
os.symlink(os.path.basename(filename), symlink)
|
| 920 |
+
except OSError:
|
| 921 |
+
# If it fails, we're sad but it's no error. Commonly, this
|
| 922 |
+
# fails because the symlink was created by another user and so
|
| 923 |
+
# we can't modify it
|
| 924 |
+
pass
|
| 925 |
+
|
| 926 |
+
def use_absl_log_file(self, program_name=None, log_dir=None):
|
| 927 |
+
"""Conditionally logs to files, based on --logtostderr."""
|
| 928 |
+
if FLAGS['logtostderr'].value:
|
| 929 |
+
self.stream = sys.stderr
|
| 930 |
+
else:
|
| 931 |
+
self.start_logging_to_file(program_name=program_name, log_dir=log_dir)
|
| 932 |
+
|
| 933 |
+
def flush(self):
|
| 934 |
+
"""Flushes all log files."""
|
| 935 |
+
self.acquire()
|
| 936 |
+
try:
|
| 937 |
+
if self.stream and hasattr(self.stream, 'flush'):
|
| 938 |
+
self.stream.flush()
|
| 939 |
+
except (OSError, ValueError):
|
| 940 |
+
# A ValueError is thrown if we try to flush a closed file.
|
| 941 |
+
pass
|
| 942 |
+
finally:
|
| 943 |
+
self.release()
|
| 944 |
+
|
| 945 |
+
def _log_to_stderr(self, record):
|
| 946 |
+
"""Emits the record to stderr.
|
| 947 |
+
|
| 948 |
+
This temporarily sets the handler stream to stderr, calls
|
| 949 |
+
StreamHandler.emit, then reverts the stream back.
|
| 950 |
+
|
| 951 |
+
Args:
|
| 952 |
+
record: logging.LogRecord, the record to log.
|
| 953 |
+
"""
|
| 954 |
+
# emit() is protected by a lock in logging.Handler, so we don't need to
|
| 955 |
+
# protect here again.
|
| 956 |
+
old_stream = self.stream
|
| 957 |
+
self.stream = sys.stderr
|
| 958 |
+
try:
|
| 959 |
+
super().emit(record)
|
| 960 |
+
finally:
|
| 961 |
+
self.stream = old_stream
|
| 962 |
+
|
| 963 |
+
def emit(self, record):
|
| 964 |
+
"""Prints a record out to some streams.
|
| 965 |
+
|
| 966 |
+
1. If ``FLAGS.logtostderr`` is set, it will print to ``sys.stderr`` ONLY.
|
| 967 |
+
2. If ``FLAGS.alsologtostderr`` is set, it will print to ``sys.stderr``.
|
| 968 |
+
3. If ``FLAGS.logtostderr`` is not set, it will log to the stream
|
| 969 |
+
associated with the current thread.
|
| 970 |
+
|
| 971 |
+
Args:
|
| 972 |
+
record: :class:`logging.LogRecord`, the record to emit.
|
| 973 |
+
"""
|
| 974 |
+
# People occasionally call logging functions at import time before
|
| 975 |
+
# our flags may have even been defined yet, let alone even parsed, as we
|
| 976 |
+
# rely on the C++ side to define some flags for us and app init to
|
| 977 |
+
# deal with parsing. Match the C++ library behavior of notify and emit
|
| 978 |
+
# such messages to stderr. It encourages people to clean-up and does
|
| 979 |
+
# not hide the message.
|
| 980 |
+
level = record.levelno
|
| 981 |
+
if not FLAGS.is_parsed(): # Also implies "before flag has been defined".
|
| 982 |
+
global _warn_preinit_stderr
|
| 983 |
+
if _warn_preinit_stderr:
|
| 984 |
+
sys.stderr.write(
|
| 985 |
+
'WARNING: Logging before flag parsing goes to stderr.\n')
|
| 986 |
+
_warn_preinit_stderr = False
|
| 987 |
+
self._log_to_stderr(record)
|
| 988 |
+
elif FLAGS['logtostderr'].value:
|
| 989 |
+
self._log_to_stderr(record)
|
| 990 |
+
else:
|
| 991 |
+
super().emit(record)
|
| 992 |
+
stderr_threshold = converter.string_to_standard(
|
| 993 |
+
FLAGS['stderrthreshold'].value)
|
| 994 |
+
if ((FLAGS['alsologtostderr'].value or level >= stderr_threshold) and
|
| 995 |
+
self.stream != sys.stderr):
|
| 996 |
+
self._log_to_stderr(record)
|
| 997 |
+
# Die when the record is created from ABSLLogger and level is FATAL.
|
| 998 |
+
if _is_absl_fatal_record(record):
|
| 999 |
+
self.flush() # Flush the log before dying.
|
| 1000 |
+
|
| 1001 |
+
# In threaded python, sys.exit() from a non-main thread only
|
| 1002 |
+
# exits the thread in question.
|
| 1003 |
+
os.abort()
|
| 1004 |
+
|
| 1005 |
+
def close(self):
|
| 1006 |
+
"""Closes the stream to which we are writing."""
|
| 1007 |
+
self.acquire()
|
| 1008 |
+
try:
|
| 1009 |
+
self.flush()
|
| 1010 |
+
try:
|
| 1011 |
+
# Do not close the stream if it's sys.stderr|stdout. They may be
|
| 1012 |
+
# redirected or overridden to files, which should be managed by users
|
| 1013 |
+
# explicitly.
|
| 1014 |
+
user_managed = sys.stderr, sys.stdout, sys.__stderr__, sys.__stdout__
|
| 1015 |
+
if self.stream not in user_managed and (
|
| 1016 |
+
not hasattr(self.stream, 'isatty') or not self.stream.isatty()):
|
| 1017 |
+
self.stream.close()
|
| 1018 |
+
except ValueError:
|
| 1019 |
+
# A ValueError is thrown if we try to run isatty() on a closed file.
|
| 1020 |
+
pass
|
| 1021 |
+
super().close()
|
| 1022 |
+
finally:
|
| 1023 |
+
self.release()
|
| 1024 |
+
|
| 1025 |
+
|
| 1026 |
+
class ABSLHandler(logging.Handler):
|
| 1027 |
+
"""Abseil Python logging module's log handler."""
|
| 1028 |
+
|
| 1029 |
+
def __init__(self, python_logging_formatter):
|
| 1030 |
+
super().__init__()
|
| 1031 |
+
|
| 1032 |
+
self._python_handler = PythonHandler(formatter=python_logging_formatter)
|
| 1033 |
+
self.activate_python_handler()
|
| 1034 |
+
|
| 1035 |
+
def format(self, record):
|
| 1036 |
+
return self._current_handler.format(record)
|
| 1037 |
+
|
| 1038 |
+
def setFormatter(self, fmt):
|
| 1039 |
+
self._current_handler.setFormatter(fmt)
|
| 1040 |
+
|
| 1041 |
+
def emit(self, record):
|
| 1042 |
+
self._current_handler.emit(record)
|
| 1043 |
+
|
| 1044 |
+
def flush(self):
|
| 1045 |
+
self._current_handler.flush()
|
| 1046 |
+
|
| 1047 |
+
def close(self):
|
| 1048 |
+
super().close()
|
| 1049 |
+
self._current_handler.close()
|
| 1050 |
+
|
| 1051 |
+
def handle(self, record):
|
| 1052 |
+
rv = self.filter(record)
|
| 1053 |
+
if rv:
|
| 1054 |
+
return self._current_handler.handle(record)
|
| 1055 |
+
return rv
|
| 1056 |
+
|
| 1057 |
+
@property
|
| 1058 |
+
def python_handler(self):
|
| 1059 |
+
return self._python_handler
|
| 1060 |
+
|
| 1061 |
+
def activate_python_handler(self):
|
| 1062 |
+
"""Uses the Python logging handler as the current logging handler."""
|
| 1063 |
+
self._current_handler = self._python_handler
|
| 1064 |
+
|
| 1065 |
+
def use_absl_log_file(self, program_name=None, log_dir=None):
|
| 1066 |
+
self._current_handler.use_absl_log_file(program_name, log_dir)
|
| 1067 |
+
|
| 1068 |
+
def start_logging_to_file(self, program_name=None, log_dir=None):
|
| 1069 |
+
self._current_handler.start_logging_to_file(program_name, log_dir)
|
| 1070 |
+
|
| 1071 |
+
|
| 1072 |
+
class PythonFormatter(logging.Formatter):
|
| 1073 |
+
"""Formatter class used by :class:`~absl.logging.PythonHandler`."""
|
| 1074 |
+
|
| 1075 |
+
def format(self, record):
|
| 1076 |
+
"""Appends the message from the record to the results of the prefix.
|
| 1077 |
+
|
| 1078 |
+
Args:
|
| 1079 |
+
record: logging.LogRecord, the record to be formatted.
|
| 1080 |
+
|
| 1081 |
+
Returns:
|
| 1082 |
+
The formatted string representing the record.
|
| 1083 |
+
"""
|
| 1084 |
+
if (not FLAGS['showprefixforinfo'].value and
|
| 1085 |
+
FLAGS['verbosity'].value == converter.ABSL_INFO and
|
| 1086 |
+
record.levelno == logging.INFO and
|
| 1087 |
+
_absl_handler.python_handler.stream == sys.stderr):
|
| 1088 |
+
prefix = ''
|
| 1089 |
+
else:
|
| 1090 |
+
prefix = get_absl_log_prefix(record)
|
| 1091 |
+
return prefix + super().format(record)
|
| 1092 |
+
|
| 1093 |
+
|
| 1094 |
+
class ABSLLogger(logging.getLoggerClass()):
|
| 1095 |
+
"""A logger that will create LogRecords while skipping some stack frames.
|
| 1096 |
+
|
| 1097 |
+
This class maintains an internal list of filenames and method names
|
| 1098 |
+
for use when determining who called the currently executing stack
|
| 1099 |
+
frame. Any method names from specific source files are skipped when
|
| 1100 |
+
walking backwards through the stack.
|
| 1101 |
+
|
| 1102 |
+
Client code should use the register_frame_to_skip method to let the
|
| 1103 |
+
ABSLLogger know which method from which file should be
|
| 1104 |
+
excluded from the walk backwards through the stack.
|
| 1105 |
+
"""
|
| 1106 |
+
_frames_to_skip = set()
|
| 1107 |
+
|
| 1108 |
+
def findCaller(self, stack_info=False, stacklevel=1):
|
| 1109 |
+
"""Finds the frame of the calling method on the stack.
|
| 1110 |
+
|
| 1111 |
+
This method skips any frames registered with the
|
| 1112 |
+
ABSLLogger and any methods from this file, and whatever
|
| 1113 |
+
method is currently being used to generate the prefix for the log
|
| 1114 |
+
line. Then it returns the file name, line number, and method name
|
| 1115 |
+
of the calling method. An optional fourth item may be returned,
|
| 1116 |
+
callers who only need things from the first three are advised to
|
| 1117 |
+
always slice or index the result rather than using direct unpacking
|
| 1118 |
+
assignment.
|
| 1119 |
+
|
| 1120 |
+
Args:
|
| 1121 |
+
stack_info: bool, when True, include the stack trace as a fourth item
|
| 1122 |
+
returned. On Python 3 there are always four items returned - the fourth
|
| 1123 |
+
will be None when this is False. On Python 2 the stdlib base class API
|
| 1124 |
+
only returns three items. We do the same when this new parameter is
|
| 1125 |
+
unspecified or False for compatibility.
|
| 1126 |
+
stacklevel: int, if greater than 1, that number of frames will be skipped.
|
| 1127 |
+
|
| 1128 |
+
Returns:
|
| 1129 |
+
(filename, lineno, methodname[, sinfo]) of the calling method.
|
| 1130 |
+
"""
|
| 1131 |
+
f_to_skip = ABSLLogger._frames_to_skip
|
| 1132 |
+
# Use sys._getframe(2) instead of logging.currentframe(), it's slightly
|
| 1133 |
+
# faster because there is one less frame to traverse.
|
| 1134 |
+
frame = sys._getframe(2) # pylint: disable=protected-access
|
| 1135 |
+
frame_to_return = None
|
| 1136 |
+
|
| 1137 |
+
while frame:
|
| 1138 |
+
code = frame.f_code
|
| 1139 |
+
if (_LOGGING_FILE_PREFIX not in code.co_filename and
|
| 1140 |
+
(code.co_filename, code.co_name,
|
| 1141 |
+
code.co_firstlineno) not in f_to_skip and
|
| 1142 |
+
(code.co_filename, code.co_name) not in f_to_skip):
|
| 1143 |
+
frame_to_return = frame
|
| 1144 |
+
stacklevel -= 1
|
| 1145 |
+
if stacklevel <= 0:
|
| 1146 |
+
break
|
| 1147 |
+
frame = frame.f_back
|
| 1148 |
+
|
| 1149 |
+
if frame_to_return is not None:
|
| 1150 |
+
sinfo = None
|
| 1151 |
+
if stack_info:
|
| 1152 |
+
out = io.StringIO()
|
| 1153 |
+
out.write('Stack (most recent call last):\n')
|
| 1154 |
+
traceback.print_stack(frame, file=out)
|
| 1155 |
+
sinfo = out.getvalue().rstrip('\n')
|
| 1156 |
+
return (
|
| 1157 |
+
frame_to_return.f_code.co_filename,
|
| 1158 |
+
frame_to_return.f_lineno,
|
| 1159 |
+
frame_to_return.f_code.co_name,
|
| 1160 |
+
sinfo,
|
| 1161 |
+
)
|
| 1162 |
+
|
| 1163 |
+
return None
|
| 1164 |
+
|
| 1165 |
+
def critical(self, msg, *args, **kwargs):
|
| 1166 |
+
"""Logs ``msg % args`` with severity ``CRITICAL``."""
|
| 1167 |
+
self.log(logging.CRITICAL, msg, *args, **kwargs)
|
| 1168 |
+
|
| 1169 |
+
def fatal(self, msg, *args, **kwargs):
|
| 1170 |
+
"""Logs ``msg % args`` with severity ``FATAL``."""
|
| 1171 |
+
self.log(logging.FATAL, msg, *args, **kwargs)
|
| 1172 |
+
|
| 1173 |
+
def error(self, msg, *args, **kwargs):
|
| 1174 |
+
"""Logs ``msg % args`` with severity ``ERROR``."""
|
| 1175 |
+
self.log(logging.ERROR, msg, *args, **kwargs)
|
| 1176 |
+
|
| 1177 |
+
def warn(self, msg, *args, **kwargs):
|
| 1178 |
+
"""Logs ``msg % args`` with severity ``WARN``."""
|
| 1179 |
+
warnings.warn("The 'warn' method is deprecated, use 'warning' instead",
|
| 1180 |
+
DeprecationWarning, 2)
|
| 1181 |
+
self.log(logging.WARN, msg, *args, **kwargs)
|
| 1182 |
+
|
| 1183 |
+
def warning(self, msg, *args, **kwargs):
|
| 1184 |
+
"""Logs ``msg % args`` with severity ``WARNING``."""
|
| 1185 |
+
self.log(logging.WARNING, msg, *args, **kwargs)
|
| 1186 |
+
|
| 1187 |
+
def info(self, msg, *args, **kwargs):
|
| 1188 |
+
"""Logs ``msg % args`` with severity ``INFO``."""
|
| 1189 |
+
self.log(logging.INFO, msg, *args, **kwargs)
|
| 1190 |
+
|
| 1191 |
+
def debug(self, msg, *args, **kwargs):
|
| 1192 |
+
"""Logs ``msg % args`` with severity ``DEBUG``."""
|
| 1193 |
+
self.log(logging.DEBUG, msg, *args, **kwargs)
|
| 1194 |
+
|
| 1195 |
+
def log(self, level, msg, *args, **kwargs):
|
| 1196 |
+
"""Logs a message at a certain level substituting in the supplied arguments.
|
| 1197 |
+
|
| 1198 |
+
This method behaves differently in python and c++ modes.
|
| 1199 |
+
|
| 1200 |
+
Args:
|
| 1201 |
+
level: int, the standard logging level at which to log the message.
|
| 1202 |
+
msg: str, the text of the message to log.
|
| 1203 |
+
*args: The arguments to substitute in the message.
|
| 1204 |
+
**kwargs: The keyword arguments to substitute in the message.
|
| 1205 |
+
"""
|
| 1206 |
+
if level >= logging.FATAL:
|
| 1207 |
+
# Add property to the LogRecord created by this logger.
|
| 1208 |
+
# This will be used by the ABSLHandler to determine whether it should
|
| 1209 |
+
# treat CRITICAL/FATAL logs as really FATAL.
|
| 1210 |
+
extra = kwargs.setdefault('extra', {})
|
| 1211 |
+
extra[_ABSL_LOG_FATAL] = True
|
| 1212 |
+
super().log(level, msg, *args, **kwargs)
|
| 1213 |
+
|
| 1214 |
+
def handle(self, record):
|
| 1215 |
+
"""Calls handlers without checking ``Logger.disabled``.
|
| 1216 |
+
|
| 1217 |
+
Non-root loggers are set to disabled after setup with :func:`logging.config`
|
| 1218 |
+
if it's not explicitly specified. Historically, absl logging will not be
|
| 1219 |
+
disabled by that. To maintaining this behavior, this function skips
|
| 1220 |
+
checking the ``Logger.disabled`` bit.
|
| 1221 |
+
|
| 1222 |
+
This logger can still be disabled by adding a filter that filters out
|
| 1223 |
+
everything.
|
| 1224 |
+
|
| 1225 |
+
Args:
|
| 1226 |
+
record: logging.LogRecord, the record to handle.
|
| 1227 |
+
"""
|
| 1228 |
+
if self.filter(record):
|
| 1229 |
+
self.callHandlers(record)
|
| 1230 |
+
|
| 1231 |
+
@classmethod
|
| 1232 |
+
def register_frame_to_skip(cls, file_name, function_name, line_number=None):
|
| 1233 |
+
"""Registers a function name to skip when walking the stack.
|
| 1234 |
+
|
| 1235 |
+
The :class:`~absl.logging.ABSLLogger` sometimes skips method calls on the
|
| 1236 |
+
stack to make the log messages meaningful in their appropriate context.
|
| 1237 |
+
This method registers a function from a particular file as one
|
| 1238 |
+
which should be skipped.
|
| 1239 |
+
|
| 1240 |
+
Args:
|
| 1241 |
+
file_name: str, the name of the file that contains the function.
|
| 1242 |
+
function_name: str, the name of the function to skip.
|
| 1243 |
+
line_number: int, if provided, only the function with this starting line
|
| 1244 |
+
number will be skipped. Otherwise, all functions with the same name
|
| 1245 |
+
in the file will be skipped.
|
| 1246 |
+
"""
|
| 1247 |
+
if line_number is not None:
|
| 1248 |
+
cls._frames_to_skip.add((file_name, function_name, line_number))
|
| 1249 |
+
else:
|
| 1250 |
+
cls._frames_to_skip.add((file_name, function_name))
|
| 1251 |
+
|
| 1252 |
+
|
| 1253 |
+
def _get_thread_id():
|
| 1254 |
+
"""Gets id of current thread, suitable for logging as an unsigned quantity.
|
| 1255 |
+
|
| 1256 |
+
If pywrapbase is linked, returns GetTID() for the thread ID to be
|
| 1257 |
+
consistent with C++ logging. Otherwise, returns the numeric thread id.
|
| 1258 |
+
The quantities are made unsigned by masking with 2*sys.maxint + 1.
|
| 1259 |
+
|
| 1260 |
+
Returns:
|
| 1261 |
+
Thread ID unique to this process (unsigned)
|
| 1262 |
+
"""
|
| 1263 |
+
thread_id = threading.get_ident()
|
| 1264 |
+
return thread_id & _THREAD_ID_MASK
|
| 1265 |
+
|
| 1266 |
+
|
| 1267 |
+
def get_absl_logger():
|
| 1268 |
+
"""Returns the absl logger instance."""
|
| 1269 |
+
assert _absl_logger is not None
|
| 1270 |
+
return _absl_logger
|
| 1271 |
+
|
| 1272 |
+
|
| 1273 |
+
def get_absl_handler():
|
| 1274 |
+
"""Returns the absl handler instance."""
|
| 1275 |
+
assert _absl_handler is not None
|
| 1276 |
+
return _absl_handler
|
| 1277 |
+
|
| 1278 |
+
|
| 1279 |
+
def use_python_logging(quiet=False):
|
| 1280 |
+
"""Uses the python implementation of the logging code.
|
| 1281 |
+
|
| 1282 |
+
Args:
|
| 1283 |
+
quiet: No logging message about switching logging type.
|
| 1284 |
+
"""
|
| 1285 |
+
get_absl_handler().activate_python_handler()
|
| 1286 |
+
if not quiet:
|
| 1287 |
+
info('Restoring pure python logging')
|
| 1288 |
+
|
| 1289 |
+
|
| 1290 |
+
_attempted_to_remove_stderr_stream_handlers = False
|
| 1291 |
+
|
| 1292 |
+
|
| 1293 |
+
def use_absl_handler():
|
| 1294 |
+
"""Uses the ABSL logging handler for logging.
|
| 1295 |
+
|
| 1296 |
+
This method is called in :func:`app.run()<absl.app.run>` so the absl handler
|
| 1297 |
+
is used in absl apps.
|
| 1298 |
+
"""
|
| 1299 |
+
global _attempted_to_remove_stderr_stream_handlers
|
| 1300 |
+
if not _attempted_to_remove_stderr_stream_handlers:
|
| 1301 |
+
# The absl handler logs to stderr by default. To prevent double logging to
|
| 1302 |
+
# stderr, the following code tries its best to remove other handlers that
|
| 1303 |
+
# emit to stderr. Those handlers are most commonly added when
|
| 1304 |
+
# logging.info/debug is called before calling use_absl_handler().
|
| 1305 |
+
handlers = [
|
| 1306 |
+
h for h in logging.root.handlers
|
| 1307 |
+
if isinstance(h, logging.StreamHandler) and h.stream == sys.stderr]
|
| 1308 |
+
for h in handlers:
|
| 1309 |
+
logging.root.removeHandler(h)
|
| 1310 |
+
_attempted_to_remove_stderr_stream_handlers = True
|
| 1311 |
+
|
| 1312 |
+
absl_handler = get_absl_handler()
|
| 1313 |
+
if absl_handler not in logging.root.handlers:
|
| 1314 |
+
logging.root.addHandler(absl_handler)
|
| 1315 |
+
FLAGS['verbosity']._update_logging_levels() # pylint: disable=protected-access
|
| 1316 |
+
FLAGS['logger_levels']._update_logger_levels() # pylint: disable=protected-access
|
| 1317 |
+
|
| 1318 |
+
|
| 1319 |
+
def _initialize():
|
| 1320 |
+
"""Initializes loggers and handlers."""
|
| 1321 |
+
global _absl_logger, _absl_handler
|
| 1322 |
+
|
| 1323 |
+
if _absl_logger:
|
| 1324 |
+
return
|
| 1325 |
+
|
| 1326 |
+
original_logger_class = logging.getLoggerClass()
|
| 1327 |
+
logging.setLoggerClass(ABSLLogger)
|
| 1328 |
+
_absl_logger = logging.getLogger('absl')
|
| 1329 |
+
logging.setLoggerClass(original_logger_class)
|
| 1330 |
+
|
| 1331 |
+
python_logging_formatter = PythonFormatter()
|
| 1332 |
+
_absl_handler = ABSLHandler(python_logging_formatter)
|
| 1333 |
+
|
| 1334 |
+
|
| 1335 |
+
_initialize()
|
.venv/lib/python3.13/site-packages/absl/logging/__init__.pyi
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from collections.abc import Callable
|
| 16 |
+
import logging
|
| 17 |
+
from typing import Any, NoReturn, TypeVar
|
| 18 |
+
|
| 19 |
+
from absl import flags
|
| 20 |
+
|
| 21 |
+
# Logging levels.
|
| 22 |
+
FATAL: int
|
| 23 |
+
ERROR: int
|
| 24 |
+
WARNING: int
|
| 25 |
+
WARN: int # Deprecated name.
|
| 26 |
+
INFO: int
|
| 27 |
+
DEBUG: int
|
| 28 |
+
|
| 29 |
+
ABSL_LOGGING_PREFIX_REGEX: str
|
| 30 |
+
|
| 31 |
+
LOGTOSTDERR: flags.FlagHolder[bool]
|
| 32 |
+
ALSOLOGTOSTDERR: flags.FlagHolder[bool]
|
| 33 |
+
LOG_DIR: flags.FlagHolder[str]
|
| 34 |
+
VERBOSITY: flags.FlagHolder[int]
|
| 35 |
+
LOGGER_LEVELS: flags.FlagHolder[dict[str, str]]
|
| 36 |
+
STDERRTHRESHOLD: flags.FlagHolder[str]
|
| 37 |
+
SHOWPREFIXFORINFO: flags.FlagHolder[bool]
|
| 38 |
+
|
| 39 |
+
def get_verbosity() -> int:
|
| 40 |
+
...
|
| 41 |
+
|
| 42 |
+
def set_verbosity(v: int | str) -> None:
|
| 43 |
+
...
|
| 44 |
+
|
| 45 |
+
def set_stderrthreshold(s: int | str) -> None:
|
| 46 |
+
...
|
| 47 |
+
|
| 48 |
+
# TODO(b/277607978): Provide actual args+kwargs shadowing stdlib's logging functions.
|
| 49 |
+
def fatal(msg: Any, *args: Any, **kwargs: Any) -> NoReturn:
|
| 50 |
+
...
|
| 51 |
+
|
| 52 |
+
def error(msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 53 |
+
...
|
| 54 |
+
|
| 55 |
+
def warning(msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 56 |
+
...
|
| 57 |
+
|
| 58 |
+
def warn(msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 59 |
+
...
|
| 60 |
+
|
| 61 |
+
def info(msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 62 |
+
...
|
| 63 |
+
|
| 64 |
+
def debug(msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 65 |
+
...
|
| 66 |
+
|
| 67 |
+
def exception(msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 68 |
+
...
|
| 69 |
+
|
| 70 |
+
def log_every_n(
|
| 71 |
+
level: int,
|
| 72 |
+
msg: Any,
|
| 73 |
+
n: int,
|
| 74 |
+
*args: Any,
|
| 75 |
+
use_call_stack: bool = ...,
|
| 76 |
+
**kwargs: Any,
|
| 77 |
+
) -> None:
|
| 78 |
+
...
|
| 79 |
+
|
| 80 |
+
def log_every_n_seconds(
|
| 81 |
+
level: int,
|
| 82 |
+
msg: Any,
|
| 83 |
+
n_seconds: float,
|
| 84 |
+
*args: Any,
|
| 85 |
+
use_call_stack: bool = ...,
|
| 86 |
+
**kwargs: Any,
|
| 87 |
+
) -> None:
|
| 88 |
+
...
|
| 89 |
+
|
| 90 |
+
def log_first_n(
|
| 91 |
+
level: int,
|
| 92 |
+
msg: Any,
|
| 93 |
+
n: int,
|
| 94 |
+
*args: Any,
|
| 95 |
+
use_call_stack: bool = ...,
|
| 96 |
+
**kwargs: Any,
|
| 97 |
+
) -> None:
|
| 98 |
+
...
|
| 99 |
+
|
| 100 |
+
def log_if(level: int,
|
| 101 |
+
msg: Any,
|
| 102 |
+
condition: Any,
|
| 103 |
+
*args: Any,
|
| 104 |
+
**kwargs: Any,
|
| 105 |
+
) -> None:
|
| 106 |
+
...
|
| 107 |
+
|
| 108 |
+
def log(level: int, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 109 |
+
...
|
| 110 |
+
|
| 111 |
+
def vlog(level: int, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 112 |
+
...
|
| 113 |
+
|
| 114 |
+
def vlog_is_on(level: int) -> bool:
|
| 115 |
+
...
|
| 116 |
+
|
| 117 |
+
def flush() -> None:
|
| 118 |
+
...
|
| 119 |
+
|
| 120 |
+
def level_debug() -> bool:
|
| 121 |
+
...
|
| 122 |
+
|
| 123 |
+
def level_info() -> bool:
|
| 124 |
+
...
|
| 125 |
+
|
| 126 |
+
def level_warning() -> bool:
|
| 127 |
+
...
|
| 128 |
+
|
| 129 |
+
level_warn = level_warning # Deprecated function.
|
| 130 |
+
|
| 131 |
+
def level_error() -> bool:
|
| 132 |
+
...
|
| 133 |
+
|
| 134 |
+
def get_log_file_name(level: int = ...) -> str:
|
| 135 |
+
...
|
| 136 |
+
|
| 137 |
+
def find_log_dir_and_names(
|
| 138 |
+
program_name: str | None = ..., log_dir: str | None = ...
|
| 139 |
+
) -> tuple[str, str, str]:
|
| 140 |
+
...
|
| 141 |
+
|
| 142 |
+
def find_log_dir(log_dir: str | None = ...) -> str:
|
| 143 |
+
...
|
| 144 |
+
|
| 145 |
+
def get_absl_log_prefix(record: logging.LogRecord) -> str:
|
| 146 |
+
...
|
| 147 |
+
|
| 148 |
+
_SkipLogT = TypeVar('_SkipLogT', str, Callable[..., Any])
|
| 149 |
+
|
| 150 |
+
def skip_log_prefix(func: _SkipLogT) -> _SkipLogT:
|
| 151 |
+
...
|
| 152 |
+
|
| 153 |
+
_StreamT = TypeVar('_StreamT')
|
| 154 |
+
|
| 155 |
+
class PythonHandler(logging.StreamHandler[_StreamT]): # type: ignore[type-var]
|
| 156 |
+
|
| 157 |
+
def __init__(
|
| 158 |
+
self,
|
| 159 |
+
stream: _StreamT | None = ...,
|
| 160 |
+
formatter: logging.Formatter | None = ...,
|
| 161 |
+
) -> None:
|
| 162 |
+
...
|
| 163 |
+
|
| 164 |
+
def start_logging_to_file(
|
| 165 |
+
self, program_name: str | None = ..., log_dir: str | None = ...
|
| 166 |
+
) -> None:
|
| 167 |
+
...
|
| 168 |
+
|
| 169 |
+
def use_absl_log_file(
|
| 170 |
+
self, program_name: str | None = ..., log_dir: str | None = ...
|
| 171 |
+
) -> None:
|
| 172 |
+
...
|
| 173 |
+
|
| 174 |
+
def flush(self) -> None:
|
| 175 |
+
...
|
| 176 |
+
|
| 177 |
+
def emit(self, record: logging.LogRecord) -> None:
|
| 178 |
+
...
|
| 179 |
+
|
| 180 |
+
def close(self) -> None:
|
| 181 |
+
...
|
| 182 |
+
|
| 183 |
+
class ABSLHandler(logging.Handler):
|
| 184 |
+
|
| 185 |
+
def __init__(self, python_logging_formatter: PythonFormatter) -> None:
|
| 186 |
+
...
|
| 187 |
+
|
| 188 |
+
def format(self, record: logging.LogRecord) -> str:
|
| 189 |
+
...
|
| 190 |
+
|
| 191 |
+
def setFormatter(self, fmt) -> None:
|
| 192 |
+
...
|
| 193 |
+
|
| 194 |
+
def emit(self, record: logging.LogRecord) -> None:
|
| 195 |
+
...
|
| 196 |
+
|
| 197 |
+
def flush(self) -> None:
|
| 198 |
+
...
|
| 199 |
+
|
| 200 |
+
def close(self) -> None:
|
| 201 |
+
...
|
| 202 |
+
|
| 203 |
+
def handle(self, record: logging.LogRecord) -> bool:
|
| 204 |
+
...
|
| 205 |
+
|
| 206 |
+
@property
|
| 207 |
+
def python_handler(self) -> PythonHandler:
|
| 208 |
+
...
|
| 209 |
+
|
| 210 |
+
def activate_python_handler(self) -> None:
|
| 211 |
+
...
|
| 212 |
+
|
| 213 |
+
def use_absl_log_file(
|
| 214 |
+
self, program_name: str | None = ..., log_dir: str | None = ...
|
| 215 |
+
) -> None:
|
| 216 |
+
...
|
| 217 |
+
|
| 218 |
+
def start_logging_to_file(self, program_name=None, log_dir=None) -> None:
|
| 219 |
+
...
|
| 220 |
+
|
| 221 |
+
class PythonFormatter(logging.Formatter):
|
| 222 |
+
|
| 223 |
+
def format(self, record: logging.LogRecord) -> str:
|
| 224 |
+
...
|
| 225 |
+
|
| 226 |
+
class ABSLLogger(logging.Logger):
|
| 227 |
+
|
| 228 |
+
def findCaller(
|
| 229 |
+
self, stack_info: bool = ..., stacklevel: int = ...
|
| 230 |
+
) -> tuple[str, int, str, str | None]:
|
| 231 |
+
...
|
| 232 |
+
|
| 233 |
+
def critical(self, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 234 |
+
...
|
| 235 |
+
|
| 236 |
+
def fatal(self, msg: Any, *args: Any, **kwargs: Any) -> NoReturn: # type: ignore[override]
|
| 237 |
+
...
|
| 238 |
+
|
| 239 |
+
def error(self, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 240 |
+
...
|
| 241 |
+
|
| 242 |
+
def warn(self, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 243 |
+
...
|
| 244 |
+
|
| 245 |
+
def warning(self, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 246 |
+
...
|
| 247 |
+
|
| 248 |
+
def info(self, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 249 |
+
...
|
| 250 |
+
|
| 251 |
+
def debug(self, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 252 |
+
...
|
| 253 |
+
|
| 254 |
+
def log(self, level: int, msg: Any, *args: Any, **kwargs: Any) -> None:
|
| 255 |
+
...
|
| 256 |
+
|
| 257 |
+
def handle(self, record: logging.LogRecord) -> None:
|
| 258 |
+
...
|
| 259 |
+
|
| 260 |
+
@classmethod
|
| 261 |
+
def register_frame_to_skip(
|
| 262 |
+
cls, file_name: str, function_name: str, line_number: int | None = ...
|
| 263 |
+
) -> None:
|
| 264 |
+
...
|
| 265 |
+
|
| 266 |
+
# NOTE: Returns None before _initialize called but shouldn't occur after import.
|
| 267 |
+
def get_absl_logger() -> ABSLLogger:
|
| 268 |
+
...
|
| 269 |
+
|
| 270 |
+
# NOTE: Returns None before _initialize called but shouldn't occur after import.
|
| 271 |
+
def get_absl_handler() -> ABSLHandler:
|
| 272 |
+
...
|
| 273 |
+
|
| 274 |
+
def use_python_logging(quiet: bool = ...) -> None:
|
| 275 |
+
...
|
| 276 |
+
|
| 277 |
+
def use_absl_handler() -> None:
|
| 278 |
+
...
|
.venv/lib/python3.13/site-packages/absl/logging/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (54.5 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/logging/__pycache__/converter.cpython-313.pyc
ADDED
|
Binary file (6.15 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/logging/converter.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Module to convert log levels between Abseil Python, C++, and Python standard.
|
| 16 |
+
|
| 17 |
+
This converter has to convert (best effort) between three different
|
| 18 |
+
logging level schemes:
|
| 19 |
+
|
| 20 |
+
* **cpp**: The C++ logging level scheme used in Abseil C++.
|
| 21 |
+
* **absl**: The absl.logging level scheme used in Abseil Python.
|
| 22 |
+
* **standard**: The python standard library logging level scheme.
|
| 23 |
+
|
| 24 |
+
Here is a handy ascii chart for easy mental mapping::
|
| 25 |
+
|
| 26 |
+
LEVEL | cpp | absl | standard |
|
| 27 |
+
---------+-----+--------+----------+
|
| 28 |
+
DEBUG | 0 | 1 | 10 |
|
| 29 |
+
INFO | 0 | 0 | 20 |
|
| 30 |
+
WARNING | 1 | -1 | 30 |
|
| 31 |
+
ERROR | 2 | -2 | 40 |
|
| 32 |
+
CRITICAL | 3 | -3 | 50 |
|
| 33 |
+
FATAL | 3 | -3 | 50 |
|
| 34 |
+
|
| 35 |
+
Note: standard logging ``CRITICAL`` is mapped to absl/cpp ``FATAL``.
|
| 36 |
+
However, only ``CRITICAL`` logs from the absl logger (or absl.logging.fatal)
|
| 37 |
+
will terminate the program. ``CRITICAL`` logs from non-absl loggers are treated
|
| 38 |
+
as error logs with a message prefix ``"CRITICAL - "``.
|
| 39 |
+
|
| 40 |
+
Converting from standard to absl or cpp is a lossy conversion.
|
| 41 |
+
Converting back to standard will lose granularity. For this reason,
|
| 42 |
+
users should always try to convert to standard, the richest
|
| 43 |
+
representation, before manipulating the levels, and then only to cpp
|
| 44 |
+
or absl if those level schemes are absolutely necessary.
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
import logging
|
| 48 |
+
|
| 49 |
+
STANDARD_CRITICAL = logging.CRITICAL
|
| 50 |
+
STANDARD_ERROR = logging.ERROR
|
| 51 |
+
STANDARD_WARNING = logging.WARNING
|
| 52 |
+
STANDARD_INFO = logging.INFO
|
| 53 |
+
STANDARD_DEBUG = logging.DEBUG
|
| 54 |
+
|
| 55 |
+
# These levels are also used to define the constants
|
| 56 |
+
# FATAL, ERROR, WARNING, INFO, and DEBUG in the
|
| 57 |
+
# absl.logging module.
|
| 58 |
+
ABSL_FATAL = -3
|
| 59 |
+
ABSL_ERROR = -2
|
| 60 |
+
ABSL_WARNING = -1
|
| 61 |
+
ABSL_WARN = -1 # Deprecated name.
|
| 62 |
+
ABSL_INFO = 0
|
| 63 |
+
ABSL_DEBUG = 1
|
| 64 |
+
|
| 65 |
+
ABSL_LEVELS = {ABSL_FATAL: 'FATAL',
|
| 66 |
+
ABSL_ERROR: 'ERROR',
|
| 67 |
+
ABSL_WARNING: 'WARNING',
|
| 68 |
+
ABSL_INFO: 'INFO',
|
| 69 |
+
ABSL_DEBUG: 'DEBUG'}
|
| 70 |
+
|
| 71 |
+
# Inverts the ABSL_LEVELS dictionary
|
| 72 |
+
ABSL_NAMES = {'FATAL': ABSL_FATAL,
|
| 73 |
+
'ERROR': ABSL_ERROR,
|
| 74 |
+
'WARNING': ABSL_WARNING,
|
| 75 |
+
'WARN': ABSL_WARNING, # Deprecated name.
|
| 76 |
+
'INFO': ABSL_INFO,
|
| 77 |
+
'DEBUG': ABSL_DEBUG}
|
| 78 |
+
|
| 79 |
+
ABSL_TO_STANDARD = {ABSL_FATAL: STANDARD_CRITICAL,
|
| 80 |
+
ABSL_ERROR: STANDARD_ERROR,
|
| 81 |
+
ABSL_WARNING: STANDARD_WARNING,
|
| 82 |
+
ABSL_INFO: STANDARD_INFO,
|
| 83 |
+
ABSL_DEBUG: STANDARD_DEBUG}
|
| 84 |
+
|
| 85 |
+
# Inverts the ABSL_TO_STANDARD
|
| 86 |
+
STANDARD_TO_ABSL = {v: k for (k, v) in ABSL_TO_STANDARD.items()}
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def get_initial_for_level(level):
|
| 90 |
+
"""Gets the initial that should start the log line for the given level.
|
| 91 |
+
|
| 92 |
+
It returns:
|
| 93 |
+
|
| 94 |
+
* ``'I'`` when: ``level < STANDARD_WARNING``.
|
| 95 |
+
* ``'W'`` when: ``STANDARD_WARNING <= level < STANDARD_ERROR``.
|
| 96 |
+
* ``'E'`` when: ``STANDARD_ERROR <= level < STANDARD_CRITICAL``.
|
| 97 |
+
* ``'F'`` when: ``level >= STANDARD_CRITICAL``.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
level: int, a Python standard logging level.
|
| 101 |
+
|
| 102 |
+
Returns:
|
| 103 |
+
The first initial as it would be logged by the C++ logging module.
|
| 104 |
+
"""
|
| 105 |
+
if level < STANDARD_WARNING:
|
| 106 |
+
return 'I'
|
| 107 |
+
elif level < STANDARD_ERROR:
|
| 108 |
+
return 'W'
|
| 109 |
+
elif level < STANDARD_CRITICAL:
|
| 110 |
+
return 'E'
|
| 111 |
+
else:
|
| 112 |
+
return 'F'
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def absl_to_cpp(level):
|
| 116 |
+
"""Converts an absl log level to a cpp log level.
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
level: int, an absl.logging level.
|
| 120 |
+
|
| 121 |
+
Raises:
|
| 122 |
+
TypeError: Raised when level is not an integer.
|
| 123 |
+
|
| 124 |
+
Returns:
|
| 125 |
+
The corresponding integer level for use in Abseil C++.
|
| 126 |
+
"""
|
| 127 |
+
if not isinstance(level, int):
|
| 128 |
+
raise TypeError(f'Expect an int level, found {type(level)}')
|
| 129 |
+
if level >= 0:
|
| 130 |
+
# C++ log levels must be >= 0
|
| 131 |
+
return 0
|
| 132 |
+
else:
|
| 133 |
+
return -level
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def absl_to_standard(level):
|
| 137 |
+
"""Converts an integer level from the absl value to the standard value.
|
| 138 |
+
|
| 139 |
+
Args:
|
| 140 |
+
level: int, an absl.logging level.
|
| 141 |
+
|
| 142 |
+
Raises:
|
| 143 |
+
TypeError: Raised when level is not an integer.
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
The corresponding integer level for use in standard logging.
|
| 147 |
+
"""
|
| 148 |
+
if not isinstance(level, int):
|
| 149 |
+
raise TypeError(f'Expect an int level, found {type(level)}')
|
| 150 |
+
if level < ABSL_FATAL:
|
| 151 |
+
level = ABSL_FATAL
|
| 152 |
+
if level <= ABSL_DEBUG:
|
| 153 |
+
return ABSL_TO_STANDARD[level]
|
| 154 |
+
# Maps to vlog levels.
|
| 155 |
+
return STANDARD_DEBUG - level + 1
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def string_to_standard(level):
|
| 159 |
+
"""Converts a string level to standard logging level value.
|
| 160 |
+
|
| 161 |
+
Args:
|
| 162 |
+
level: str, case-insensitive ``'debug'``, ``'info'``, ``'warning'``,
|
| 163 |
+
``'error'``, ``'fatal'``.
|
| 164 |
+
|
| 165 |
+
Returns:
|
| 166 |
+
The corresponding integer level for use in standard logging.
|
| 167 |
+
"""
|
| 168 |
+
return absl_to_standard(ABSL_NAMES.get(level.upper()))
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def standard_to_absl(level):
|
| 172 |
+
"""Converts an integer level from the standard value to the absl value.
|
| 173 |
+
|
| 174 |
+
Args:
|
| 175 |
+
level: int, a Python standard logging level.
|
| 176 |
+
|
| 177 |
+
Raises:
|
| 178 |
+
TypeError: Raised when level is not an integer.
|
| 179 |
+
|
| 180 |
+
Returns:
|
| 181 |
+
The corresponding integer level for use in absl logging.
|
| 182 |
+
"""
|
| 183 |
+
if not isinstance(level, int):
|
| 184 |
+
raise TypeError(f'Expect an int level, found {type(level)}')
|
| 185 |
+
if level < 0:
|
| 186 |
+
level = 0
|
| 187 |
+
if level < STANDARD_DEBUG:
|
| 188 |
+
# Maps to vlog levels.
|
| 189 |
+
return STANDARD_DEBUG - level + 1
|
| 190 |
+
elif level < STANDARD_INFO:
|
| 191 |
+
return ABSL_DEBUG
|
| 192 |
+
elif level < STANDARD_WARNING:
|
| 193 |
+
return ABSL_INFO
|
| 194 |
+
elif level < STANDARD_ERROR:
|
| 195 |
+
return ABSL_WARNING
|
| 196 |
+
elif level < STANDARD_CRITICAL:
|
| 197 |
+
return ABSL_ERROR
|
| 198 |
+
else:
|
| 199 |
+
return ABSL_FATAL
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def standard_to_cpp(level):
|
| 203 |
+
"""Converts an integer level from the standard value to the cpp value.
|
| 204 |
+
|
| 205 |
+
Args:
|
| 206 |
+
level: int, a Python standard logging level.
|
| 207 |
+
|
| 208 |
+
Raises:
|
| 209 |
+
TypeError: Raised when level is not an integer.
|
| 210 |
+
|
| 211 |
+
Returns:
|
| 212 |
+
The corresponding integer level for use in cpp logging.
|
| 213 |
+
"""
|
| 214 |
+
return absl_to_cpp(standard_to_absl(level))
|
.venv/lib/python3.13/site-packages/absl/testing/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
.venv/lib/python3.13/site-packages/absl/testing/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (187 Bytes). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/testing/__pycache__/_bazelize_command.cpython-313.pyc
ADDED
|
Binary file (2.38 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/testing/__pycache__/_pretty_print_reporter.cpython-313.pyc
ADDED
|
Binary file (4.73 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/testing/__pycache__/flagsaver.cpython-313.pyc
ADDED
|
Binary file (16.1 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/testing/__pycache__/parameterized.cpython-313.pyc
ADDED
|
Binary file (30.2 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/testing/__pycache__/xml_reporter.cpython-313.pyc
ADDED
|
Binary file (27.4 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/absl/testing/_bazelize_command.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Internal helper for running tests on Windows Bazel."""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
|
| 19 |
+
from absl import flags
|
| 20 |
+
|
| 21 |
+
FLAGS = flags.FLAGS
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def get_executable_path(py_binary_name):
|
| 25 |
+
"""Returns the executable path of a py_binary.
|
| 26 |
+
|
| 27 |
+
This returns the executable path of a py_binary that is in another Bazel
|
| 28 |
+
target's data dependencies.
|
| 29 |
+
|
| 30 |
+
On Linux/macOS, the path and __file__ has the same root directory.
|
| 31 |
+
On Windows, bazel builds an .exe file and we need to use the MANIFEST file
|
| 32 |
+
the location the actual binary.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
py_binary_name: string, the name of a py_binary that is in another Bazel
|
| 36 |
+
target's data dependencies.
|
| 37 |
+
|
| 38 |
+
Raises:
|
| 39 |
+
RuntimeError: Raised when it cannot locate the executable path.
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
if os.name == 'nt':
|
| 43 |
+
py_binary_name += '.exe'
|
| 44 |
+
manifest_file = os.path.join(FLAGS.test_srcdir, 'MANIFEST')
|
| 45 |
+
workspace_name = os.environ['TEST_WORKSPACE']
|
| 46 |
+
manifest_entry = f'{workspace_name}/{py_binary_name}'
|
| 47 |
+
with open(manifest_file) as manifest_fd:
|
| 48 |
+
for line in manifest_fd:
|
| 49 |
+
tokens = line.strip().split(' ')
|
| 50 |
+
if len(tokens) != 2:
|
| 51 |
+
continue
|
| 52 |
+
if manifest_entry == tokens[0]:
|
| 53 |
+
return tokens[1]
|
| 54 |
+
raise RuntimeError(
|
| 55 |
+
'Cannot locate executable path for {}, MANIFEST file: {}.'.format(
|
| 56 |
+
py_binary_name, manifest_file))
|
| 57 |
+
else:
|
| 58 |
+
# NOTE: __file__ may be .py or .pyc, depending on how the module was
|
| 59 |
+
# loaded and executed.
|
| 60 |
+
path = __file__
|
| 61 |
+
|
| 62 |
+
# Use the package name to find the root directory: every dot is
|
| 63 |
+
# a directory, plus one for ourselves.
|
| 64 |
+
for _ in range(__name__.count('.') + 1):
|
| 65 |
+
path = os.path.dirname(path)
|
| 66 |
+
|
| 67 |
+
root_directory = path
|
| 68 |
+
return os.path.join(root_directory, py_binary_name)
|
.venv/lib/python3.13/site-packages/absl/testing/_pretty_print_reporter.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2018 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""TestResult implementing default output for test execution status."""
|
| 16 |
+
|
| 17 |
+
import unittest
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TextTestResult(unittest.TextTestResult):
|
| 21 |
+
"""TestResult class that provides the default text result formatting."""
|
| 22 |
+
|
| 23 |
+
def __init__(self, stream, descriptions, verbosity):
|
| 24 |
+
# Disable the verbose per-test output from the superclass, since it would
|
| 25 |
+
# conflict with our customized output.
|
| 26 |
+
super().__init__(stream, descriptions, 0)
|
| 27 |
+
self._per_test_output = verbosity > 0
|
| 28 |
+
|
| 29 |
+
def _print_status(self, tag, test, reason=None):
|
| 30 |
+
if self._per_test_output:
|
| 31 |
+
test_id = test.id()
|
| 32 |
+
if test_id.startswith('__main__.'):
|
| 33 |
+
test_id = test_id[len('__main__.'):]
|
| 34 |
+
if reason:
|
| 35 |
+
print('[%s] %s - %s' % (tag, test_id, reason), file=self.stream)
|
| 36 |
+
else:
|
| 37 |
+
print('[%s] %s' % (tag, test_id), file=self.stream)
|
| 38 |
+
self.stream.flush()
|
| 39 |
+
|
| 40 |
+
def startTest(self, test):
|
| 41 |
+
super().startTest(test)
|
| 42 |
+
self._print_status(' RUN ', test)
|
| 43 |
+
|
| 44 |
+
def addSuccess(self, test):
|
| 45 |
+
super().addSuccess(test)
|
| 46 |
+
self._print_status(' OK ', test)
|
| 47 |
+
|
| 48 |
+
def addError(self, test, err):
|
| 49 |
+
super().addError(test, err)
|
| 50 |
+
self._print_status(' FAILED ', test)
|
| 51 |
+
|
| 52 |
+
def addFailure(self, test, err):
|
| 53 |
+
super().addFailure(test, err)
|
| 54 |
+
self._print_status(' FAILED ', test)
|
| 55 |
+
|
| 56 |
+
def addSkip(self, test, reason):
|
| 57 |
+
super().addSkip(test, reason)
|
| 58 |
+
self._print_status(' SKIPPED ', test, reason)
|
| 59 |
+
|
| 60 |
+
def addExpectedFailure(self, test, err):
|
| 61 |
+
super().addExpectedFailure(test, err)
|
| 62 |
+
self._print_status(' OK ', test)
|
| 63 |
+
|
| 64 |
+
def addUnexpectedSuccess(self, test):
|
| 65 |
+
super().addUnexpectedSuccess(test)
|
| 66 |
+
self._print_status(' FAILED ', test)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class TextTestRunner(unittest.TextTestRunner):
|
| 70 |
+
"""A test runner that produces formatted text results."""
|
| 71 |
+
|
| 72 |
+
_TEST_RESULT_CLASS = TextTestResult
|
| 73 |
+
|
| 74 |
+
# Set this to true at the class or instance level to run tests using a
|
| 75 |
+
# debug-friendly method (e.g, one that doesn't catch exceptions and interacts
|
| 76 |
+
# better with debuggers).
|
| 77 |
+
# Usually this is set using --pdb_post_mortem.
|
| 78 |
+
run_for_debugging = False
|
| 79 |
+
|
| 80 |
+
def run(self, test) -> unittest.TextTestResult:
|
| 81 |
+
if self.run_for_debugging:
|
| 82 |
+
return self._run_debug(test)
|
| 83 |
+
else:
|
| 84 |
+
return super().run(test)
|
| 85 |
+
|
| 86 |
+
def _run_debug(self, test) -> unittest.TextTestResult:
|
| 87 |
+
test.debug()
|
| 88 |
+
# Return an empty result to indicate success.
|
| 89 |
+
return self._makeResult()
|
| 90 |
+
|
| 91 |
+
def _makeResult(self):
|
| 92 |
+
return TextTestResult(self.stream, self.descriptions, self.verbosity)
|
.venv/lib/python3.13/site-packages/absl/testing/absltest.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
.venv/lib/python3.13/site-packages/absl/testing/flagsaver.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Decorator and context manager for saving and restoring flag values.
|
| 16 |
+
|
| 17 |
+
There are many ways to save and restore. Always use the most convenient method
|
| 18 |
+
for a given use case.
|
| 19 |
+
|
| 20 |
+
Here are examples of each method. They all call ``do_stuff()`` while
|
| 21 |
+
``FLAGS.someflag`` is temporarily set to ``'foo'``::
|
| 22 |
+
|
| 23 |
+
from absl.testing import flagsaver
|
| 24 |
+
|
| 25 |
+
# Use a decorator which can optionally override flags via arguments.
|
| 26 |
+
@flagsaver.flagsaver(someflag='foo')
|
| 27 |
+
def some_func():
|
| 28 |
+
do_stuff()
|
| 29 |
+
|
| 30 |
+
# Use a decorator which can optionally override flags with flagholders.
|
| 31 |
+
@flagsaver.flagsaver((module.FOO_FLAG, 'foo'), (other_mod.BAR_FLAG, 23))
|
| 32 |
+
def some_func():
|
| 33 |
+
do_stuff()
|
| 34 |
+
|
| 35 |
+
# Use a decorator which does not override flags itself.
|
| 36 |
+
@flagsaver.flagsaver
|
| 37 |
+
def some_func():
|
| 38 |
+
FLAGS.someflag = 'foo'
|
| 39 |
+
do_stuff()
|
| 40 |
+
|
| 41 |
+
# Use a context manager which can optionally override flags via arguments.
|
| 42 |
+
with flagsaver.flagsaver(someflag='foo'):
|
| 43 |
+
do_stuff()
|
| 44 |
+
|
| 45 |
+
# Save and restore the flag values yourself.
|
| 46 |
+
saved_flag_values = flagsaver.save_flag_values()
|
| 47 |
+
try:
|
| 48 |
+
FLAGS.someflag = 'foo'
|
| 49 |
+
do_stuff()
|
| 50 |
+
finally:
|
| 51 |
+
flagsaver.restore_flag_values(saved_flag_values)
|
| 52 |
+
|
| 53 |
+
# Use the parsing version to emulate users providing the flags.
|
| 54 |
+
# Note that all flags must be provided as strings (unparsed).
|
| 55 |
+
@flagsaver.as_parsed(some_int_flag='123')
|
| 56 |
+
def some_func():
|
| 57 |
+
# Because the flag was parsed it is considered "present".
|
| 58 |
+
assert FLAGS.some_int_flag.present
|
| 59 |
+
do_stuff()
|
| 60 |
+
|
| 61 |
+
# flagsaver.as_parsed() can also be used as a context manager just like
|
| 62 |
+
# flagsaver.flagsaver()
|
| 63 |
+
with flagsaver.as_parsed(some_int_flag='123'):
|
| 64 |
+
do_stuff()
|
| 65 |
+
|
| 66 |
+
# The flagsaver.as_parsed() interface also supports FlagHolder objects.
|
| 67 |
+
@flagsaver.as_parsed((module.FOO_FLAG, 'foo'), (other_mod.BAR_FLAG, '23'))
|
| 68 |
+
def some_func():
|
| 69 |
+
do_stuff()
|
| 70 |
+
|
| 71 |
+
# Using as_parsed with a multi_X flag requires a sequence of strings.
|
| 72 |
+
@flagsaver.as_parsed(some_multi_int_flag=['123', '456'])
|
| 73 |
+
def some_func():
|
| 74 |
+
assert FLAGS.some_multi_int_flag.present
|
| 75 |
+
do_stuff()
|
| 76 |
+
|
| 77 |
+
# If a flag name includes non-identifier characters it can be specified like
|
| 78 |
+
# so:
|
| 79 |
+
@flagsaver.as_parsed(**{'i-like-dashes': 'true'})
|
| 80 |
+
def some_func():
|
| 81 |
+
do_stuff()
|
| 82 |
+
|
| 83 |
+
We save and restore a shallow copy of each Flag object's ``__dict__`` attribute.
|
| 84 |
+
This preserves all attributes of the flag, such as whether or not it was
|
| 85 |
+
overridden from its default value.
|
| 86 |
+
|
| 87 |
+
WARNING: Currently a flag that is saved and then deleted cannot be restored. An
|
| 88 |
+
exception will be raised. However if you *add* a flag after saving flag values,
|
| 89 |
+
and then restore flag values, the added flag will be deleted with no errors.
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
from collections.abc import Callable, Mapping, Sequence
|
| 93 |
+
import functools
|
| 94 |
+
import inspect
|
| 95 |
+
from typing import Any, TypeVar, overload
|
| 96 |
+
|
| 97 |
+
from absl import flags
|
| 98 |
+
|
| 99 |
+
FLAGS = flags.FLAGS
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# The type of pre/post wrapped functions.
|
| 103 |
+
_CallableT = TypeVar('_CallableT', bound=Callable)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@overload
|
| 107 |
+
def flagsaver(func: _CallableT) -> _CallableT:
|
| 108 |
+
...
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@overload
|
| 112 |
+
def flagsaver(
|
| 113 |
+
*args: tuple[flags.FlagHolder, Any], **kwargs: Any
|
| 114 |
+
) -> '_FlagOverrider':
|
| 115 |
+
...
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def flagsaver(*args, **kwargs):
|
| 119 |
+
"""The main flagsaver interface. See module doc for usage."""
|
| 120 |
+
return _construct_overrider(_FlagOverrider, *args, **kwargs) # type: ignore[bad-return-type]
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
@overload
|
| 124 |
+
def as_parsed(
|
| 125 |
+
*args: tuple[flags.FlagHolder, str | Sequence[str]],
|
| 126 |
+
**kwargs: str | Sequence[str],
|
| 127 |
+
) -> '_ParsingFlagOverrider':
|
| 128 |
+
...
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@overload
|
| 132 |
+
def as_parsed(func: _CallableT) -> _CallableT:
|
| 133 |
+
...
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def as_parsed(*args, **kwargs):
|
| 137 |
+
"""Overrides flags by parsing strings, saves flag state similar to flagsaver.
|
| 138 |
+
|
| 139 |
+
This function can be used as either a decorator or context manager similar to
|
| 140 |
+
flagsaver.flagsaver(). However, where flagsaver.flagsaver() directly sets the
|
| 141 |
+
flags to new values, this function will parse the provided arguments as if
|
| 142 |
+
they were provided on the command line. Among other things, this will cause
|
| 143 |
+
`FLAGS['flag_name'].present == True`.
|
| 144 |
+
|
| 145 |
+
A note on unparsed input: For many flag types, the unparsed version will be
|
| 146 |
+
a single string. However for multi_x (multi_string, multi_integer, multi_enum)
|
| 147 |
+
the unparsed version will be a Sequence of strings.
|
| 148 |
+
|
| 149 |
+
Args:
|
| 150 |
+
*args: Tuples of FlagHolders and their unparsed value.
|
| 151 |
+
**kwargs: The keyword args are flag names, and the values are unparsed
|
| 152 |
+
values.
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
_ParsingFlagOverrider that serves as a context manager or decorator. Will
|
| 156 |
+
save previous flag state and parse new flags, then on cleanup it will
|
| 157 |
+
restore the previous flag state.
|
| 158 |
+
"""
|
| 159 |
+
return _construct_overrider(_ParsingFlagOverrider, *args, **kwargs)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# NOTE: the order of these overload declarations matters. The type checker will
|
| 163 |
+
# pick the first match which could be incorrect.
|
| 164 |
+
@overload
|
| 165 |
+
def _construct_overrider(
|
| 166 |
+
flag_overrider_cls: type['_ParsingFlagOverrider'],
|
| 167 |
+
*args: tuple[flags.FlagHolder, str | Sequence[str]],
|
| 168 |
+
**kwargs: str | Sequence[str],
|
| 169 |
+
) -> '_ParsingFlagOverrider':
|
| 170 |
+
...
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
@overload
|
| 174 |
+
def _construct_overrider(
|
| 175 |
+
flag_overrider_cls: type['_FlagOverrider'], func: _CallableT
|
| 176 |
+
) -> _CallableT:
|
| 177 |
+
...
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
@overload
|
| 181 |
+
def _construct_overrider(
|
| 182 |
+
flag_overrider_cls: type['_FlagOverrider'],
|
| 183 |
+
*args: tuple[flags.FlagHolder, Any],
|
| 184 |
+
**kwargs: Any,
|
| 185 |
+
) -> '_FlagOverrider':
|
| 186 |
+
...
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _construct_overrider(flag_overrider_cls, *args, **kwargs):
|
| 190 |
+
"""Handles the args/kwargs returning an instance of flag_overrider_cls.
|
| 191 |
+
|
| 192 |
+
If flag_overrider_cls is _FlagOverrider then values should be native python
|
| 193 |
+
types matching the python types. Otherwise if flag_overrider_cls is
|
| 194 |
+
_ParsingFlagOverrider the values should be strings or sequences of strings.
|
| 195 |
+
|
| 196 |
+
Args:
|
| 197 |
+
flag_overrider_cls: The class that will do the overriding.
|
| 198 |
+
*args: Tuples of FlagHolder and the new flag value.
|
| 199 |
+
**kwargs: Keword args mapping flag name to new flag value.
|
| 200 |
+
|
| 201 |
+
Returns:
|
| 202 |
+
A _FlagOverrider to be used as a decorator or context manager.
|
| 203 |
+
"""
|
| 204 |
+
if not args:
|
| 205 |
+
return flag_overrider_cls(**kwargs)
|
| 206 |
+
# args can be [func] if used as `@flagsaver` instead of `@flagsaver(...)`
|
| 207 |
+
if len(args) == 1 and callable(args[0]):
|
| 208 |
+
if kwargs:
|
| 209 |
+
raise ValueError(
|
| 210 |
+
"It's invalid to specify both positional and keyword parameters.")
|
| 211 |
+
func = args[0]
|
| 212 |
+
if inspect.isclass(func):
|
| 213 |
+
raise TypeError('@flagsaver.flagsaver cannot be applied to a class.')
|
| 214 |
+
return _wrap(flag_overrider_cls, func, {})
|
| 215 |
+
# args can be a list of (FlagHolder, value) pairs.
|
| 216 |
+
# In which case they augment any specified kwargs.
|
| 217 |
+
for arg in args:
|
| 218 |
+
if not isinstance(arg, tuple) or len(arg) != 2:
|
| 219 |
+
raise ValueError('Expected (FlagHolder, value) pair, found %r' % (arg,))
|
| 220 |
+
holder, value = arg
|
| 221 |
+
if not isinstance(holder, flags.FlagHolder):
|
| 222 |
+
raise ValueError('Expected (FlagHolder, value) pair, found %r' % (arg,))
|
| 223 |
+
if holder.name in kwargs:
|
| 224 |
+
raise ValueError('Cannot set --%s multiple times' % holder.name)
|
| 225 |
+
kwargs[holder.name] = value
|
| 226 |
+
return flag_overrider_cls(**kwargs)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def save_flag_values(
|
| 230 |
+
flag_values: flags.FlagValues = FLAGS,
|
| 231 |
+
) -> dict[str, dict[str, Any]]:
|
| 232 |
+
"""Returns copy of flag values as a dict.
|
| 233 |
+
|
| 234 |
+
Args:
|
| 235 |
+
flag_values: FlagValues, the FlagValues instance with which the flag will be
|
| 236 |
+
saved. This should almost never need to be overridden.
|
| 237 |
+
|
| 238 |
+
Returns:
|
| 239 |
+
Dictionary mapping keys to values. Keys are flag names, values are
|
| 240 |
+
corresponding ``__dict__`` members. E.g. ``{'key': value_dict, ...}``.
|
| 241 |
+
"""
|
| 242 |
+
return {name: _copy_flag_dict(flag_values[name]) for name in flag_values}
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def restore_flag_values(
|
| 246 |
+
saved_flag_values: Mapping[str, dict[str, Any]],
|
| 247 |
+
flag_values: flags.FlagValues = FLAGS,
|
| 248 |
+
) -> None:
|
| 249 |
+
"""Restores flag values based on the dictionary of flag values.
|
| 250 |
+
|
| 251 |
+
Args:
|
| 252 |
+
saved_flag_values: {'flag_name': value_dict, ...}
|
| 253 |
+
flag_values: FlagValues, the FlagValues instance from which the flag will be
|
| 254 |
+
restored. This should almost never need to be overridden.
|
| 255 |
+
"""
|
| 256 |
+
new_flag_names = list(flag_values)
|
| 257 |
+
for name in new_flag_names:
|
| 258 |
+
saved = saved_flag_values.get(name)
|
| 259 |
+
if saved is None:
|
| 260 |
+
# If __dict__ was not saved delete "new" flag.
|
| 261 |
+
delattr(flag_values, name)
|
| 262 |
+
else:
|
| 263 |
+
if flag_values[name].value != saved['_value']:
|
| 264 |
+
flag_values[name].value = saved['_value'] # Ensure C++ value is set.
|
| 265 |
+
flag_values[name].__dict__ = saved
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
@overload
|
| 269 |
+
def _wrap(
|
| 270 |
+
flag_overrider_cls: type['_FlagOverrider'],
|
| 271 |
+
func: _CallableT,
|
| 272 |
+
overrides: Mapping[str, Any],
|
| 273 |
+
) -> _CallableT:
|
| 274 |
+
...
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
@overload
|
| 278 |
+
def _wrap(
|
| 279 |
+
flag_overrider_cls: type['_ParsingFlagOverrider'],
|
| 280 |
+
func: _CallableT,
|
| 281 |
+
overrides: Mapping[str, str | Sequence[str]],
|
| 282 |
+
) -> _CallableT:
|
| 283 |
+
...
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def _wrap(flag_overrider_cls, func, overrides):
|
| 287 |
+
"""Creates a wrapper function that saves/restores flag values.
|
| 288 |
+
|
| 289 |
+
Args:
|
| 290 |
+
flag_overrider_cls: The class that will be used as a context manager.
|
| 291 |
+
func: This will be called between saving flags and restoring flags.
|
| 292 |
+
overrides: Flag names mapped to their values. These flags will be set after
|
| 293 |
+
saving the original flag state. The type of the values depends on if
|
| 294 |
+
_FlagOverrider or _ParsingFlagOverrider was specified.
|
| 295 |
+
|
| 296 |
+
Returns:
|
| 297 |
+
A wrapped version of func.
|
| 298 |
+
"""
|
| 299 |
+
|
| 300 |
+
@functools.wraps(func)
|
| 301 |
+
def _flagsaver_wrapper(*args, **kwargs):
|
| 302 |
+
"""Wrapper function that saves and restores flags."""
|
| 303 |
+
with flag_overrider_cls(**overrides):
|
| 304 |
+
return func(*args, **kwargs)
|
| 305 |
+
|
| 306 |
+
return _flagsaver_wrapper
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
class _FlagOverrider:
|
| 310 |
+
"""Overrides flags for the duration of the decorated function call.
|
| 311 |
+
|
| 312 |
+
It also restores all original values of flags after decorated method
|
| 313 |
+
completes.
|
| 314 |
+
"""
|
| 315 |
+
|
| 316 |
+
def __init__(self, **overrides: Any):
|
| 317 |
+
self._overrides = overrides
|
| 318 |
+
self._saved_flag_values = None
|
| 319 |
+
|
| 320 |
+
def __call__(self, func: _CallableT) -> _CallableT:
|
| 321 |
+
if inspect.isclass(func):
|
| 322 |
+
raise TypeError('flagsaver cannot be applied to a class.')
|
| 323 |
+
return _wrap(self.__class__, func, self._overrides)
|
| 324 |
+
|
| 325 |
+
def __enter__(self):
|
| 326 |
+
self._saved_flag_values = save_flag_values(FLAGS)
|
| 327 |
+
try:
|
| 328 |
+
FLAGS._set_attributes(**self._overrides)
|
| 329 |
+
except:
|
| 330 |
+
# It may fail because of flag validators.
|
| 331 |
+
restore_flag_values(self._saved_flag_values, FLAGS)
|
| 332 |
+
raise
|
| 333 |
+
|
| 334 |
+
def __exit__(self, exc_type, exc_value, traceback):
|
| 335 |
+
restore_flag_values(self._saved_flag_values, FLAGS)
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
class _ParsingFlagOverrider(_FlagOverrider):
|
| 339 |
+
"""Context manager for overriding flags.
|
| 340 |
+
|
| 341 |
+
Simulates command line parsing.
|
| 342 |
+
|
| 343 |
+
This is simlar to _FlagOverrider except that all **overrides should be
|
| 344 |
+
strings or sequences of strings, and when context is entered this class calls
|
| 345 |
+
.parse(value)
|
| 346 |
+
|
| 347 |
+
This results in the flags having .present set properly.
|
| 348 |
+
"""
|
| 349 |
+
|
| 350 |
+
def __init__(self, **overrides: str | Sequence[str]):
|
| 351 |
+
for flag_name, new_value in overrides.items():
|
| 352 |
+
if isinstance(new_value, str):
|
| 353 |
+
continue
|
| 354 |
+
if isinstance(new_value, Sequence) and all(
|
| 355 |
+
isinstance(single_value, str) for single_value in new_value
|
| 356 |
+
):
|
| 357 |
+
continue
|
| 358 |
+
raise TypeError(
|
| 359 |
+
f'flagsaver.as_parsed() cannot parse {flag_name}. Expected a single '
|
| 360 |
+
f'string or sequence of strings but {type(new_value)} was provided.')
|
| 361 |
+
super().__init__(**overrides)
|
| 362 |
+
|
| 363 |
+
def __enter__(self):
|
| 364 |
+
self._saved_flag_values = save_flag_values(FLAGS)
|
| 365 |
+
try:
|
| 366 |
+
for flag_name, unparsed_value in self._overrides.items():
|
| 367 |
+
# LINT.IfChange(flag_override_parsing)
|
| 368 |
+
FLAGS[flag_name].parse(unparsed_value)
|
| 369 |
+
FLAGS[flag_name].using_default_value = False
|
| 370 |
+
# LINT.ThenChange()
|
| 371 |
+
|
| 372 |
+
# Perform the validation on all modified flags. This is something that
|
| 373 |
+
# FLAGS._set_attributes() does for you in _FlagOverrider.
|
| 374 |
+
for flag_name in self._overrides:
|
| 375 |
+
FLAGS._assert_validators(FLAGS[flag_name].validators)
|
| 376 |
+
|
| 377 |
+
except KeyError as e:
|
| 378 |
+
# If a flag doesn't exist, an UnrecognizedFlagError is more specific.
|
| 379 |
+
restore_flag_values(self._saved_flag_values, FLAGS)
|
| 380 |
+
raise flags.UnrecognizedFlagError('Unknown command line flag.') from e
|
| 381 |
+
|
| 382 |
+
except:
|
| 383 |
+
# It may fail because of flag validators or general parsing issues.
|
| 384 |
+
restore_flag_values(self._saved_flag_values, FLAGS)
|
| 385 |
+
raise
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def _copy_flag_dict(flag: flags.Flag) -> dict[str, Any]:
|
| 389 |
+
"""Returns a copy of the flag object's ``__dict__``.
|
| 390 |
+
|
| 391 |
+
It's mostly a shallow copy of the ``__dict__``, except it also does a shallow
|
| 392 |
+
copy of the validator list.
|
| 393 |
+
|
| 394 |
+
Args:
|
| 395 |
+
flag: flags.Flag, the flag to copy.
|
| 396 |
+
|
| 397 |
+
Returns:
|
| 398 |
+
A copy of the flag object's ``__dict__``.
|
| 399 |
+
"""
|
| 400 |
+
copy = flag.__dict__.copy()
|
| 401 |
+
copy['_value'] = flag.value # Ensure correct restore for C++ flags.
|
| 402 |
+
copy['validators'] = list(flag.validators)
|
| 403 |
+
return copy
|
.venv/lib/python3.13/site-packages/absl/testing/parameterized.py
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Adds support for parameterized tests to Python's unittest TestCase class.
|
| 16 |
+
|
| 17 |
+
A parameterized test is a method in a test case that is invoked with different
|
| 18 |
+
argument tuples.
|
| 19 |
+
|
| 20 |
+
A simple example::
|
| 21 |
+
|
| 22 |
+
class AdditionExample(parameterized.TestCase):
|
| 23 |
+
@parameterized.parameters(
|
| 24 |
+
(1, 2, 3),
|
| 25 |
+
(4, 5, 9),
|
| 26 |
+
(1, 1, 3))
|
| 27 |
+
def testAddition(self, op1, op2, result):
|
| 28 |
+
self.assertEqual(result, op1 + op2)
|
| 29 |
+
|
| 30 |
+
Each invocation is a separate test case and properly isolated just
|
| 31 |
+
like a normal test method, with its own setUp/tearDown cycle. In the
|
| 32 |
+
example above, there are three separate testcases, one of which will
|
| 33 |
+
fail due to an assertion error (1 + 1 != 3).
|
| 34 |
+
|
| 35 |
+
Parameters for individual test cases can be tuples (with positional parameters)
|
| 36 |
+
or dictionaries (with named parameters)::
|
| 37 |
+
|
| 38 |
+
class AdditionExample(parameterized.TestCase):
|
| 39 |
+
@parameterized.parameters(
|
| 40 |
+
{'op1': 1, 'op2': 2, 'result': 3},
|
| 41 |
+
{'op1': 4, 'op2': 5, 'result': 9},
|
| 42 |
+
)
|
| 43 |
+
def testAddition(self, op1, op2, result):
|
| 44 |
+
self.assertEqual(result, op1 + op2)
|
| 45 |
+
|
| 46 |
+
If a parameterized test fails, the error message will show the
|
| 47 |
+
original test name and the parameters for that test.
|
| 48 |
+
|
| 49 |
+
The id method of the test, used internally by the unittest framework, is also
|
| 50 |
+
modified to show the arguments (but note that the name reported by `id()`
|
| 51 |
+
doesn't match the actual test name, see below). To make sure that test names
|
| 52 |
+
stay the same across several invocations, object representations like::
|
| 53 |
+
|
| 54 |
+
>>> class Foo(object):
|
| 55 |
+
... pass
|
| 56 |
+
>>> repr(Foo())
|
| 57 |
+
'<__main__.Foo object at 0x23d8610>'
|
| 58 |
+
|
| 59 |
+
are turned into ``__main__.Foo``. When selecting a subset of test cases to run
|
| 60 |
+
on the command-line, the test cases contain an index suffix for each argument
|
| 61 |
+
in the order they were passed to :func:`parameters` (eg. testAddition0,
|
| 62 |
+
testAddition1, etc.) This naming scheme is subject to change; for more reliable
|
| 63 |
+
and stable names, especially in test logs, use :func:`named_parameters` instead.
|
| 64 |
+
|
| 65 |
+
Tests using :func:`named_parameters` are similar to :func:`parameters`, except
|
| 66 |
+
only tuples or dicts of args are supported. For tuples, the first parameter arg
|
| 67 |
+
has to be a string (or an object that returns an apt name when converted via
|
| 68 |
+
``str()``). For dicts, a value for the key ``testcase_name`` must be present and
|
| 69 |
+
must be a string (or an object that returns an apt name when converted via
|
| 70 |
+
``str()``)::
|
| 71 |
+
|
| 72 |
+
class NamedExample(parameterized.TestCase):
|
| 73 |
+
@parameterized.named_parameters(
|
| 74 |
+
('Normal', 'aa', 'aaa', True),
|
| 75 |
+
('EmptyPrefix', '', 'abc', True),
|
| 76 |
+
('BothEmpty', '', '', True))
|
| 77 |
+
def testStartsWith(self, prefix, string, result):
|
| 78 |
+
self.assertEqual(result, string.startswith(prefix))
|
| 79 |
+
|
| 80 |
+
class NamedExample(parameterized.TestCase):
|
| 81 |
+
@parameterized.named_parameters(
|
| 82 |
+
{'testcase_name': 'Normal',
|
| 83 |
+
'result': True, 'string': 'aaa', 'prefix': 'aa'},
|
| 84 |
+
{'testcase_name': 'EmptyPrefix',
|
| 85 |
+
'result': True, 'string': 'abc', 'prefix': ''},
|
| 86 |
+
{'testcase_name': 'BothEmpty',
|
| 87 |
+
'result': True, 'string': '', 'prefix': ''})
|
| 88 |
+
def testStartsWith(self, prefix, string, result):
|
| 89 |
+
self.assertEqual(result, string.startswith(prefix))
|
| 90 |
+
|
| 91 |
+
Named tests also have the benefit that they can be run individually
|
| 92 |
+
from the command line::
|
| 93 |
+
|
| 94 |
+
$ testmodule.py NamedExample.testStartsWithNormal
|
| 95 |
+
.
|
| 96 |
+
--------------------------------------------------------------------
|
| 97 |
+
Ran 1 test in 0.000s
|
| 98 |
+
|
| 99 |
+
OK
|
| 100 |
+
|
| 101 |
+
Parameterized Classes
|
| 102 |
+
=====================
|
| 103 |
+
|
| 104 |
+
If invocation arguments are shared across test methods in a single
|
| 105 |
+
TestCase class, instead of decorating all test methods
|
| 106 |
+
individually, the class itself can be decorated::
|
| 107 |
+
|
| 108 |
+
@parameterized.parameters(
|
| 109 |
+
(1, 2, 3),
|
| 110 |
+
(4, 5, 9))
|
| 111 |
+
class ArithmeticTest(parameterized.TestCase):
|
| 112 |
+
def testAdd(self, arg1, arg2, result):
|
| 113 |
+
self.assertEqual(arg1 + arg2, result)
|
| 114 |
+
|
| 115 |
+
def testSubtract(self, arg1, arg2, result):
|
| 116 |
+
self.assertEqual(result - arg1, arg2)
|
| 117 |
+
|
| 118 |
+
Inputs from Iterables
|
| 119 |
+
=====================
|
| 120 |
+
|
| 121 |
+
If parameters should be shared across several test cases, or are dynamically
|
| 122 |
+
created from other sources, a single non-tuple iterable can be passed into
|
| 123 |
+
the decorator. This iterable will be used to obtain the test cases::
|
| 124 |
+
|
| 125 |
+
class AdditionExample(parameterized.TestCase):
|
| 126 |
+
@parameterized.parameters(
|
| 127 |
+
c.op1, c.op2, c.result for c in testcases
|
| 128 |
+
)
|
| 129 |
+
def testAddition(self, op1, op2, result):
|
| 130 |
+
self.assertEqual(result, op1 + op2)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
Single-Argument Test Methods
|
| 134 |
+
============================
|
| 135 |
+
|
| 136 |
+
If a test method takes only one argument, the single arguments must not be
|
| 137 |
+
wrapped into a tuple::
|
| 138 |
+
|
| 139 |
+
class NegativeNumberExample(parameterized.TestCase):
|
| 140 |
+
@parameterized.parameters(
|
| 141 |
+
-1, -3, -4, -5
|
| 142 |
+
)
|
| 143 |
+
def testIsNegative(self, arg):
|
| 144 |
+
self.assertTrue(IsNegative(arg))
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
List/tuple as a Single Argument
|
| 148 |
+
===============================
|
| 149 |
+
|
| 150 |
+
If a test method takes a single argument of a list/tuple, it must be wrapped
|
| 151 |
+
inside a tuple::
|
| 152 |
+
|
| 153 |
+
class ZeroSumExample(parameterized.TestCase):
|
| 154 |
+
@parameterized.parameters(
|
| 155 |
+
([-1, 0, 1], ),
|
| 156 |
+
([-2, 0, 2], ),
|
| 157 |
+
)
|
| 158 |
+
def testSumIsZero(self, arg):
|
| 159 |
+
self.assertEqual(0, sum(arg))
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
Cartesian product of Parameter Values as Parameterized Test Cases
|
| 163 |
+
=================================================================
|
| 164 |
+
|
| 165 |
+
If required to test method over a cartesian product of parameters,
|
| 166 |
+
`parameterized.product` may be used to facilitate generation of parameters
|
| 167 |
+
test combinations::
|
| 168 |
+
|
| 169 |
+
class TestModuloExample(parameterized.TestCase):
|
| 170 |
+
@parameterized.product(
|
| 171 |
+
num=[0, 20, 80],
|
| 172 |
+
modulo=[2, 4],
|
| 173 |
+
expected=[0]
|
| 174 |
+
)
|
| 175 |
+
def testModuloResult(self, num, modulo, expected):
|
| 176 |
+
self.assertEqual(expected, num % modulo)
|
| 177 |
+
|
| 178 |
+
This results in 6 test cases being created - one for each combination of the
|
| 179 |
+
parameters. It is also possible to supply sequences of keyword argument dicts
|
| 180 |
+
as elements of the cartesian product::
|
| 181 |
+
|
| 182 |
+
@parameterized.product(
|
| 183 |
+
(dict(num=5, modulo=3, expected=2),
|
| 184 |
+
dict(num=7, modulo=4, expected=3)),
|
| 185 |
+
dtype=(int, float)
|
| 186 |
+
)
|
| 187 |
+
def testModuloResult(self, num, modulo, expected, dtype):
|
| 188 |
+
self.assertEqual(expected, dtype(num) % modulo)
|
| 189 |
+
|
| 190 |
+
This results in 4 test cases being created - for each of the two sets of test
|
| 191 |
+
data (supplied as kwarg dicts) and for each of the two data types (supplied as
|
| 192 |
+
a named parameter). Multiple keyword argument dicts may be supplied if required.
|
| 193 |
+
|
| 194 |
+
Async Support
|
| 195 |
+
=============
|
| 196 |
+
|
| 197 |
+
If a test needs to call async functions, it can inherit from both
|
| 198 |
+
parameterized.TestCase and another TestCase that supports async calls, such
|
| 199 |
+
as [asynctest](https://github.com/Martiusweb/asynctest)::
|
| 200 |
+
|
| 201 |
+
import asynctest
|
| 202 |
+
|
| 203 |
+
class AsyncExample(parameterized.TestCase, asynctest.TestCase):
|
| 204 |
+
@parameterized.parameters(
|
| 205 |
+
('a', 1),
|
| 206 |
+
('b', 2),
|
| 207 |
+
)
|
| 208 |
+
async def testSomeAsyncFunction(self, arg, expected):
|
| 209 |
+
actual = await someAsyncFunction(arg)
|
| 210 |
+
self.assertEqual(actual, expected)
|
| 211 |
+
"""
|
| 212 |
+
|
| 213 |
+
from collections import abc
|
| 214 |
+
import functools
|
| 215 |
+
import inspect
|
| 216 |
+
import itertools
|
| 217 |
+
import re
|
| 218 |
+
import types
|
| 219 |
+
import unittest
|
| 220 |
+
import warnings
|
| 221 |
+
|
| 222 |
+
from absl.testing import absltest
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
_ADDR_RE = re.compile(r'\<([a-zA-Z0-9_\-\.]+) object at 0x[a-fA-F0-9]+\>')
|
| 226 |
+
_NAMED = object()
|
| 227 |
+
_ARGUMENT_REPR = object()
|
| 228 |
+
_NAMED_DICT_KEY = 'testcase_name'
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
class NoTestsError(Exception):
|
| 232 |
+
"""Raised when parameterized decorators do not generate any tests."""
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class DuplicateTestNameError(Exception):
|
| 236 |
+
"""Raised when a parameterized test has the same test name multiple times."""
|
| 237 |
+
|
| 238 |
+
def __init__(self, test_class_name, new_test_name, original_test_name):
|
| 239 |
+
super().__init__(
|
| 240 |
+
'Duplicate parameterized test name in {}: generated test name {!r} '
|
| 241 |
+
'(generated from {!r}) already exists. Consider using '
|
| 242 |
+
'named_parameters() to give your tests unique names and/or renaming '
|
| 243 |
+
'the conflicting test method.'.format(
|
| 244 |
+
test_class_name, new_test_name, original_test_name
|
| 245 |
+
)
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def _clean_repr(obj):
|
| 250 |
+
return _ADDR_RE.sub(r'<\1>', repr(obj))
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def _non_string_or_bytes_iterable(obj):
|
| 254 |
+
return (isinstance(obj, abc.Iterable) and not isinstance(obj, str) and
|
| 255 |
+
not isinstance(obj, bytes))
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def _format_parameter_list(testcase_params):
|
| 259 |
+
if isinstance(testcase_params, abc.Mapping):
|
| 260 |
+
return ', '.join('%s=%s' % (argname, _clean_repr(value))
|
| 261 |
+
for argname, value in testcase_params.items())
|
| 262 |
+
elif _non_string_or_bytes_iterable(testcase_params):
|
| 263 |
+
return ', '.join(map(_clean_repr, testcase_params))
|
| 264 |
+
else:
|
| 265 |
+
return _format_parameter_list((testcase_params,))
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def _async_wrapped(func):
|
| 269 |
+
@functools.wraps(func)
|
| 270 |
+
async def wrapper(*args, **kwargs):
|
| 271 |
+
return await func(*args, **kwargs)
|
| 272 |
+
return wrapper
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
class _ParameterizedTestIter:
|
| 276 |
+
"""Callable and iterable class for producing new test cases."""
|
| 277 |
+
|
| 278 |
+
def __init__(self, test_method, testcases, naming_type, original_name=None):
|
| 279 |
+
"""Returns concrete test functions for a test and a list of parameters.
|
| 280 |
+
|
| 281 |
+
The naming_type is used to determine the name of the concrete
|
| 282 |
+
functions as reported by the unittest framework. If naming_type is
|
| 283 |
+
_FIRST_ARG, the testcases must be tuples, and the first element must
|
| 284 |
+
have a string representation that is a valid Python identifier.
|
| 285 |
+
|
| 286 |
+
Args:
|
| 287 |
+
test_method: The decorated test method.
|
| 288 |
+
testcases: (list of tuple/dict) A list of parameter tuples/dicts for
|
| 289 |
+
individual test invocations.
|
| 290 |
+
naming_type: The test naming type, either _NAMED or _ARGUMENT_REPR.
|
| 291 |
+
original_name: The original test method name. When decorated on a test
|
| 292 |
+
method, None is passed to __init__ and test_method.__name__ is used.
|
| 293 |
+
Note test_method.__name__ might be different than the original defined
|
| 294 |
+
test method because of the use of other decorators. A more accurate
|
| 295 |
+
value is set by TestGeneratorMetaclass.__new__ later.
|
| 296 |
+
"""
|
| 297 |
+
self._test_method = test_method
|
| 298 |
+
self.testcases = testcases
|
| 299 |
+
self._naming_type = naming_type
|
| 300 |
+
if original_name is None:
|
| 301 |
+
original_name = test_method.__name__
|
| 302 |
+
self._original_name = original_name
|
| 303 |
+
self.__name__ = _ParameterizedTestIter.__name__
|
| 304 |
+
|
| 305 |
+
def __call__(self, *args, **kwargs):
|
| 306 |
+
raise RuntimeError('You appear to be running a parameterized test case '
|
| 307 |
+
'without having inherited from parameterized.'
|
| 308 |
+
'TestCase. This is bad because none of '
|
| 309 |
+
'your test cases are actually being run. You may also '
|
| 310 |
+
'be using another decorator before the parameterized '
|
| 311 |
+
'one, in which case you should reverse the order.')
|
| 312 |
+
|
| 313 |
+
def __iter__(self):
|
| 314 |
+
test_method = self._test_method
|
| 315 |
+
naming_type = self._naming_type
|
| 316 |
+
|
| 317 |
+
def make_bound_param_test(testcase_params):
|
| 318 |
+
@functools.wraps(test_method)
|
| 319 |
+
def bound_param_test(self):
|
| 320 |
+
if isinstance(testcase_params, abc.Mapping):
|
| 321 |
+
return test_method(self, **testcase_params)
|
| 322 |
+
elif _non_string_or_bytes_iterable(testcase_params):
|
| 323 |
+
return test_method(self, *testcase_params)
|
| 324 |
+
else:
|
| 325 |
+
return test_method(self, testcase_params)
|
| 326 |
+
|
| 327 |
+
if naming_type is _NAMED:
|
| 328 |
+
# Signal the metaclass that the name of the test function is unique
|
| 329 |
+
# and descriptive.
|
| 330 |
+
bound_param_test.__x_use_name__ = True
|
| 331 |
+
|
| 332 |
+
testcase_name = None
|
| 333 |
+
if isinstance(testcase_params, abc.Mapping):
|
| 334 |
+
if _NAMED_DICT_KEY not in testcase_params:
|
| 335 |
+
raise RuntimeError(
|
| 336 |
+
'Dict for named tests must contain key "%s"' % _NAMED_DICT_KEY)
|
| 337 |
+
# Create a new dict to avoid modifying the supplied testcase_params.
|
| 338 |
+
testcase_name = testcase_params[_NAMED_DICT_KEY]
|
| 339 |
+
testcase_params = {
|
| 340 |
+
k: v for k, v in testcase_params.items() if k != _NAMED_DICT_KEY
|
| 341 |
+
}
|
| 342 |
+
elif _non_string_or_bytes_iterable(testcase_params):
|
| 343 |
+
if not isinstance(testcase_params[0], str):
|
| 344 |
+
raise RuntimeError(
|
| 345 |
+
'The first element of named test parameters is the test name '
|
| 346 |
+
'suffix and must be a string')
|
| 347 |
+
testcase_name = testcase_params[0]
|
| 348 |
+
testcase_params = testcase_params[1:]
|
| 349 |
+
else:
|
| 350 |
+
raise RuntimeError(
|
| 351 |
+
'Named tests must be passed a dict or non-string iterable.')
|
| 352 |
+
|
| 353 |
+
test_method_name = self._original_name
|
| 354 |
+
# Support PEP-8 underscore style for test naming if used.
|
| 355 |
+
if (test_method_name.startswith('test_')
|
| 356 |
+
and testcase_name
|
| 357 |
+
and not testcase_name.startswith('_')):
|
| 358 |
+
test_method_name += '_'
|
| 359 |
+
|
| 360 |
+
bound_param_test.__name__ = test_method_name + str(testcase_name)
|
| 361 |
+
elif naming_type is _ARGUMENT_REPR:
|
| 362 |
+
# If it's a generator, convert it to a tuple and treat them as
|
| 363 |
+
# parameters.
|
| 364 |
+
if isinstance(testcase_params, types.GeneratorType):
|
| 365 |
+
testcase_params = tuple(testcase_params)
|
| 366 |
+
# The metaclass creates a unique, but non-descriptive method name for
|
| 367 |
+
# _ARGUMENT_REPR tests using an indexed suffix.
|
| 368 |
+
# To keep test names descriptive, only the original method name is used.
|
| 369 |
+
# To make sure test names are unique, we add a unique descriptive suffix
|
| 370 |
+
# __x_params_repr__ for every test.
|
| 371 |
+
params_repr = '(%s)' % (_format_parameter_list(testcase_params),)
|
| 372 |
+
bound_param_test.__x_params_repr__ = params_repr
|
| 373 |
+
else:
|
| 374 |
+
raise RuntimeError('%s is not a valid naming type.' % (naming_type,))
|
| 375 |
+
|
| 376 |
+
bound_param_test.__doc__ = '%s(%s)' % (
|
| 377 |
+
bound_param_test.__name__, _format_parameter_list(testcase_params))
|
| 378 |
+
if test_method.__doc__:
|
| 379 |
+
bound_param_test.__doc__ += '\n%s' % (test_method.__doc__,)
|
| 380 |
+
if inspect.iscoroutinefunction(test_method):
|
| 381 |
+
return _async_wrapped(bound_param_test)
|
| 382 |
+
return bound_param_test
|
| 383 |
+
|
| 384 |
+
return (make_bound_param_test(c) for c in self.testcases)
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def _modify_class(class_object, testcases, naming_type):
|
| 388 |
+
assert not getattr(class_object, '_test_params_reprs', None), (
|
| 389 |
+
'Cannot add parameters to %s. Either it already has parameterized '
|
| 390 |
+
'methods, or its super class is also a parameterized class.' % (
|
| 391 |
+
class_object,))
|
| 392 |
+
# NOTE: _test_params_repr is private to parameterized.TestCase and it's
|
| 393 |
+
# metaclass; do not use it outside of those classes.
|
| 394 |
+
class_object._test_params_reprs = test_params_reprs = {}
|
| 395 |
+
for name, obj in class_object.__dict__.copy().items():
|
| 396 |
+
if (name.startswith(unittest.TestLoader.testMethodPrefix)
|
| 397 |
+
and isinstance(obj, types.FunctionType)):
|
| 398 |
+
delattr(class_object, name)
|
| 399 |
+
methods = {}
|
| 400 |
+
_update_class_dict_for_param_test_case(
|
| 401 |
+
class_object.__name__, methods, test_params_reprs, name,
|
| 402 |
+
_ParameterizedTestIter(obj, testcases, naming_type, name))
|
| 403 |
+
for meth_name, meth in methods.items():
|
| 404 |
+
setattr(class_object, meth_name, meth)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def _parameter_decorator(naming_type, testcases):
|
| 408 |
+
"""Implementation of the parameterization decorators.
|
| 409 |
+
|
| 410 |
+
Args:
|
| 411 |
+
naming_type: The naming type.
|
| 412 |
+
testcases: Testcase parameters.
|
| 413 |
+
|
| 414 |
+
Raises:
|
| 415 |
+
NoTestsError: Raised when the decorator generates no tests.
|
| 416 |
+
|
| 417 |
+
Returns:
|
| 418 |
+
A function for modifying the decorated object.
|
| 419 |
+
"""
|
| 420 |
+
def _apply(obj):
|
| 421 |
+
if isinstance(obj, type):
|
| 422 |
+
_modify_class(obj, testcases, naming_type)
|
| 423 |
+
return obj
|
| 424 |
+
else:
|
| 425 |
+
return _ParameterizedTestIter(obj, testcases, naming_type)
|
| 426 |
+
|
| 427 |
+
if (len(testcases) == 1 and
|
| 428 |
+
not isinstance(testcases[0], tuple) and
|
| 429 |
+
not isinstance(testcases[0], abc.Mapping)):
|
| 430 |
+
# Support using a single non-tuple parameter as a list of test cases.
|
| 431 |
+
# Note that the single non-tuple parameter can't be Mapping either, which
|
| 432 |
+
# means a single dict parameter case.
|
| 433 |
+
assert _non_string_or_bytes_iterable(testcases[0]), (
|
| 434 |
+
'Single parameter argument must be a non-string non-Mapping iterable')
|
| 435 |
+
testcases = testcases[0]
|
| 436 |
+
|
| 437 |
+
if not isinstance(testcases, abc.Sequence):
|
| 438 |
+
testcases = list(testcases)
|
| 439 |
+
if not testcases:
|
| 440 |
+
raise NoTestsError(
|
| 441 |
+
'parameterized test decorators did not generate any tests. '
|
| 442 |
+
'Make sure you specify non-empty parameters, '
|
| 443 |
+
'and do not reuse generators more than once.')
|
| 444 |
+
|
| 445 |
+
return _apply
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def parameters(*testcases):
|
| 449 |
+
"""A decorator for creating parameterized tests.
|
| 450 |
+
|
| 451 |
+
See the module docstring for a usage example.
|
| 452 |
+
|
| 453 |
+
Args:
|
| 454 |
+
*testcases: Parameters for the decorated method, either a single
|
| 455 |
+
iterable, or a list of tuples/dicts/objects (for tests with only one
|
| 456 |
+
argument).
|
| 457 |
+
|
| 458 |
+
Raises:
|
| 459 |
+
NoTestsError: Raised when the decorator generates no tests.
|
| 460 |
+
|
| 461 |
+
Returns:
|
| 462 |
+
A test generator to be handled by TestGeneratorMetaclass.
|
| 463 |
+
"""
|
| 464 |
+
return _parameter_decorator(_ARGUMENT_REPR, testcases)
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def named_parameters(*testcases):
|
| 468 |
+
"""A decorator for creating parameterized tests.
|
| 469 |
+
|
| 470 |
+
See the module docstring for a usage example. For every parameter tuple
|
| 471 |
+
passed, the first element of the tuple should be a string and will be appended
|
| 472 |
+
to the name of the test method. Each parameter dict passed must have a value
|
| 473 |
+
for the key "testcase_name", the string representation of that value will be
|
| 474 |
+
appended to the name of the test method.
|
| 475 |
+
|
| 476 |
+
Args:
|
| 477 |
+
*testcases: Parameters for the decorated method, either a single iterable,
|
| 478 |
+
or a list of tuples or dicts.
|
| 479 |
+
|
| 480 |
+
Raises:
|
| 481 |
+
NoTestsError: Raised when the decorator generates no tests.
|
| 482 |
+
|
| 483 |
+
Returns:
|
| 484 |
+
A test generator to be handled by TestGeneratorMetaclass.
|
| 485 |
+
"""
|
| 486 |
+
return _parameter_decorator(_NAMED, testcases)
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
def product(*kwargs_seqs, **testgrid):
|
| 490 |
+
"""A decorator for running tests over cartesian product of parameters values.
|
| 491 |
+
|
| 492 |
+
See the module docstring for a usage example. The test will be run for every
|
| 493 |
+
possible combination of the parameters.
|
| 494 |
+
|
| 495 |
+
Args:
|
| 496 |
+
*kwargs_seqs: Each positional parameter is a sequence of keyword arg dicts;
|
| 497 |
+
every test case generated will include exactly one kwargs dict from each
|
| 498 |
+
positional parameter; these will then be merged to form an overall list
|
| 499 |
+
of arguments for the test case.
|
| 500 |
+
**testgrid: A mapping of parameter names and their possible values. Possible
|
| 501 |
+
values should given as either a list or a tuple.
|
| 502 |
+
|
| 503 |
+
Raises:
|
| 504 |
+
NoTestsError: Raised when the decorator generates no tests.
|
| 505 |
+
|
| 506 |
+
Returns:
|
| 507 |
+
A test generator to be handled by TestGeneratorMetaclass.
|
| 508 |
+
"""
|
| 509 |
+
|
| 510 |
+
for name, values in testgrid.items():
|
| 511 |
+
assert isinstance(values, (list, tuple)), (
|
| 512 |
+
'Values of {} must be given as list or tuple, found {}'.format(
|
| 513 |
+
name, type(values)))
|
| 514 |
+
|
| 515 |
+
prior_arg_names = set()
|
| 516 |
+
for kwargs_seq in kwargs_seqs:
|
| 517 |
+
assert ((isinstance(kwargs_seq, (list, tuple))) and
|
| 518 |
+
all(isinstance(kwargs, dict) for kwargs in kwargs_seq)), (
|
| 519 |
+
'Positional parameters must be a sequence of keyword arg'
|
| 520 |
+
'dicts, found {}'
|
| 521 |
+
.format(kwargs_seq))
|
| 522 |
+
if kwargs_seq:
|
| 523 |
+
arg_names = set(kwargs_seq[0])
|
| 524 |
+
assert all(set(kwargs) == arg_names for kwargs in kwargs_seq), (
|
| 525 |
+
'Keyword argument dicts within a single parameter must all have the '
|
| 526 |
+
'same keys, found {}'.format(kwargs_seq))
|
| 527 |
+
assert not (arg_names & prior_arg_names), (
|
| 528 |
+
'Keyword argument dict sequences must all have distinct argument '
|
| 529 |
+
'names, found duplicate(s) {}'
|
| 530 |
+
.format(sorted(arg_names & prior_arg_names)))
|
| 531 |
+
prior_arg_names |= arg_names
|
| 532 |
+
|
| 533 |
+
assert not (prior_arg_names & set(testgrid)), (
|
| 534 |
+
'Arguments supplied in kwargs dicts in positional parameters must not '
|
| 535 |
+
'overlap with arguments supplied as named parameters; found duplicate '
|
| 536 |
+
'argument(s) {}'.format(sorted(prior_arg_names & set(testgrid))))
|
| 537 |
+
|
| 538 |
+
# Convert testgrid into a sequence of sequences of kwargs dicts and combine
|
| 539 |
+
# with the positional parameters.
|
| 540 |
+
# So foo=[1,2], bar=[3,4] --> [[{foo: 1}, {foo: 2}], [{bar: 3, bar: 4}]]
|
| 541 |
+
testgrid = (tuple({k: v} for v in vs) for k, vs in testgrid.items())
|
| 542 |
+
testgrid = tuple(kwargs_seqs) + tuple(testgrid)
|
| 543 |
+
|
| 544 |
+
# Create all possible combinations of parameters as a cartesian product
|
| 545 |
+
# of parameter values.
|
| 546 |
+
testcases = [
|
| 547 |
+
dict(itertools.chain.from_iterable(case.items()
|
| 548 |
+
for case in cases))
|
| 549 |
+
for cases in itertools.product(*testgrid)
|
| 550 |
+
]
|
| 551 |
+
return _parameter_decorator(_ARGUMENT_REPR, testcases)
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
class TestGeneratorMetaclass(type):
|
| 555 |
+
"""Metaclass for adding tests generated by parameterized decorators."""
|
| 556 |
+
|
| 557 |
+
def __new__(cls, class_name, bases, dct):
|
| 558 |
+
# NOTE: _test_params_repr is private to parameterized.TestCase and it's
|
| 559 |
+
# metaclass; do not use it outside of those classes.
|
| 560 |
+
test_params_reprs = dct.setdefault('_test_params_reprs', {})
|
| 561 |
+
for name, obj in dct.copy().items():
|
| 562 |
+
if (name.startswith(unittest.TestLoader.testMethodPrefix) and
|
| 563 |
+
_non_string_or_bytes_iterable(obj)):
|
| 564 |
+
# NOTE: `obj` might not be a _ParameterizedTestIter in two cases:
|
| 565 |
+
# 1. a class-level iterable named test* that isn't a test, such as
|
| 566 |
+
# a list of something. Such attributes get deleted from the class.
|
| 567 |
+
#
|
| 568 |
+
# 2. If a decorator is applied to the parameterized test, e.g.
|
| 569 |
+
# @morestuff
|
| 570 |
+
# @parameterized.parameters(...)
|
| 571 |
+
# def test_foo(...): ...
|
| 572 |
+
#
|
| 573 |
+
# This is OK so long as the underlying parameterized function state
|
| 574 |
+
# is forwarded (e.g. using functool.wraps() and **without**
|
| 575 |
+
# accessing explicitly accessing the internal attributes.
|
| 576 |
+
if isinstance(obj, _ParameterizedTestIter):
|
| 577 |
+
# Update the original test method name so it's more accurate.
|
| 578 |
+
# The mismatch might happen when another decorator is used inside
|
| 579 |
+
# the parameterized decrators, and the inner decorator doesn't
|
| 580 |
+
# preserve its __name__.
|
| 581 |
+
obj._original_name = name
|
| 582 |
+
iterator = iter(obj)
|
| 583 |
+
dct.pop(name)
|
| 584 |
+
_update_class_dict_for_param_test_case(
|
| 585 |
+
class_name, dct, test_params_reprs, name, iterator)
|
| 586 |
+
# If the base class is a subclass of parameterized.TestCase, inherit its
|
| 587 |
+
# _test_params_reprs too.
|
| 588 |
+
for base in bases:
|
| 589 |
+
# Check if the base has _test_params_reprs first, then check if it's a
|
| 590 |
+
# subclass of parameterized.TestCase. Otherwise when this is called for
|
| 591 |
+
# the parameterized.TestCase definition itself, this raises because
|
| 592 |
+
# itself is not defined yet. This works as long as absltest.TestCase does
|
| 593 |
+
# not define _test_params_reprs.
|
| 594 |
+
base_test_params_reprs = getattr(base, '_test_params_reprs', None)
|
| 595 |
+
if base_test_params_reprs and issubclass(base, TestCase):
|
| 596 |
+
for test_method, test_method_id in base_test_params_reprs.items():
|
| 597 |
+
# test_method may both exists in base and this class.
|
| 598 |
+
# This class's method overrides base class's.
|
| 599 |
+
# That's why it should only inherit it if it does not exist.
|
| 600 |
+
test_params_reprs.setdefault(test_method, test_method_id)
|
| 601 |
+
|
| 602 |
+
return type.__new__(cls, class_name, bases, dct)
|
| 603 |
+
|
| 604 |
+
|
| 605 |
+
def _update_class_dict_for_param_test_case(
|
| 606 |
+
test_class_name, dct, test_params_reprs, name, iterator):
|
| 607 |
+
"""Adds individual test cases to a dictionary.
|
| 608 |
+
|
| 609 |
+
Args:
|
| 610 |
+
test_class_name: The name of the class tests are added to.
|
| 611 |
+
dct: The target dictionary.
|
| 612 |
+
test_params_reprs: The dictionary for mapping names to test IDs.
|
| 613 |
+
name: The original name of the test case.
|
| 614 |
+
iterator: The iterator generating the individual test cases.
|
| 615 |
+
|
| 616 |
+
Raises:
|
| 617 |
+
DuplicateTestNameError: Raised when a test name occurs multiple times.
|
| 618 |
+
RuntimeError: If non-parameterized functions are generated.
|
| 619 |
+
"""
|
| 620 |
+
for idx, func in enumerate(iterator):
|
| 621 |
+
assert callable(func), 'Test generators must yield callables, got %r' % (
|
| 622 |
+
func,)
|
| 623 |
+
if not (getattr(func, '__x_use_name__', None) or
|
| 624 |
+
getattr(func, '__x_params_repr__', None)):
|
| 625 |
+
raise RuntimeError(
|
| 626 |
+
'{}.{} generated a test function without using the parameterized '
|
| 627 |
+
'decorators. Only tests generated using the decorators are '
|
| 628 |
+
'supported.'.format(test_class_name, name))
|
| 629 |
+
|
| 630 |
+
if getattr(func, '__x_use_name__', False):
|
| 631 |
+
original_name = func.__name__
|
| 632 |
+
new_name = original_name
|
| 633 |
+
else:
|
| 634 |
+
original_name = name
|
| 635 |
+
new_name = '%s%d' % (original_name, idx)
|
| 636 |
+
|
| 637 |
+
if new_name in dct:
|
| 638 |
+
raise DuplicateTestNameError(test_class_name, new_name, original_name)
|
| 639 |
+
|
| 640 |
+
dct[new_name] = func
|
| 641 |
+
test_params_reprs[new_name] = getattr(func, '__x_params_repr__', '')
|
| 642 |
+
|
| 643 |
+
|
| 644 |
+
class TestCase(absltest.TestCase, metaclass=TestGeneratorMetaclass):
|
| 645 |
+
"""Base class for test cases using the parameters decorator."""
|
| 646 |
+
|
| 647 |
+
# visibility: private; do not call outside this class.
|
| 648 |
+
def _get_params_repr(self):
|
| 649 |
+
return self._test_params_reprs.get(self._testMethodName, '')
|
| 650 |
+
|
| 651 |
+
def __str__(self):
|
| 652 |
+
params_repr = self._get_params_repr()
|
| 653 |
+
if params_repr:
|
| 654 |
+
params_repr = ' ' + params_repr
|
| 655 |
+
return '{}{} ({})'.format(
|
| 656 |
+
self._testMethodName, params_repr,
|
| 657 |
+
unittest.util.strclass(self.__class__))
|
| 658 |
+
|
| 659 |
+
def id(self):
|
| 660 |
+
"""Returns the descriptive ID of the test.
|
| 661 |
+
|
| 662 |
+
This is used internally by the unittesting framework to get a name
|
| 663 |
+
for the test to be used in reports.
|
| 664 |
+
|
| 665 |
+
Returns:
|
| 666 |
+
The test id.
|
| 667 |
+
"""
|
| 668 |
+
base = super().id()
|
| 669 |
+
params_repr = self._get_params_repr()
|
| 670 |
+
if params_repr:
|
| 671 |
+
# We include the params in the id so that, when reported in the
|
| 672 |
+
# test.xml file, the value is more informative than just "test_foo0".
|
| 673 |
+
# Use a space to separate them so that it's copy/paste friendly and
|
| 674 |
+
# easy to identify the actual test id.
|
| 675 |
+
return f'{base} {params_repr}'
|
| 676 |
+
else:
|
| 677 |
+
return base
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
# This function is kept CamelCase because it's used as a class's base class.
|
| 681 |
+
def CoopTestCase(other_base_class) -> type: # pylint: disable=invalid-name, g-bare-generic
|
| 682 |
+
"""Returns a new base class with a cooperative metaclass base.
|
| 683 |
+
|
| 684 |
+
This enables the TestCase to be used in combination
|
| 685 |
+
with other base classes that have custom metaclasses, such as
|
| 686 |
+
``mox.MoxTestBase``.
|
| 687 |
+
|
| 688 |
+
Only works with metaclasses that do not override ``type.__new__``.
|
| 689 |
+
|
| 690 |
+
Example::
|
| 691 |
+
|
| 692 |
+
from absl.testing import parameterized
|
| 693 |
+
|
| 694 |
+
class ExampleTest(parameterized.CoopTestCase(OtherTestCase)):
|
| 695 |
+
...
|
| 696 |
+
|
| 697 |
+
Args:
|
| 698 |
+
other_base_class: (class) A test case base class.
|
| 699 |
+
|
| 700 |
+
Returns:
|
| 701 |
+
A new class object.
|
| 702 |
+
"""
|
| 703 |
+
# If the other base class has a metaclass of 'type' then trying to combine
|
| 704 |
+
# the metaclasses will result in an MRO error. So simply combine them and
|
| 705 |
+
# return.
|
| 706 |
+
if type(other_base_class) == type: # pylint: disable=unidiomatic-typecheck
|
| 707 |
+
warnings.warn(
|
| 708 |
+
'CoopTestCase is only necessary when combining with a class that uses'
|
| 709 |
+
' a metaclass. Use multiple inheritance like this instead: class'
|
| 710 |
+
f' ExampleTest(paramaterized.TestCase, {other_base_class.__name__}):',
|
| 711 |
+
stacklevel=2,
|
| 712 |
+
)
|
| 713 |
+
|
| 714 |
+
class CoopTestCaseBase(other_base_class, TestCase):
|
| 715 |
+
pass
|
| 716 |
+
|
| 717 |
+
return CoopTestCaseBase
|
| 718 |
+
else:
|
| 719 |
+
|
| 720 |
+
class CoopMetaclass(type(other_base_class), TestGeneratorMetaclass): # type: ignore # pylint: disable=unused-variable
|
| 721 |
+
pass
|
| 722 |
+
|
| 723 |
+
class CoopTestCaseBase(other_base_class, TestCase, metaclass=CoopMetaclass): # type: ignore
|
| 724 |
+
pass
|
| 725 |
+
|
| 726 |
+
return CoopTestCaseBase
|
.venv/lib/python3.13/site-packages/absl/testing/xml_reporter.py
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2017 The Abseil Authors.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""A Python test reporter that generates test reports in JUnit XML format."""
|
| 16 |
+
|
| 17 |
+
import datetime
|
| 18 |
+
import re
|
| 19 |
+
import sys
|
| 20 |
+
import threading
|
| 21 |
+
import time
|
| 22 |
+
import traceback
|
| 23 |
+
from typing import Any
|
| 24 |
+
import unittest
|
| 25 |
+
from xml.sax import saxutils
|
| 26 |
+
from absl.testing import _pretty_print_reporter
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# See http://www.w3.org/TR/REC-xml/#NT-Char
|
| 30 |
+
_bad_control_character_codes = set(range(0, 0x20)) - {0x9, 0xA, 0xD}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
_control_character_conversions = {
|
| 34 |
+
chr(i): f'\\x{i:02x}' for i in _bad_control_character_codes
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
_escape_xml_attr_conversions = {
|
| 39 |
+
'"': '"',
|
| 40 |
+
"'": ''',
|
| 41 |
+
'\n': '
',
|
| 42 |
+
'\t': '	',
|
| 43 |
+
'\r': '
',
|
| 44 |
+
' ': ' '}
|
| 45 |
+
_escape_xml_attr_conversions.update(_control_character_conversions)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# When class or module level function fails, unittest/suite.py adds a
|
| 49 |
+
# _ErrorHolder instance instead of a real TestCase, and it has a description
|
| 50 |
+
# like "setUpClass (__main__.MyTestCase)".
|
| 51 |
+
_CLASS_OR_MODULE_LEVEL_TEST_DESC_REGEX = re.compile(r'^(\w+) \((\S+)\)$')
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# NOTE: while saxutils.quoteattr() theoretically does the same thing; it
|
| 55 |
+
# seems to often end up being too smart for it's own good not escaping properly.
|
| 56 |
+
# This function is much more reliable.
|
| 57 |
+
def _escape_xml_attr(content):
|
| 58 |
+
"""Escapes xml attributes."""
|
| 59 |
+
# Note: saxutils doesn't escape the quotes.
|
| 60 |
+
return saxutils.escape(content, _escape_xml_attr_conversions)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _escape_cdata(s):
|
| 64 |
+
"""Escapes a string to be used as XML CDATA.
|
| 65 |
+
|
| 66 |
+
CDATA characters are treated strictly as character data, not as XML markup,
|
| 67 |
+
but there are still certain restrictions on them.
|
| 68 |
+
|
| 69 |
+
Args:
|
| 70 |
+
s: the string to be escaped.
|
| 71 |
+
Returns:
|
| 72 |
+
An escaped version of the input string.
|
| 73 |
+
"""
|
| 74 |
+
for char, escaped in _control_character_conversions.items():
|
| 75 |
+
s = s.replace(char, escaped)
|
| 76 |
+
return s.replace(']]>', ']] >')
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _iso8601_timestamp(timestamp):
|
| 80 |
+
"""Produces an ISO8601 datetime.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
timestamp: an Epoch based timestamp in seconds.
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
A iso8601 format timestamp if the input is a valid timestamp, None otherwise
|
| 87 |
+
"""
|
| 88 |
+
if timestamp is None or timestamp < 0:
|
| 89 |
+
return None
|
| 90 |
+
return datetime.datetime.fromtimestamp(
|
| 91 |
+
timestamp, tz=datetime.timezone.utc).isoformat()
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _print_xml_element_header(element, attributes, stream, indentation=''):
|
| 95 |
+
"""Prints an XML header of an arbitrary element.
|
| 96 |
+
|
| 97 |
+
Args:
|
| 98 |
+
element: element name (testsuites, testsuite, testcase)
|
| 99 |
+
attributes: 2-tuple list with (attributes, values) already escaped
|
| 100 |
+
stream: output stream to write test report XML to
|
| 101 |
+
indentation: indentation added to the element header
|
| 102 |
+
"""
|
| 103 |
+
stream.write('%s<%s' % (indentation, element))
|
| 104 |
+
for attribute in attributes:
|
| 105 |
+
if (len(attribute) == 2 and attribute[0] is not None and
|
| 106 |
+
attribute[1] is not None):
|
| 107 |
+
stream.write(' %s="%s"' % (attribute[0], attribute[1]))
|
| 108 |
+
stream.write('>\n')
|
| 109 |
+
|
| 110 |
+
# Copy time.time which ensures the real time is used internally.
|
| 111 |
+
# This prevents bad interactions with tests that stub out time.
|
| 112 |
+
_time_copy = time.time
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def _safe_str(obj: object) -> str:
|
| 116 |
+
"""Returns a string representation of an object."""
|
| 117 |
+
try:
|
| 118 |
+
return str(obj)
|
| 119 |
+
except Exception: # pylint: disable=broad-except
|
| 120 |
+
return '<unprintable %s.%s object>' % (
|
| 121 |
+
type(obj).__module__,
|
| 122 |
+
type(obj).__name__,
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class _TestCaseResult:
|
| 127 |
+
"""Private helper for _TextAndXMLTestResult that represents a test result.
|
| 128 |
+
|
| 129 |
+
Attributes:
|
| 130 |
+
test: A TestCase instance of an individual test method.
|
| 131 |
+
name: The name of the individual test method.
|
| 132 |
+
full_class_name: The full name of the test class.
|
| 133 |
+
run_time: The duration (in seconds) it took to run the test.
|
| 134 |
+
start_time: Epoch relative timestamp of when test started (in seconds)
|
| 135 |
+
errors: A list of error 4-tuples. Error tuple entries are
|
| 136 |
+
1) a string identifier of either "failure" or "error"
|
| 137 |
+
2) an exception_type
|
| 138 |
+
3) an exception_message
|
| 139 |
+
4) a string version of a sys.exc_info()-style tuple of values
|
| 140 |
+
('error', err[0], err[1], self._exc_info_to_string(err))
|
| 141 |
+
If the length of errors is 0, then the test is either passed or
|
| 142 |
+
skipped.
|
| 143 |
+
skip_reason: A string explaining why the test was skipped.
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
def __init__(self, test):
|
| 147 |
+
self.run_time = -1
|
| 148 |
+
self.start_time = -1
|
| 149 |
+
self.skip_reason = None
|
| 150 |
+
self.errors = []
|
| 151 |
+
self.test = test
|
| 152 |
+
|
| 153 |
+
# Parse the test id to get its test name and full class path.
|
| 154 |
+
# Unfortunately there is no better way of knowning the test and class.
|
| 155 |
+
# Worse, unittest uses _ErrorHandler instances to represent class / module
|
| 156 |
+
# level failures.
|
| 157 |
+
test_desc = test.id() or str(test)
|
| 158 |
+
# Check if it's something like "setUpClass (__main__.TestCase)".
|
| 159 |
+
match = _CLASS_OR_MODULE_LEVEL_TEST_DESC_REGEX.match(test_desc)
|
| 160 |
+
if match:
|
| 161 |
+
name = match.group(1)
|
| 162 |
+
full_class_name = match.group(2)
|
| 163 |
+
else:
|
| 164 |
+
class_name = unittest.util.strclass(test.__class__)
|
| 165 |
+
if isinstance(test, unittest.case._SubTest):
|
| 166 |
+
# If the test case is a _SubTest, the real TestCase instance is
|
| 167 |
+
# available as _SubTest.test_case.
|
| 168 |
+
class_name = unittest.util.strclass(test.test_case.__class__)
|
| 169 |
+
if test_desc.startswith(class_name + '.'):
|
| 170 |
+
# In a typical unittest.TestCase scenario, test.id() returns with
|
| 171 |
+
# a class name formatted using unittest.util.strclass.
|
| 172 |
+
name = test_desc[len(class_name)+1:]
|
| 173 |
+
full_class_name = class_name
|
| 174 |
+
else:
|
| 175 |
+
# Otherwise make a best effort to guess the test name and full class
|
| 176 |
+
# path.
|
| 177 |
+
parts = test_desc.rsplit('.', 1)
|
| 178 |
+
name = parts[-1]
|
| 179 |
+
full_class_name = parts[0] if len(parts) == 2 else ''
|
| 180 |
+
self.name = _escape_xml_attr(name)
|
| 181 |
+
self.full_class_name = _escape_xml_attr(full_class_name)
|
| 182 |
+
|
| 183 |
+
def set_run_time(self, time_in_secs):
|
| 184 |
+
self.run_time = time_in_secs
|
| 185 |
+
|
| 186 |
+
def set_start_time(self, time_in_secs):
|
| 187 |
+
self.start_time = time_in_secs
|
| 188 |
+
|
| 189 |
+
def print_xml_summary(self, stream):
|
| 190 |
+
"""Prints an XML Summary of a TestCase.
|
| 191 |
+
|
| 192 |
+
Status and result are populated as per JUnit XML test result reporter.
|
| 193 |
+
A test that has been skipped will always have a skip reason,
|
| 194 |
+
as every skip method in Python's unittest requires the reason arg to be
|
| 195 |
+
passed.
|
| 196 |
+
|
| 197 |
+
Args:
|
| 198 |
+
stream: output stream to write test report XML to
|
| 199 |
+
"""
|
| 200 |
+
|
| 201 |
+
if self.skip_reason is None:
|
| 202 |
+
status = 'run'
|
| 203 |
+
result = 'completed'
|
| 204 |
+
else:
|
| 205 |
+
status = 'notrun'
|
| 206 |
+
result = 'suppressed'
|
| 207 |
+
|
| 208 |
+
test_case_attributes = [
|
| 209 |
+
('name', '%s' % self.name),
|
| 210 |
+
('status', '%s' % status),
|
| 211 |
+
('result', '%s' % result),
|
| 212 |
+
('time', '%.3f' % self.run_time),
|
| 213 |
+
('classname', self.full_class_name),
|
| 214 |
+
('timestamp', _iso8601_timestamp(self.start_time)),
|
| 215 |
+
]
|
| 216 |
+
_print_xml_element_header('testcase', test_case_attributes, stream, ' ')
|
| 217 |
+
self._print_testcase_details(stream)
|
| 218 |
+
stream.write(' </testcase>\n')
|
| 219 |
+
|
| 220 |
+
def _print_testcase_details(self, stream):
|
| 221 |
+
for error in self.errors:
|
| 222 |
+
outcome, exception_type, message, error_msg = error # pylint: disable=unpacking-non-sequence
|
| 223 |
+
message = _escape_xml_attr(_safe_str(message))
|
| 224 |
+
exception_type = _escape_xml_attr(str(exception_type))
|
| 225 |
+
error_msg = _escape_cdata(error_msg)
|
| 226 |
+
stream.write(' <%s message="%s" type="%s"><![CDATA[%s]]></%s>\n'
|
| 227 |
+
% (outcome, message, exception_type, error_msg, outcome))
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
class _TestSuiteResult:
|
| 231 |
+
"""Private helper for _TextAndXMLTestResult."""
|
| 232 |
+
|
| 233 |
+
def __init__(self):
|
| 234 |
+
self.suites = {}
|
| 235 |
+
self.failure_counts = {}
|
| 236 |
+
self.error_counts = {}
|
| 237 |
+
self.overall_start_time = -1
|
| 238 |
+
self.overall_end_time = -1
|
| 239 |
+
self._testsuites_properties = {}
|
| 240 |
+
|
| 241 |
+
def add_test_case_result(self, test_case_result):
|
| 242 |
+
suite_name = type(test_case_result.test).__name__
|
| 243 |
+
if suite_name == '_ErrorHolder':
|
| 244 |
+
# _ErrorHolder is a special case created by unittest for class / module
|
| 245 |
+
# level functions.
|
| 246 |
+
suite_name = test_case_result.full_class_name.rsplit('.')[-1]
|
| 247 |
+
if isinstance(test_case_result.test, unittest.case._SubTest):
|
| 248 |
+
# If the test case is a _SubTest, the real TestCase instance is
|
| 249 |
+
# available as _SubTest.test_case.
|
| 250 |
+
suite_name = type(test_case_result.test.test_case).__name__
|
| 251 |
+
|
| 252 |
+
self._setup_test_suite(suite_name)
|
| 253 |
+
self.suites[suite_name].append(test_case_result)
|
| 254 |
+
for error in test_case_result.errors:
|
| 255 |
+
# Only count the first failure or error so that the sum is equal to the
|
| 256 |
+
# total number of *testcases* that have failures or errors.
|
| 257 |
+
if error[0] == 'failure':
|
| 258 |
+
self.failure_counts[suite_name] += 1
|
| 259 |
+
break
|
| 260 |
+
elif error[0] == 'error':
|
| 261 |
+
self.error_counts[suite_name] += 1
|
| 262 |
+
break
|
| 263 |
+
|
| 264 |
+
def print_xml_summary(self, stream):
|
| 265 |
+
overall_test_count = sum(len(x) for x in self.suites.values())
|
| 266 |
+
overall_failures = sum(self.failure_counts.values())
|
| 267 |
+
overall_errors = sum(self.error_counts.values())
|
| 268 |
+
overall_attributes = [
|
| 269 |
+
('name', ''),
|
| 270 |
+
('tests', '%d' % overall_test_count),
|
| 271 |
+
('failures', '%d' % overall_failures),
|
| 272 |
+
('errors', '%d' % overall_errors),
|
| 273 |
+
('time', '%.3f' % (self.overall_end_time - self.overall_start_time)),
|
| 274 |
+
('timestamp', _iso8601_timestamp(self.overall_start_time)),
|
| 275 |
+
]
|
| 276 |
+
_print_xml_element_header('testsuites', overall_attributes, stream)
|
| 277 |
+
if self._testsuites_properties:
|
| 278 |
+
stream.write(' <properties>\n')
|
| 279 |
+
for name, value in sorted(self._testsuites_properties.items()):
|
| 280 |
+
stream.write(' <property name="%s" value="%s"></property>\n' %
|
| 281 |
+
(_escape_xml_attr(name), _escape_xml_attr(str(value))))
|
| 282 |
+
stream.write(' </properties>\n')
|
| 283 |
+
|
| 284 |
+
for suite_name in self.suites:
|
| 285 |
+
suite = self.suites[suite_name]
|
| 286 |
+
suite_end_time = max(x.start_time + x.run_time for x in suite)
|
| 287 |
+
suite_start_time = min(x.start_time for x in suite)
|
| 288 |
+
failures = self.failure_counts[suite_name]
|
| 289 |
+
errors = self.error_counts[suite_name]
|
| 290 |
+
suite_attributes = [
|
| 291 |
+
('name', '%s' % suite_name),
|
| 292 |
+
('tests', '%d' % len(suite)),
|
| 293 |
+
('failures', '%d' % failures),
|
| 294 |
+
('errors', '%d' % errors),
|
| 295 |
+
('time', '%.3f' % (suite_end_time - suite_start_time)),
|
| 296 |
+
('timestamp', _iso8601_timestamp(suite_start_time)),
|
| 297 |
+
]
|
| 298 |
+
_print_xml_element_header('testsuite', suite_attributes, stream)
|
| 299 |
+
|
| 300 |
+
# test_case_result entries are not guaranteed to be in any user-friendly
|
| 301 |
+
# order, especially when using subtests. So sort them.
|
| 302 |
+
for test_case_result in sorted(suite, key=lambda t: t.name):
|
| 303 |
+
test_case_result.print_xml_summary(stream)
|
| 304 |
+
stream.write('</testsuite>\n')
|
| 305 |
+
stream.write('</testsuites>\n')
|
| 306 |
+
|
| 307 |
+
def _setup_test_suite(self, suite_name):
|
| 308 |
+
"""Adds a test suite to the set of suites tracked by this test run.
|
| 309 |
+
|
| 310 |
+
Args:
|
| 311 |
+
suite_name: string, The name of the test suite being initialized.
|
| 312 |
+
"""
|
| 313 |
+
if suite_name in self.suites:
|
| 314 |
+
return
|
| 315 |
+
self.suites[suite_name] = []
|
| 316 |
+
self.failure_counts[suite_name] = 0
|
| 317 |
+
self.error_counts[suite_name] = 0
|
| 318 |
+
|
| 319 |
+
def set_end_time(self, timestamp_in_secs):
|
| 320 |
+
"""Sets the start timestamp of this test suite.
|
| 321 |
+
|
| 322 |
+
Args:
|
| 323 |
+
timestamp_in_secs: timestamp in seconds since epoch
|
| 324 |
+
"""
|
| 325 |
+
self.overall_end_time = timestamp_in_secs
|
| 326 |
+
|
| 327 |
+
def set_start_time(self, timestamp_in_secs):
|
| 328 |
+
"""Sets the end timestamp of this test suite.
|
| 329 |
+
|
| 330 |
+
Args:
|
| 331 |
+
timestamp_in_secs: timestamp in seconds since epoch
|
| 332 |
+
"""
|
| 333 |
+
self.overall_start_time = timestamp_in_secs
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
class _TextAndXMLTestResult(_pretty_print_reporter.TextTestResult):
|
| 337 |
+
"""Private TestResult class that produces both formatted text results and XML.
|
| 338 |
+
|
| 339 |
+
Used by TextAndXMLTestRunner.
|
| 340 |
+
"""
|
| 341 |
+
|
| 342 |
+
_TEST_SUITE_RESULT_CLASS = _TestSuiteResult
|
| 343 |
+
_TEST_CASE_RESULT_CLASS = _TestCaseResult
|
| 344 |
+
|
| 345 |
+
def __init__(self, xml_stream, stream, descriptions, verbosity,
|
| 346 |
+
time_getter=_time_copy, testsuites_properties=None):
|
| 347 |
+
super().__init__(stream, descriptions, verbosity)
|
| 348 |
+
self.xml_stream = xml_stream
|
| 349 |
+
self.pending_test_case_results = {}
|
| 350 |
+
self.suite = self._TEST_SUITE_RESULT_CLASS()
|
| 351 |
+
if testsuites_properties:
|
| 352 |
+
self.suite._testsuites_properties = testsuites_properties
|
| 353 |
+
self.time_getter = time_getter
|
| 354 |
+
|
| 355 |
+
# This lock guards any mutations on pending_test_case_results.
|
| 356 |
+
self._pending_test_case_results_lock = threading.RLock()
|
| 357 |
+
|
| 358 |
+
def startTest(self, test):
|
| 359 |
+
self.start_time = self.time_getter()
|
| 360 |
+
super().startTest(test)
|
| 361 |
+
|
| 362 |
+
def stopTest(self, test):
|
| 363 |
+
# Grabbing the write lock to avoid conflicting with stopTestRun.
|
| 364 |
+
with self._pending_test_case_results_lock:
|
| 365 |
+
super().stopTest(test)
|
| 366 |
+
result = self.get_pending_test_case_result(test)
|
| 367 |
+
if not result:
|
| 368 |
+
test_name = test.id() or str(test)
|
| 369 |
+
sys.stderr.write('No pending test case: %s\n' % test_name)
|
| 370 |
+
return
|
| 371 |
+
if getattr(self, 'start_time', None) is None:
|
| 372 |
+
# startTest may not be called for skipped tests since Python 3.12.1.
|
| 373 |
+
self.start_time = self.time_getter()
|
| 374 |
+
test_id = id(test)
|
| 375 |
+
run_time = self.time_getter() - self.start_time
|
| 376 |
+
result.set_run_time(run_time)
|
| 377 |
+
result.set_start_time(self.start_time)
|
| 378 |
+
self.suite.add_test_case_result(result)
|
| 379 |
+
del self.pending_test_case_results[test_id]
|
| 380 |
+
|
| 381 |
+
def startTestRun(self):
|
| 382 |
+
self.suite.set_start_time(self.time_getter())
|
| 383 |
+
super().startTestRun()
|
| 384 |
+
|
| 385 |
+
def stopTestRun(self):
|
| 386 |
+
self.suite.set_end_time(self.time_getter())
|
| 387 |
+
# All pending_test_case_results will be added to the suite and removed from
|
| 388 |
+
# the pending_test_case_results dictionary. Grabbing the write lock to avoid
|
| 389 |
+
# results from being added during this process to avoid duplicating adds or
|
| 390 |
+
# accidentally erasing newly appended pending results.
|
| 391 |
+
with self._pending_test_case_results_lock:
|
| 392 |
+
# Errors in the test fixture (setUpModule, tearDownModule,
|
| 393 |
+
# setUpClass, tearDownClass) can leave a pending result which
|
| 394 |
+
# never gets added to the suite. The runner calls stopTestRun
|
| 395 |
+
# which gives us an opportunity to add these errors for
|
| 396 |
+
# reporting here.
|
| 397 |
+
for test_id in self.pending_test_case_results:
|
| 398 |
+
result = self.pending_test_case_results[test_id]
|
| 399 |
+
if getattr(self, 'start_time', None) is not None:
|
| 400 |
+
run_time = self.suite.overall_end_time - self.start_time
|
| 401 |
+
result.set_run_time(run_time)
|
| 402 |
+
result.set_start_time(self.start_time)
|
| 403 |
+
self.suite.add_test_case_result(result)
|
| 404 |
+
self.pending_test_case_results.clear()
|
| 405 |
+
|
| 406 |
+
def _exc_info_to_string(self, err, test=None):
|
| 407 |
+
"""Converts a sys.exc_info()-style tuple of values into a string.
|
| 408 |
+
|
| 409 |
+
This method must be overridden because the method signature in
|
| 410 |
+
unittest.TestResult changed between Python 2.2 and 2.4.
|
| 411 |
+
|
| 412 |
+
Args:
|
| 413 |
+
err: A sys.exc_info() tuple of values for an error.
|
| 414 |
+
test: The test method.
|
| 415 |
+
|
| 416 |
+
Returns:
|
| 417 |
+
A formatted exception string.
|
| 418 |
+
"""
|
| 419 |
+
if test:
|
| 420 |
+
return super()._exc_info_to_string(err, test)
|
| 421 |
+
return ''.join(traceback.format_exception(*err))
|
| 422 |
+
|
| 423 |
+
def add_pending_test_case_result(self, test, error_summary=None,
|
| 424 |
+
skip_reason=None):
|
| 425 |
+
"""Adds result information to a test case result which may still be running.
|
| 426 |
+
|
| 427 |
+
If a result entry for the test already exists, add_pending_test_case_result
|
| 428 |
+
will add error summary tuples and/or overwrite skip_reason for the result.
|
| 429 |
+
If it does not yet exist, a result entry will be created.
|
| 430 |
+
Note that a test result is considered to have been run and passed
|
| 431 |
+
only if there are no errors or skip_reason.
|
| 432 |
+
|
| 433 |
+
Args:
|
| 434 |
+
test: A test method as defined by unittest
|
| 435 |
+
error_summary: A 4-tuple with the following entries:
|
| 436 |
+
1) a string identifier of either "failure" or "error"
|
| 437 |
+
2) an exception_type
|
| 438 |
+
3) an exception_message
|
| 439 |
+
4) a string version of a sys.exc_info()-style tuple of values
|
| 440 |
+
('error', err[0], err[1], self._exc_info_to_string(err))
|
| 441 |
+
If the length of errors is 0, then the test is either passed or
|
| 442 |
+
skipped.
|
| 443 |
+
skip_reason: a string explaining why the test was skipped
|
| 444 |
+
"""
|
| 445 |
+
with self._pending_test_case_results_lock:
|
| 446 |
+
test_id = id(test)
|
| 447 |
+
if test_id not in self.pending_test_case_results:
|
| 448 |
+
self.pending_test_case_results[test_id] = self._TEST_CASE_RESULT_CLASS(
|
| 449 |
+
test)
|
| 450 |
+
if error_summary:
|
| 451 |
+
self.pending_test_case_results[test_id].errors.append(error_summary)
|
| 452 |
+
if skip_reason:
|
| 453 |
+
self.pending_test_case_results[test_id].skip_reason = skip_reason
|
| 454 |
+
|
| 455 |
+
def delete_pending_test_case_result(self, test):
|
| 456 |
+
with self._pending_test_case_results_lock:
|
| 457 |
+
test_id = id(test)
|
| 458 |
+
del self.pending_test_case_results[test_id]
|
| 459 |
+
|
| 460 |
+
def get_pending_test_case_result(self, test):
|
| 461 |
+
test_id = id(test)
|
| 462 |
+
return self.pending_test_case_results.get(test_id, None)
|
| 463 |
+
|
| 464 |
+
def addSuccess(self, test):
|
| 465 |
+
super().addSuccess(test)
|
| 466 |
+
self.add_pending_test_case_result(test)
|
| 467 |
+
|
| 468 |
+
def addError(self, test, err):
|
| 469 |
+
super().addError(test, err)
|
| 470 |
+
error_summary = ('error', err[0], err[1],
|
| 471 |
+
self._exc_info_to_string(err, test=test))
|
| 472 |
+
self.add_pending_test_case_result(test, error_summary=error_summary)
|
| 473 |
+
|
| 474 |
+
def addFailure(self, test, err):
|
| 475 |
+
super().addFailure(test, err)
|
| 476 |
+
error_summary = ('failure', err[0], err[1],
|
| 477 |
+
self._exc_info_to_string(err, test=test))
|
| 478 |
+
self.add_pending_test_case_result(test, error_summary=error_summary)
|
| 479 |
+
|
| 480 |
+
def addSkip(self, test, reason):
|
| 481 |
+
super().addSkip(test, reason)
|
| 482 |
+
self.add_pending_test_case_result(test, skip_reason=reason)
|
| 483 |
+
|
| 484 |
+
def addExpectedFailure(self, test, err):
|
| 485 |
+
super().addExpectedFailure(test, err)
|
| 486 |
+
if callable(getattr(test, 'recordProperty', None)):
|
| 487 |
+
test.recordProperty('EXPECTED_FAILURE',
|
| 488 |
+
self._exc_info_to_string(err, test=test))
|
| 489 |
+
self.add_pending_test_case_result(test)
|
| 490 |
+
|
| 491 |
+
def addUnexpectedSuccess(self, test):
|
| 492 |
+
super().addUnexpectedSuccess(test)
|
| 493 |
+
test_name = test.id() or str(test)
|
| 494 |
+
error_summary = ('error', '', '',
|
| 495 |
+
'Test case %s should have failed, but passed.'
|
| 496 |
+
% (test_name))
|
| 497 |
+
self.add_pending_test_case_result(test, error_summary=error_summary)
|
| 498 |
+
|
| 499 |
+
def addSubTest(self, test, subtest, err): # pylint: disable=invalid-name
|
| 500 |
+
super().addSubTest(test, subtest, err)
|
| 501 |
+
if err is not None:
|
| 502 |
+
if issubclass(err[0], test.failureException):
|
| 503 |
+
error_summary = ('failure', err[0], err[1],
|
| 504 |
+
self._exc_info_to_string(err, test=test))
|
| 505 |
+
else:
|
| 506 |
+
error_summary = ('error', err[0], err[1],
|
| 507 |
+
self._exc_info_to_string(err, test=test))
|
| 508 |
+
else:
|
| 509 |
+
error_summary = None
|
| 510 |
+
self.add_pending_test_case_result(subtest, error_summary=error_summary)
|
| 511 |
+
|
| 512 |
+
def printErrors(self):
|
| 513 |
+
super().printErrors()
|
| 514 |
+
self.xml_stream.write('<?xml version="1.0"?>\n')
|
| 515 |
+
self.suite.print_xml_summary(self.xml_stream)
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
class TextAndXMLTestRunner(unittest.TextTestRunner):
|
| 519 |
+
"""A test runner that produces both formatted text results and XML.
|
| 520 |
+
|
| 521 |
+
It prints out the names of tests as they are run, errors as they
|
| 522 |
+
occur, and a summary of the results at the end of the test run.
|
| 523 |
+
"""
|
| 524 |
+
|
| 525 |
+
_TEST_RESULT_CLASS = _TextAndXMLTestResult
|
| 526 |
+
|
| 527 |
+
_xml_stream = None
|
| 528 |
+
_testsuites_properties: dict[Any, Any] = {}
|
| 529 |
+
|
| 530 |
+
def __init__(self, xml_stream=None, *args, **kwargs):
|
| 531 |
+
"""Initialize a TextAndXMLTestRunner.
|
| 532 |
+
|
| 533 |
+
Args:
|
| 534 |
+
xml_stream: file-like or None; XML-formatted test results are output
|
| 535 |
+
via this object's write() method. If None (the default), the
|
| 536 |
+
new instance behaves as described in the set_default_xml_stream method
|
| 537 |
+
documentation below.
|
| 538 |
+
*args: passed unmodified to unittest.TextTestRunner.__init__.
|
| 539 |
+
**kwargs: passed unmodified to unittest.TextTestRunner.__init__.
|
| 540 |
+
"""
|
| 541 |
+
super().__init__(*args, **kwargs)
|
| 542 |
+
if xml_stream is not None:
|
| 543 |
+
self._xml_stream = xml_stream
|
| 544 |
+
# else, do not set self._xml_stream to None -- this allows implicit fallback
|
| 545 |
+
# to the class attribute's value.
|
| 546 |
+
|
| 547 |
+
@classmethod
|
| 548 |
+
def set_default_xml_stream(cls, xml_stream):
|
| 549 |
+
"""Sets the default XML stream for the class.
|
| 550 |
+
|
| 551 |
+
Args:
|
| 552 |
+
xml_stream: file-like or None; used for instances when xml_stream is None
|
| 553 |
+
or not passed to their constructors. If None is passed, instances
|
| 554 |
+
created with xml_stream=None will act as ordinary TextTestRunner
|
| 555 |
+
instances; this is the default state before any calls to this method
|
| 556 |
+
have been made.
|
| 557 |
+
"""
|
| 558 |
+
cls._xml_stream = xml_stream
|
| 559 |
+
|
| 560 |
+
def _makeResult(self):
|
| 561 |
+
if self._xml_stream is None:
|
| 562 |
+
return super()._makeResult()
|
| 563 |
+
else:
|
| 564 |
+
return self._TEST_RESULT_CLASS(
|
| 565 |
+
self._xml_stream, self.stream, self.descriptions, self.verbosity,
|
| 566 |
+
testsuites_properties=self._testsuites_properties)
|
| 567 |
+
|
| 568 |
+
@classmethod
|
| 569 |
+
def set_testsuites_property(cls, key, value):
|
| 570 |
+
cls._testsuites_properties[key] = value
|
.venv/lib/python3.13/site-packages/attrs/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (1.21 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/attrs/__pycache__/converters.cpython-313.pyc
ADDED
|
Binary file (221 Bytes). View file
|
|
|
.venv/lib/python3.13/site-packages/attrs/__pycache__/exceptions.cpython-313.pyc
ADDED
|
Binary file (221 Bytes). View file
|
|
|
.venv/lib/python3.13/site-packages/attrs/__pycache__/filters.cpython-313.pyc
ADDED
|
Binary file (215 Bytes). View file
|
|
|
.venv/lib/python3.13/site-packages/attrs/__pycache__/setters.cpython-313.pyc
ADDED
|
Binary file (215 Bytes). View file
|
|
|
.venv/lib/python3.13/site-packages/attrs/__pycache__/validators.cpython-313.pyc
ADDED
|
Binary file (221 Bytes). View file
|
|
|
.venv/lib/python3.13/site-packages/black/resources/__init__.cpython-313-x86_64-linux-gnu.so
ADDED
|
Binary file (16 kB). View file
|
|
|
.venv/lib/python3.13/site-packages/black/resources/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/black/resources/black.schema.json
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
| 3 |
+
"$id": "https://github.com/psf/black/blob/main/src/black/resources/black.schema.json",
|
| 4 |
+
"$comment": "tool.black table in pyproject.toml",
|
| 5 |
+
"type": "object",
|
| 6 |
+
"additionalProperties": false,
|
| 7 |
+
"properties": {
|
| 8 |
+
"code": {
|
| 9 |
+
"type": "string",
|
| 10 |
+
"description": "Format the code passed in as a string."
|
| 11 |
+
},
|
| 12 |
+
"line-length": {
|
| 13 |
+
"type": "integer",
|
| 14 |
+
"description": "How many characters per line to allow.",
|
| 15 |
+
"default": 88
|
| 16 |
+
},
|
| 17 |
+
"target-version": {
|
| 18 |
+
"type": "array",
|
| 19 |
+
"items": {
|
| 20 |
+
"enum": [
|
| 21 |
+
"py33",
|
| 22 |
+
"py34",
|
| 23 |
+
"py35",
|
| 24 |
+
"py36",
|
| 25 |
+
"py37",
|
| 26 |
+
"py38",
|
| 27 |
+
"py39",
|
| 28 |
+
"py310",
|
| 29 |
+
"py311",
|
| 30 |
+
"py312",
|
| 31 |
+
"py313",
|
| 32 |
+
"py314"
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
"description": "Python versions that should be supported by Black's output. You should include all versions that your code supports. By default, Black will infer target versions from the project metadata in pyproject.toml. If this does not yield conclusive results, Black will use per-file auto-detection."
|
| 36 |
+
},
|
| 37 |
+
"pyi": {
|
| 38 |
+
"type": "boolean",
|
| 39 |
+
"description": "Format all input files like typing stubs regardless of file extension. This is useful when piping source on standard input.",
|
| 40 |
+
"default": false
|
| 41 |
+
},
|
| 42 |
+
"ipynb": {
|
| 43 |
+
"type": "boolean",
|
| 44 |
+
"description": "Format all input files like Jupyter Notebooks regardless of file extension. This is useful when piping source on standard input.",
|
| 45 |
+
"default": false
|
| 46 |
+
},
|
| 47 |
+
"python-cell-magics": {
|
| 48 |
+
"type": "array",
|
| 49 |
+
"items": {
|
| 50 |
+
"type": "string"
|
| 51 |
+
},
|
| 52 |
+
"description": "When processing Jupyter Notebooks, add the given magic to the list of known python-magics (capture, prun, pypy, python, python3, time, timeit). Useful for formatting cells with custom python magics."
|
| 53 |
+
},
|
| 54 |
+
"skip-source-first-line": {
|
| 55 |
+
"type": "boolean",
|
| 56 |
+
"description": "Skip the first line of the source code.",
|
| 57 |
+
"default": false
|
| 58 |
+
},
|
| 59 |
+
"skip-string-normalization": {
|
| 60 |
+
"type": "boolean",
|
| 61 |
+
"description": "Don't normalize string quotes or prefixes.",
|
| 62 |
+
"default": false
|
| 63 |
+
},
|
| 64 |
+
"skip-magic-trailing-comma": {
|
| 65 |
+
"type": "boolean",
|
| 66 |
+
"description": "Don't use trailing commas as a reason to split lines.",
|
| 67 |
+
"default": false
|
| 68 |
+
},
|
| 69 |
+
"preview": {
|
| 70 |
+
"type": "boolean",
|
| 71 |
+
"description": "Enable potentially disruptive style changes that may be added to Black's main functionality in the next major release.",
|
| 72 |
+
"default": false
|
| 73 |
+
},
|
| 74 |
+
"unstable": {
|
| 75 |
+
"type": "boolean",
|
| 76 |
+
"description": "Enable potentially disruptive style changes that have known bugs or are not currently expected to make it into the stable style Black's next major release. Implies --preview.",
|
| 77 |
+
"default": false
|
| 78 |
+
},
|
| 79 |
+
"enable-unstable-feature": {
|
| 80 |
+
"type": "array",
|
| 81 |
+
"items": {
|
| 82 |
+
"enum": [
|
| 83 |
+
"string_processing",
|
| 84 |
+
"hug_parens_with_braces_and_square_brackets",
|
| 85 |
+
"wrap_long_dict_values_in_parens",
|
| 86 |
+
"multiline_string_handling",
|
| 87 |
+
"always_one_newline_after_import",
|
| 88 |
+
"fix_fmt_skip_in_one_liners",
|
| 89 |
+
"standardize_type_comments",
|
| 90 |
+
"wrap_comprehension_in",
|
| 91 |
+
"remove_parens_around_except_types",
|
| 92 |
+
"normalize_cr_newlines",
|
| 93 |
+
"fix_module_docstring_detection",
|
| 94 |
+
"fix_type_expansion_split",
|
| 95 |
+
"remove_parens_from_assignment_lhs"
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
"description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features."
|
| 99 |
+
},
|
| 100 |
+
"check": {
|
| 101 |
+
"type": "boolean",
|
| 102 |
+
"description": "Don't write the files back, just return the status. Return code 0 means nothing would change. Return code 1 means some files would be reformatted. Return code 123 means there was an internal error.",
|
| 103 |
+
"default": false
|
| 104 |
+
},
|
| 105 |
+
"diff": {
|
| 106 |
+
"type": "boolean",
|
| 107 |
+
"description": "Don't write the files back, just output a diff to indicate what changes Black would've made. They are printed to stdout so capturing them is simple.",
|
| 108 |
+
"default": false
|
| 109 |
+
},
|
| 110 |
+
"color": {
|
| 111 |
+
"type": "boolean",
|
| 112 |
+
"description": "Show (or do not show) colored diff. Only applies when --diff is given.",
|
| 113 |
+
"default": false
|
| 114 |
+
},
|
| 115 |
+
"fast": {
|
| 116 |
+
"type": "boolean",
|
| 117 |
+
"description": "By default, Black performs an AST safety check after formatting your code. The --fast flag turns off this check and the --safe flag explicitly enables it. [default: --safe]",
|
| 118 |
+
"default": false
|
| 119 |
+
},
|
| 120 |
+
"required-version": {
|
| 121 |
+
"type": "string",
|
| 122 |
+
"description": "Require a specific version of Black to be running. This is useful for ensuring that all contributors to your project are using the same version, because different versions of Black may format code a little differently. This option can be set in a configuration file for consistent results across environments."
|
| 123 |
+
},
|
| 124 |
+
"exclude": {
|
| 125 |
+
"type": "string",
|
| 126 |
+
"description": "A regular expression that matches files and directories that should be excluded on recursive searches. An empty value means no paths are excluded. Use forward slashes for directories on all platforms (Windows, too). By default, Black also ignores all paths listed in .gitignore. Changing this value will override all default exclusions. [default: /(\\.direnv|\\.eggs|\\.git|\\.hg|\\.ipynb_checkpoints|\\.mypy_cache|\\.nox|\\.pytest_cache|\\.ruff_cache|\\.tox|\\.svn|\\.venv|\\.vscode|__pypackages__|_build|buck-out|build|dist|venv)/]"
|
| 127 |
+
},
|
| 128 |
+
"extend-exclude": {
|
| 129 |
+
"type": "string",
|
| 130 |
+
"description": "Like --exclude, but adds additional files and directories on top of the default values instead of overriding them."
|
| 131 |
+
},
|
| 132 |
+
"force-exclude": {
|
| 133 |
+
"type": "string",
|
| 134 |
+
"description": "Like --exclude, but files and directories matching this regex will be excluded even when they are passed explicitly as arguments. This is useful when invoking Black programmatically on changed files, such as in a pre-commit hook or editor plugin."
|
| 135 |
+
},
|
| 136 |
+
"include": {
|
| 137 |
+
"type": "string",
|
| 138 |
+
"description": "A regular expression that matches files and directories that should be included on recursive searches. An empty value means all files are included regardless of the name. Use forward slashes for directories on all platforms (Windows, too). Overrides all exclusions, including from .gitignore and command line options.",
|
| 139 |
+
"default": "(\\.pyi?|\\.ipynb)$"
|
| 140 |
+
},
|
| 141 |
+
"workers": {
|
| 142 |
+
"type": "integer",
|
| 143 |
+
"description": "When Black formats multiple files, it may use a process pool to speed up formatting. This option controls the number of parallel workers. This can also be specified via the BLACK_NUM_WORKERS environment variable. Defaults to the number of CPUs in the system."
|
| 144 |
+
},
|
| 145 |
+
"quiet": {
|
| 146 |
+
"type": "boolean",
|
| 147 |
+
"description": "Stop emitting all non-critical output. Error messages will still be emitted (which can silenced by 2>/dev/null).",
|
| 148 |
+
"default": false
|
| 149 |
+
},
|
| 150 |
+
"verbose": {
|
| 151 |
+
"type": "boolean",
|
| 152 |
+
"description": "Emit messages about files that were not changed or were ignored due to exclusion patterns. If Black is using a configuration file, a message detailing which one it is using will be emitted.",
|
| 153 |
+
"default": false
|
| 154 |
+
},
|
| 155 |
+
"no-cache": {
|
| 156 |
+
"type": "boolean",
|
| 157 |
+
"description": "Skip reading and writing the cache, forcing Black to reformat all included files.",
|
| 158 |
+
"default": false
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
}
|