Skip to content

Commit ddbd7d4

Browse files
authored
Merge pull request #155 from yilei/push_up_to_339298457
Push up to 339298457
2 parents 86d81c3 + 9a090c9 commit ddbd7d4

17 files changed

+321
-85
lines changed

absl/CHANGELOG.md

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

99
Nothing notable unreleased.
1010

11+
## 0.11.0 (2020-10-27)
12+
13+
### Changed
14+
15+
* (testing) Surplus entries in AssertionError stack traces from absltest are
16+
now suppressed and no longer reported in the xml_reporter.
17+
* (logging) An exception is now raised instead of `logging.fatal` when logging
18+
directories cannot be found.
19+
* (testing) Multiple flags are now set together before their validators run.
20+
This resolves an issue where multi-flag validators rely on specific flag
21+
combinations.
22+
* (flags) As a deterrent for misuse, FlagHolder objects will now raise a
23+
TypeError exception when used in a conditional statement or equality
24+
expression.
25+
1126
## 0.10.0 (2020-08-19)
1227

1328
### Added

absl/_build_defs.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def py2py3_test_binary(name, **kwargs):
3333
if len(kwargs.get("srcs", [])) != 1:
3434
fail("py2py3_test_binary requires main or len(srcs)==1")
3535
kwargs["main"] = kwargs["srcs"][0]
36+
kwargs.setdefault("tags", []).append("py3-compatible")
3637

3738
native.alias(name = name, actual = select({
3839
"//absl:py3_mode": name + "_py3",

absl/app.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ def run(
277277
Args:
278278
main: The main function to execute. It takes an single argument "argv",
279279
which is a list of command line arguments with parsed flags removed.
280-
If it returns an integer, it is used as the process's exit code.
280+
The return value is passed to `sys.exit`, and so for example
281+
a return value of 0 or None results in a successful termination, whereas
282+
a return value of 1 results in abnormal termination.
283+
For more details, see https://docs.python.org/3/library/sys#sys.exit
281284
argv: A non-empty list of the command line arguments including program name,
282285
sys.argv is used if None.
283286
flags_parser: Callable[[List[Text]], Any], the function used to parse flags.

absl/flags/_flagvalues.py

+31-8
Original file line numberDiff line numberDiff line change
@@ -499,16 +499,25 @@ def __getattr__(self, name):
499499

500500
def __setattr__(self, name, value):
501501
"""Sets the 'value' attribute of the flag --name."""
502-
fl = self._flags()
503-
if name in self.__dict__['__hiddenflags']:
504-
raise AttributeError(name)
505-
if name not in fl:
506-
return self._set_unknown_flag(name, value)
507-
fl[name].value = value
508-
self._assert_validators(fl[name].validators)
509-
fl[name].using_default_value = False
502+
self._set_attributes(**{name: value})
510503
return value
511504

505+
def _set_attributes(self, **attributes):
506+
"""Sets multiple flag values together, triggers validators afterwards."""
507+
fl = self._flags()
508+
known_flags = set()
509+
for name, value in six.iteritems(attributes):
510+
if name in self.__dict__['__hiddenflags']:
511+
raise AttributeError(name)
512+
if name in fl:
513+
fl[name].value = value
514+
known_flags.add(name)
515+
else:
516+
self._set_unknown_flag(name, value)
517+
for name in known_flags:
518+
self._assert_validators(fl[name].validators)
519+
fl[name].using_default_value = False
520+
512521
def validate_all_flags(self):
513522
"""Verifies whether all flags pass validation.
514523
@@ -1348,6 +1357,20 @@ def __init__(self, flag_values, flag, ensure_non_none_value=False):
13481357
# This allows future use of this for "required flags with None default"
13491358
self._ensure_non_none_value = ensure_non_none_value
13501359

1360+
def __eq__(self, other):
1361+
raise TypeError(
1362+
"unsupported operand type(s) for ==: '{0}' and '{1}' "
1363+
"(did you mean to use '{0}.value' instead?)".format(
1364+
type(self).__name__, type(other).__name__))
1365+
1366+
def __bool__(self):
1367+
raise TypeError(
1368+
"bool() not supported for instances of type '{0}' "
1369+
"(did you mean to use '{0}.value' instead?)".format(
1370+
type(self).__name__))
1371+
1372+
__nonzero__ = __bool__
1373+
13511374
@property
13521375
def name(self):
13531376
return self._name

absl/flags/_validators.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -334,16 +334,13 @@ def mark_flag_as_required(flag_name, flag_values=_flagvalues.FLAGS):
334334
Important note: validator will pass for any non-None value, such as False,
335335
0 (zero), '' (empty string) and so on.
336336
337-
It is recommended to call this method like this:
337+
If your module might be imported by others, and you only wish to make the flag
338+
required when the module is directly executed, call this method like this:
338339
339340
if __name__ == '__main__':
340341
flags.mark_flag_as_required('your_flag_name')
341342
app.run()
342343
343-
Because validation happens at app.run() we want to ensure required-ness
344-
is enforced at that time. You generally do not want to force users who import
345-
your code to have additional required flags for their own binaries or tests.
346-
347344
Args:
348345
flag_name: str, name of the flag
349346
flag_values: flags.FlagValues, optional FlagValues instance where the flag
@@ -367,7 +364,8 @@ def mark_flag_as_required(flag_name, flag_values=_flagvalues.FLAGS):
367364
def mark_flags_as_required(flag_names, flag_values=_flagvalues.FLAGS):
368365
"""Ensures that flags are not None during program execution.
369366
370-
Recommended usage:
367+
If your module might be imported by others, and you only wish to make the flag
368+
required when the module is directly executed, call this method like this:
371369
372370
if __name__ == '__main__':
373371
flags.mark_flags_as_required(['flag1', 'flag2', 'flag3'])

absl/flags/tests/_flagvalues_test.py

+55-12
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def _test_find_module_or_id_defining_flag(self, test_id):
240240
# Delete the changelist flag, its short name should still be registered.
241241
del fv.changelist
242242
module_or_id_changelist = testing_fn('changelist')
243-
self.assertEqual(module_or_id_changelist, None)
243+
self.assertIsNone(module_or_id_changelist)
244244
module_or_id_c = testing_fn('c')
245245
self.assertEqual(module_or_id_c, current_module_or_id)
246246
module_or_id_l = testing_fn('l')
@@ -333,30 +333,30 @@ def test_invalid_flag_name(self):
333333

334334
def test_len(self):
335335
fv = _flagvalues.FlagValues()
336-
self.assertEqual(0, len(fv))
336+
self.assertEmpty(fv)
337337
self.assertFalse(fv)
338338

339339
_defines.DEFINE_boolean('boolean', False, 'help', flag_values=fv)
340-
self.assertEqual(1, len(fv))
340+
self.assertLen(fv, 1)
341341
self.assertTrue(fv)
342342

343343
_defines.DEFINE_boolean(
344344
'bool', False, 'help', short_name='b', flag_values=fv)
345-
self.assertEqual(3, len(fv))
345+
self.assertLen(fv, 3)
346346
self.assertTrue(fv)
347347

348348
def test_pickle(self):
349349
fv = _flagvalues.FlagValues()
350-
with self.assertRaisesRegexp(TypeError, "can't pickle FlagValues"):
350+
with self.assertRaisesRegex(TypeError, "can't pickle FlagValues"):
351351
pickle.dumps(fv)
352352

353353
def test_copy(self):
354354
fv = _flagvalues.FlagValues()
355355
_defines.DEFINE_integer('answer', 0, 'help', flag_values=fv)
356356
fv(['', '--answer=1'])
357357

358-
with self.assertRaisesRegexp(
359-
TypeError, 'FlagValues does not support shallow copies'):
358+
with self.assertRaisesRegex(TypeError,
359+
'FlagValues does not support shallow copies'):
360360
copy.copy(fv)
361361

362362
fv2 = copy.deepcopy(fv)
@@ -640,6 +640,7 @@ def test_gnu_getopt_raise(self, *argv):
640640
class SettingUnknownFlagTest(absltest.TestCase):
641641

642642
def setUp(self):
643+
super(SettingUnknownFlagTest, self).setUp()
643644
self.setter_called = 0
644645

645646
def set_undef(self, unused_name, unused_val):
@@ -679,9 +680,39 @@ def setter(unused_name, unused_val):
679680
new_flags.undefined_flag = 0
680681

681682

683+
class SetAttributesTest(absltest.TestCase):
684+
685+
def setUp(self):
686+
super(SetAttributesTest, self).setUp()
687+
self.new_flags = _flagvalues.FlagValues()
688+
_defines.DEFINE_boolean(
689+
'defined_flag', None, '', flag_values=self.new_flags)
690+
_defines.DEFINE_boolean(
691+
'another_defined_flag', None, '', flag_values=self.new_flags)
692+
self.setter_called = 0
693+
694+
def set_undef(self, unused_name, unused_val):
695+
self.setter_called += 1
696+
697+
def test_two_defined_flags(self):
698+
self.new_flags._set_attributes(
699+
defined_flag=False, another_defined_flag=False)
700+
self.assertEqual(self.setter_called, 0)
701+
702+
def test_one_defined_one_undefined_flag(self):
703+
with self.assertRaises(_exceptions.UnrecognizedFlagError):
704+
self.new_flags._set_attributes(defined_flag=False, undefined_flag=0)
705+
706+
def test_register_unknown_flag_setter(self):
707+
self.new_flags._register_unknown_flag_setter(self.set_undef)
708+
self.new_flags._set_attributes(defined_flag=False, undefined_flag=0)
709+
self.assertEqual(self.setter_called, 1)
710+
711+
682712
class FlagsDashSyntaxTest(absltest.TestCase):
683713

684714
def setUp(self):
715+
super(FlagsDashSyntaxTest, self).setUp()
685716
self.fv = _flagvalues.FlagValues()
686717
_defines.DEFINE_string(
687718
'long_name', 'default', 'help', flag_values=self.fv, short_name='s')
@@ -754,15 +785,15 @@ def test_allow_overwrite_false(self):
754785

755786
fv.mark_as_parsed()
756787
self.assertEqual('foo', fv.default_foo)
757-
self.assertEqual(None, fv.default_none)
788+
self.assertIsNone(fv.default_none)
758789

759790
fv(['', '--default_foo=notFoo', '--default_none=notNone'])
760791
self.assertEqual('notFoo', fv.default_foo)
761792
self.assertEqual('notNone', fv.default_none)
762793

763794
fv.unparse_flags()
764795
self.assertEqual('foo', fv['default_foo'].value)
765-
self.assertEqual(None, fv['default_none'].value)
796+
self.assertIsNone(fv['default_none'].value)
766797

767798
fv(['', '--default_foo=alsoNotFoo', '--default_none=alsoNotNone'])
768799
self.assertEqual('alsoNotFoo', fv.default_foo)
@@ -772,15 +803,15 @@ def test_multi_string_default_none(self):
772803
fv = _flagvalues.FlagValues()
773804
_defines.DEFINE_multi_string('foo', None, 'help', flag_values=fv)
774805
fv.mark_as_parsed()
775-
self.assertEqual(None, fv.foo)
806+
self.assertIsNone(fv.foo)
776807
fv(['', '--foo=aa'])
777808
self.assertEqual(['aa'], fv.foo)
778809
fv.unparse_flags()
779-
self.assertEqual(None, fv['foo'].value)
810+
self.assertIsNone(fv['foo'].value)
780811
fv(['', '--foo=bb', '--foo=cc'])
781812
self.assertEqual(['bb', 'cc'], fv.foo)
782813
fv.unparse_flags()
783-
self.assertEqual(None, fv['foo'].value)
814+
self.assertIsNone(fv['foo'].value)
784815

785816
def test_multi_string_default_string(self):
786817
fv = _flagvalues.FlagValues()
@@ -883,6 +914,18 @@ def test_allow_override(self):
883914
self.assertEqual(3, first.value)
884915
self.assertEqual(3, second.value)
885916

917+
def test_eq(self):
918+
with self.assertRaises(TypeError):
919+
self.name_flag == 'value' # pylint: disable=pointless-statement
920+
921+
def test_eq_reflection(self):
922+
with self.assertRaises(TypeError):
923+
'value' == self.name_flag # pylint: disable=pointless-statement
924+
925+
def test_bool(self):
926+
with self.assertRaises(TypeError):
927+
bool(self.name_flag)
928+
886929

887930
if __name__ == '__main__':
888931
absltest.main()

absl/logging/__init__.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,10 @@ def find_log_dir_and_names(program_name=None, log_dir=None):
663663
664664
Returns:
665665
(log_dir, file_prefix, symlink_prefix)
666+
667+
Raises:
668+
FileNotFoundError: raised in Python 3 when it cannot find a log directory.
669+
OSError: raised in Python 2 when it cannot find a log directory.
666670
"""
667671
if not program_name:
668672
# Strip the extension (foobar.par becomes foobar, and
@@ -699,6 +703,10 @@ def find_log_dir(log_dir=None):
699703
directory. Otherwise if the --log_dir command-line flag is provided,
700704
the logfile will be created in that directory. Otherwise the logfile
701705
will be created in a standard location.
706+
707+
Raises:
708+
FileNotFoundError: raised in Python 3 when it cannot find a log directory.
709+
OSError: raised in Python 2 when it cannot find a log directory.
702710
"""
703711
# Get a list of possible log dirs (will try to use them in order).
704712
if log_dir:
@@ -715,7 +723,9 @@ def find_log_dir(log_dir=None):
715723
for d in dirs:
716724
if os.path.isdir(d) and os.access(d, os.W_OK):
717725
return d
718-
_absl_logger.fatal("Can't find a writable directory for logs, tried %s", dirs)
726+
exception_class = OSError if six.PY2 else FileNotFoundError
727+
raise exception_class(
728+
"Can't find a writable directory for logs, tried %s" % dirs)
719729

720730

721731
def get_absl_log_prefix(record):

absl/logging/tests/logging_test.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -753,13 +753,12 @@ def test_find_log_dir_with_tmp(self):
753753

754754
def test_find_log_dir_with_nothing(self):
755755
with mock.patch.object(os.path, 'exists'), \
756-
mock.patch.object(os.path, 'isdir'), \
757-
mock.patch.object(logging.get_absl_logger(), 'fatal') as mock_fatal:
756+
mock.patch.object(os.path, 'isdir'):
758757
os.path.exists.return_value = False
759758
os.path.isdir.return_value = False
760-
log_dir = logging.find_log_dir()
761-
mock_fatal.assert_called()
762-
self.assertEqual(None, log_dir)
759+
exception_class = OSError if six.PY2 else FileNotFoundError
760+
with self.assertRaises(exception_class):
761+
logging.find_log_dir()
763762

764763
def test_find_log_dir_and_names_with_args(self):
765764
user = 'test_user'

absl/testing/BUILD

-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ py_library(
5050
visibility = ["//visibility:public"],
5151
deps = [
5252
"//absl/flags",
53-
"@six_archive//:six",
5453
],
5554
)
5655

absl/testing/absltest.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@
113113

114114
_TEXT_OR_BINARY_TYPES = (six.text_type, six.binary_type)
115115

116+
# Suppress surplus entries in AssertionError stack traces.
117+
__unittest = True # pylint: disable=invalid-name
118+
116119

117120
def expectedFailureIf(condition, reason): # pylint: disable=invalid-name
118121
"""Expects the test to fail if the run condition is True.
@@ -2444,7 +2447,10 @@ def _get_qualname(cls):
24442447
def _rmtree_ignore_errors(path):
24452448
# type: (Text) -> None
24462449
if os.path.isfile(path):
2447-
os.unlink(path)
2450+
try:
2451+
os.unlink(path)
2452+
except OSError:
2453+
pass
24482454
else:
24492455
shutil.rmtree(path, ignore_errors=True)
24502456

absl/testing/flagsaver.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ def some_func():
6262
import inspect
6363

6464
from absl import flags
65-
import six
6665

6766
FLAGS = flags.FLAGS
6867

@@ -156,8 +155,7 @@ def __call__(self, func):
156155
def __enter__(self):
157156
self._saved_flag_values = save_flag_values(FLAGS)
158157
try:
159-
for name, value in six.iteritems(self._overrides):
160-
setattr(FLAGS, name, value)
158+
FLAGS._set_attributes(**self._overrides)
161159
except:
162160
# It may fail because of flag validators.
163161
restore_flag_values(self._saved_flag_values, FLAGS)

0 commit comments

Comments
 (0)