Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions Lib/enum.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import sys
from types import MappingProxyType, DynamicClassAttribute
from operator import or_ as _or_
from functools import reduce
from builtins import property as _bltin_property, bin as _bltin_bin


Expand Down Expand Up @@ -97,6 +99,9 @@ def _iter_bits_lsb(num):
yield b
num ^= b

def show_flag_values(value):
return list(_iter_bits_lsb(value))

def bin(num, max_bits=None):
"""
Like built-in bin(), except negative values are represented in
Expand Down Expand Up @@ -1601,31 +1606,39 @@ def __call__(self, enumeration):
else:
raise Exception('verify: unknown type %r' % enum_type)
if missing:
raise ValueError('invalid %s %r: missing values %s' % (
raise ValueError(('invalid %s %r: missing values %s' % (
enum_type, cls_name, ', '.join((str(m) for m in missing)))
)
)[:256])
# limit max length to protect against DOS attacks
elif check is NAMED_FLAGS:
# examine each alias and check for unnamed flags
member_names = enumeration._member_names_
member_values = [m.value for m in enumeration]
missing = []
missing_names = []
missing_value = 0
for name, alias in enumeration._member_map_.items():
if name in member_names:
# not an alias
continue
values = list(_iter_bits_lsb(alias.value))
missed = [v for v in values if v not in member_values]
if missed:
plural = ('', 's')[len(missed) > 1]
a = ('a ', '')[len(missed) > 1]
missing.append('%r is missing %snamed flag%s for value%s %s' % (
name, a, plural, plural,
', '.join(str(v) for v in missed)
))
if missing:
missing_names.append(name)
missing_value |= reduce(_or_, missed)
if missing_names:
if len(missing_names) == 1:
alias = 'alias %s is missing' % missing_names[0]
else:
alias = 'aliases %s and %s are missing' % (
', '.join(missing_names[:-1]), missing_names[-1]
)
if _is_single_bit(missing_value):
value = 'value 0x%x' % missing_value
else:
value = 'combined values of 0x%x' % missing_value
raise ValueError(
'invalid Flag %r: %s'
% (cls_name, '; '.join(missing))
'invalid Flag %r: %s %s [use `enum.show_flag_values(value)` for details]'
% (cls_name, alias, value)
)
return enumeration

Expand Down
12 changes: 5 additions & 7 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -2158,9 +2158,6 @@ def __init__(self, *args):
self._valid = True
@classmethod
def _missing_(cls, value):
# encountered an unknown value!
# Luckily I'm a LenientStrEnum, so I won't crash just yet.
# You might want to add a new case though.
unknown = cls._member_type_.__new__(cls, value)
unknown._valid = False
unknown._name_ = value.upper()
Expand Down Expand Up @@ -3586,14 +3583,15 @@ class Bizarre(Flag):
self.assertEqual(Bizarre.d.value, 6)
with self.assertRaisesRegex(
ValueError,
"invalid Flag 'Bizarre': 'b' is missing named flags for values 1, 2; 'd' is missing a named flag for value 2",
"invalid Flag 'Bizarre': aliases b and d are missing combined values of 0x3 .use `enum.show_flag_values.value.` for details.",
):
@verify(NAMED_FLAGS)
class Bizarre(Flag):
b = 3
c = 4
d = 6
#
self.assertEqual(enum.show_flag_values(3), [1, 2])
class Bizarre(IntFlag):
b = 3
c = 4
Expand All @@ -3604,13 +3602,13 @@ class Bizarre(IntFlag):
self.assertEqual(Bizarre.d.value, 6)
with self.assertRaisesRegex(
ValueError,
"invalid Flag 'Bizarre': 'b' is missing named flags for values 1, 2; 'd' is missing a named flag for value 2",
"invalid Flag 'Bizarre': alias d is missing value 0x2 .use `enum.show_flag_values.value.` for details.",
):
@verify(NAMED_FLAGS)
class Bizarre(IntFlag):
b = 3
c = 4
d = 6
self.assertEqual(enum.show_flag_values(2), [2])

def test_unique_clean(self):
@verify(UNIQUE)
Expand Down Expand Up @@ -3877,7 +3875,7 @@ class Missing:

class MiscTestCase(unittest.TestCase):
def test__all__(self):
support.check__all__(self, enum, not_exported={'bin'})
support.check__all__(self, enum, not_exported={'bin', 'show_flag_values'})


# These are unordered here on purpose to ensure that declaration order
Expand Down