Skip to content

Commit 86d81c3

Browse files
authored
Merge pull request #152 from yilei/push_up_to_327465554
Push up to 327465554
2 parents 06edd9c + 590e936 commit 86d81c3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2642
-553
lines changed

absl/BUILD

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
load(":_build_defs.bzl", "py2and3_test", "py2py3_test_binary")
22

3-
licenses(["notice"]) # Apache 2.0
3+
licenses(["notice"])
44

55
exports_files(["LICENSE"])
66

absl/CHANGELOG.md

+45-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,46 @@ The format is based on [Keep a Changelog](https://keepachangelog.com).
88

99
Nothing notable unreleased.
1010

11+
## 0.10.0 (2020-08-19)
12+
13+
### Added
14+
15+
* (testing) `_TempDir` and `_TempFile` now implement `__fspath__` to satisfy
16+
`os.PathLike`
17+
* (logging) `--logger_levels`: allows specifying the log levels of loggers.
18+
* (flags) `FLAGS.validate_all_flags`: a new method that validates all flags
19+
and raises an exception if one fails.
20+
* (flags) `FLAGS.get_flags_for_module`: Allows fetching the flags a module
21+
defines.
22+
* (testing) `parameterized.TestCase`: Supports async test definitions.
23+
* (testing,app) Added `--pdb` flag: When true, uncaught exceptions will be
24+
handled by `pdb.post_mortem`. This is an alias for `--pdb_post_mortem`.
25+
26+
### Changed
27+
28+
* (testing) Failed tests output a copy/pastable test id to make it easier to
29+
copy the failing test to the command line.
30+
* (testing) `@parameterized.parameters` now treats a single `abc.Mapping` as
31+
a single test case, consistent with `named_parameters`. Previously the
32+
`abc.Mapping` is treated as if only its keys are passed as a list of test
33+
cases. If you were relying on the old inconsistent behavior, explicitly
34+
convert the `abc.Mapping` to a `list`.
35+
* (flags) `DEFINE_enum_class` and `DEFINE_mutlti_enum_class` accept a
36+
`case_sensitive` argument. When `False` (the default), strings are mapped to
37+
enum member names without case sensitivity, and member names are serialized
38+
in lowercase form. Flag definitions for enums whose members include
39+
duplicates when case is ignored must now explicitly pass
40+
`case_sensitive=True`.
41+
42+
### Fixed
43+
44+
* (flags) Defining an alias no longer marks the aliased flag as always present
45+
on the command line.
46+
* (flags) Aliasing a multi flag no longer causes the default value to be
47+
appended to.
48+
* (flags) Alias default values now matched the aliased default value.
49+
* (flags) Alias `present` counter now correctly reflects command line usage.
50+
1151
## 0.9.0 (2019-12-17)
1252

1353
### Added
@@ -40,8 +80,8 @@ Nothing notable unreleased.
4080
* (bazel) Tests now pass when bazel
4181
`--incompatible_allow_python_version_transitions=true` is set.
4282
* (bazel) Both Python 2 and Python 3 versions of tests are now created. To
43-
only run one major Python version, use
44-
`bazel test --test_tag_filters=-python[23]` to ignore the other version.
83+
only run one major Python version, use `bazel test
84+
--test_tag_filters=-python[23]` to ignore the other version.
4585
* (testing) `assertTotallyOrdered` no longer requires objects to implement
4686
`__hash__`.
4787
* (testing) `absltest` now integrates better with `--pdb_post_mortem`.
@@ -50,9 +90,9 @@ Nothing notable unreleased.
5090

5191
### Fixed
5292

53-
* #99: `absl.logging` no longer registers itself to `logging.root` at import
54-
time.
55-
* #108: Tests now pass with Bazel 0.28.0 on macOS.
93+
* #99: `absl.logging` no longer registers itself to `logging.root` at import
94+
time.
95+
* #108: Tests now pass with Bazel 0.28.0 on macOS.
5696

5797
## 0.7.1 (2019-03-12)
5898

absl/app.py

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def main(argv):
5151
flags.DEFINE_boolean('pdb_post_mortem', False,
5252
'Set to true to handle uncaught exceptions with PDB '
5353
'post mortem.')
54+
flags.DEFINE_alias('pdb', 'pdb_post_mortem')
5455
flags.DEFINE_boolean('run_with_profiling', False,
5556
'Set to true for profiling the script. '
5657
'Execution will be slower, and the output format might '

absl/flags/BUILD

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
load("//absl:_build_defs.bzl", "py2and3_test", "py2py3_test_binary")
2+
13
licenses(["notice"]) # Apache 2.0
24

35
exports_files(["LICENSE"])
46

5-
load("//absl:_build_defs.bzl", "py2and3_test")
6-
77
py_library(
88
name = "flags",
99
srcs = ["__init__.py"],
@@ -114,6 +114,7 @@ py2and3_test(
114114
":_argument_parser",
115115
"//absl:_enum_module",
116116
"//absl/testing:absltest",
117+
"//absl/testing:parameterized",
117118
"@six_archive//:six",
118119
],
119120
)
@@ -196,11 +197,10 @@ py2and3_test(
196197
],
197198
)
198199

199-
py_binary(
200+
py2py3_test_binary(
200201
name = "tests/argparse_flags_test_helper",
201202
testonly = 1,
202203
srcs = ["tests/argparse_flags_test_helper.py"],
203-
python_version = "PY2",
204204
srcs_version = "PY2AND3",
205205
deps = [
206206
":argparse_flags",

absl/flags/__init__.py

+9-15
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
1514
"""This package is used to define and parse command line flags.
1615
1716
This package defines a *distributed* flag-definition policy: rather than
@@ -75,7 +74,6 @@
7574
DEFINE_alias = _defines.DEFINE_alias
7675
# pylint: enable=invalid-name
7776

78-
7977
# Flag validators.
8078
register_validator = _validators.register_validator
8179
validator = _validators.validator
@@ -86,13 +84,11 @@
8684
mark_flags_as_mutual_exclusive = _validators.mark_flags_as_mutual_exclusive
8785
mark_bool_flags_as_mutual_exclusive = _validators.mark_bool_flags_as_mutual_exclusive
8886

89-
9087
# Key flag related functions.
9188
declare_key_flag = _defines.declare_key_flag
9289
adopt_module_key_flags = _defines.adopt_module_key_flags
9390
disclaim_key_flags = _defines.disclaim_key_flags
9491

95-
9692
# Module exceptions.
9793
# pylint: disable=invalid-name
9894
Error = _exceptions.Error
@@ -104,14 +100,14 @@
104100
ValidationError = _exceptions.ValidationError
105101
FlagNameConflictsWithMethodError = _exceptions.FlagNameConflictsWithMethodError
106102

107-
108103
# Public classes.
109104
Flag = _flag.Flag
110105
BooleanFlag = _flag.BooleanFlag
111106
EnumFlag = _flag.EnumFlag
112107
EnumClassFlag = _flag.EnumClassFlag
113108
MultiFlag = _flag.MultiFlag
114109
MultiEnumClassFlag = _flag.MultiEnumClassFlag
110+
FlagHolder = _flagvalues.FlagHolder
115111
FlagValues = _flagvalues.FlagValues
116112
ArgumentParser = _argument_parser.ArgumentParser
117113
BooleanParser = _argument_parser.BooleanParser
@@ -127,28 +123,26 @@
127123
WhitespaceSeparatedListParser = _argument_parser.WhitespaceSeparatedListParser
128124
# pylint: enable=invalid-name
129125

130-
131126
# Helper functions.
132127
get_help_width = _helpers.get_help_width
133128
text_wrap = _helpers.text_wrap
134129
flag_dict_to_args = _helpers.flag_dict_to_args
135130
doc_to_help = _helpers.doc_to_help
136131

137-
138132
# Special flags.
139133
_helpers.SPECIAL_FLAGS = FlagValues()
140134

141135
DEFINE_string(
142136
'flagfile', '',
143137
'Insert flag definitions from the given file into the command line.',
144-
_helpers.SPECIAL_FLAGS)
145-
146-
DEFINE_string(
147-
'undefok', '',
148-
'comma-separated list of flag names that it is okay to specify '
149-
'on the command line even if the program does not define a flag '
150-
'with that name. IMPORTANT: flags in this list that have '
151-
'arguments MUST use the --flag=value format.', _helpers.SPECIAL_FLAGS)
138+
_helpers.SPECIAL_FLAGS) # pytype: disable=wrong-arg-types
139+
140+
DEFINE_string('undefok', '',
141+
'comma-separated list of flag names that it is okay to specify '
142+
'on the command line even if the program does not define a flag '
143+
'with that name. IMPORTANT: flags in this list that have '
144+
'arguments MUST use the --flag=value format.',
145+
_helpers.SPECIAL_FLAGS) # pytype: disable=wrong-arg-types
152146

153147
# The global FlagValues instance.
154148
FLAGS = _flagvalues.FLAGS

absl/flags/_argument_parser.py

+84-8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from __future__ import division
2323
from __future__ import print_function
2424

25+
import collections
2526
import csv
2627
import io
2728
import string
@@ -76,6 +77,24 @@ def __call__(cls, *args, **kwargs):
7677
return type.__call__(cls, *args)
7778

7879

80+
# NOTE about Genericity and Metaclass of ArgumentParser.
81+
# (1) In the .py source (this file)
82+
# - is not declared as Generic
83+
# - has _ArgumentParserCache as a metaclass
84+
# (2) In the .pyi source (type stub)
85+
# - is declared as Generic
86+
# - doesn't have a metaclass
87+
# The reason we need this is due to Generic having a different metaclass
88+
# (for python versions <= 3.7) and a class can have only one metaclass.
89+
#
90+
# * Lack of metaclass in .pyi is not a deal breaker, since the metaclass
91+
# doesn't affect any type information. Also type checkers can check the type
92+
# parameters.
93+
# * However, not declaring ArgumentParser as Generic in the source affects
94+
# runtime annotation processing. In particular this means, subclasses should
95+
# inherit from `ArgumentParser` and not `ArgumentParser[SomeType]`.
96+
# The corresponding DEFINE_someType method (the public API) can be annotated
97+
# to return FlagHolder[SomeType].
7998
class ArgumentParser(six.with_metaclass(_ArgumentParserCache, object)):
8099
"""Base class used to parse and convert arguments.
81100
@@ -354,11 +373,13 @@ def flag_type(self):
354373
class EnumClassParser(ArgumentParser):
355374
"""Parser of an Enum class member."""
356375

357-
def __init__(self, enum_class):
376+
def __init__(self, enum_class, case_sensitive=True):
358377
"""Initializes EnumParser.
359378
360379
Args:
361380
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.
362383
363384
Raises:
364385
TypeError: When enum_class is not a subclass of Enum.
@@ -373,9 +394,30 @@ def __init__(self, enum_class):
373394
if not enum_class.__members__:
374395
raise ValueError('enum_class cannot be empty, but "{}" is empty.'
375396
.format(enum_class))
397+
if not case_sensitive:
398+
members = collections.Counter(
399+
name.lower() for name in enum_class.__members__)
400+
duplicate_keys = {
401+
member for member, count in members.items() if count > 1
402+
}
403+
if duplicate_keys:
404+
raise ValueError(
405+
'Duplicate enum values for {} using case_sensitive=False'.format(
406+
duplicate_keys))
376407

377408
super(EnumClassParser, self).__init__()
378409
self.enum_class = enum_class
410+
self._case_sensitive = case_sensitive
411+
if case_sensitive:
412+
self._member_names = tuple(enum_class.__members__)
413+
else:
414+
self._member_names = tuple(
415+
name.lower() for name in enum_class.__members__)
416+
417+
@property
418+
def member_names(self):
419+
"""The accepted enum names, in lowercase if not case sensitive."""
420+
return self._member_names
379421

380422
def parse(self, argument):
381423
"""Determines validity of argument and returns the correct element of enum.
@@ -391,11 +433,19 @@ def parse(self, argument):
391433
"""
392434
if isinstance(argument, self.enum_class):
393435
return argument
394-
if argument not in self.enum_class.__members__:
395-
raise ValueError('value should be one of <%s>' %
396-
'|'.join(self.enum_class.__members__.keys()))
436+
elif not isinstance(argument, six.string_types):
437+
raise ValueError(
438+
'{} is not an enum member or a name of a member in {}'.format(
439+
argument, self.enum_class))
440+
key = EnumParser(
441+
self._member_names, case_sensitive=self._case_sensitive).parse(argument)
442+
if self._case_sensitive:
443+
return self.enum_class[key]
397444
else:
398-
return self.enum_class[argument]
445+
# If EnumParser.parse() return a value, we're guaranteed to find it
446+
# as a member of the class
447+
return next(value for name, value in self.enum_class.__members__.items()
448+
if name.lower() == key.lower())
399449

400450
def flag_type(self):
401451
"""See base class."""
@@ -413,13 +463,30 @@ def serialize(self, value):
413463

414464

415465
class EnumClassListSerializer(ListSerializer):
466+
"""A serializer for MultiEnumClass flags.
467+
468+
This serializer simply joins the output of `EnumClassSerializer` using a
469+
provided seperator.
470+
"""
471+
472+
def __init__(self, list_sep, **kwargs):
473+
"""Initializes EnumClassListSerializer.
474+
475+
Args:
476+
list_sep: String to be used as a separator when serializing
477+
**kwargs: Keyword arguments to the `EnumClassSerializer` used to serialize
478+
individual values.
479+
"""
480+
super(EnumClassListSerializer, self).__init__(list_sep)
481+
self._element_serializer = EnumClassSerializer(**kwargs)
416482

417483
def serialize(self, value):
418484
"""See base class."""
419485
if isinstance(value, list):
420-
return self.list_sep.join(_helpers.str_or_unicode(x.name) for x in value)
486+
return self.list_sep.join(
487+
self._element_serializer.serialize(x) for x in value)
421488
else:
422-
return _helpers.str_or_unicode(value.name)
489+
return self._element_serializer.serialize(value)
423490

424491

425492
class CsvListSerializer(ArgumentSerializer):
@@ -448,9 +515,18 @@ def serialize(self, value):
448515
class EnumClassSerializer(ArgumentSerializer):
449516
"""Class for generating string representations of an enum class flag value."""
450517

518+
def __init__(self, lowercase):
519+
"""Initializes EnumClassSerializer.
520+
521+
Args:
522+
lowercase: If True, enum member names are lowercased during serialization.
523+
"""
524+
self._lowercase = lowercase
525+
451526
def serialize(self, value):
452527
"""Returns a serialized string of the Enum class value."""
453-
return _helpers.str_or_unicode(value.name)
528+
as_string = _helpers.str_or_unicode(value.name)
529+
return as_string.lower() if self._lowercase else as_string
454530

455531

456532
class BaseListParser(ArgumentParser):

0 commit comments

Comments
 (0)