From f931992f42607c1622be4102976fd6d1998f7cca Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Mon, 8 Apr 2024 19:13:44 -0700 Subject: [PATCH 1/3] fix _simple_enum's detection of aliases --- Lib/enum.py | 72 +++++++++++-------- Lib/http/__init__.py | 1 - Lib/test/test_enum.py | 52 +++++++++++++- ...-04-08-19-12-26.gh-issue-117663.CPfc_p.rst | 2 + 4 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-04-08-19-12-26.gh-issue-117663.CPfc_p.rst diff --git a/Lib/enum.py b/Lib/enum.py index 2a135e1b1f1826..fda7f552d8a166 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -1088,8 +1088,6 @@ def _add_member_(cls, name, member): setattr(cls, name, member) # now add to _member_map_ (even aliases) cls._member_map_[name] = member - # - cls._member_map_[name] = member EnumMeta = EnumType # keep EnumMeta name for backwards compatibility @@ -1802,20 +1800,31 @@ def convert_class(cls): for name, value in attrs.items(): if isinstance(value, auto) and auto.value is _auto_null: value = gnv(name, 1, len(member_names), gnv_last_values) - if value in value2member_map or value in unhashable_values: + # create basic member (possibly isolate value for alias check) + if use_args: + if not isinstance(value, tuple): + value = (value, ) + member = new_member(enum_class, *value) + value = value[0] + else: + member = new_member(enum_class) + if __new__ is None: + member._value_ = value + # now check if alias + try: + contained = None + contained = value2member_map.get(member._value_) + except TypeError: + if member._value_ in unhashable_values: + for m in enum_class: + if m._value_ == member._value_: + contained = m + break + if contained is not None: # an alias to an existing member - enum_class(value)._add_alias_(name) + contained._add_alias_(name) else: - # create the member - if use_args: - if not isinstance(value, tuple): - value = (value, ) - member = new_member(enum_class, *value) - value = value[0] - else: - member = new_member(enum_class) - if __new__ is None: - member._value_ = value + # finish creating member member._name_ = name member.__objclass__ = enum_class member.__init__(value) @@ -1847,24 +1856,31 @@ def convert_class(cls): if value.value is _auto_null: value.value = gnv(name, 1, len(member_names), gnv_last_values) value = value.value + # create basic member (possibly isolate value for alias check) + if use_args: + if not isinstance(value, tuple): + value = (value, ) + member = new_member(enum_class, *value) + value = value[0] + else: + member = new_member(enum_class) + if __new__ is None: + member._value_ = value + # now check if alias try: - contained = value in value2member_map + contained = None + contained = value2member_map.get(member._value_) except TypeError: - contained = value in unhashable_values - if contained: + if member._value_ in unhashable_values: + for m in enum_class: + if m._value_ == member._value_: + contained = m + break + if contained is not None: # an alias to an existing member - enum_class(value)._add_alias_(name) + contained._add_alias_(name) else: - # create the member - if use_args: - if not isinstance(value, tuple): - value = (value, ) - member = new_member(enum_class, *value) - value = value[0] - else: - member = new_member(enum_class) - if __new__ is None: - member._value_ = value + # finish creating member member._name_ = name member.__objclass__ = enum_class member.__init__(value) diff --git a/Lib/http/__init__.py b/Lib/http/__init__.py index e093a1fec4dffc..f3fbd7606432b3 100644 --- a/Lib/http/__init__.py +++ b/Lib/http/__init__.py @@ -26,7 +26,6 @@ class HTTPStatus: def __new__(cls, value, phrase, description=''): obj = int.__new__(cls, value) obj._value_ = value - obj.phrase = phrase obj.description = description return obj diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 6418d243db65ce..529dfc62eff680 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -5170,7 +5170,57 @@ class Unhashable: self.assertIn('python', Unhashable) self.assertEqual(Unhashable.name.value, 'python') self.assertEqual(Unhashable.name.name, 'name') - _test_simple_enum(Unhashable, Unhashable) + _test_simple_enum(CheckedUnhashable, Unhashable) + ## + class CheckedComplexStatus(IntEnum): + def __new__(cls, value, phrase, description=''): + obj = int.__new__(cls, value) + obj._value_ = value + obj.phrase = phrase + obj.description = description + return obj + CONTINUE = 100, 'Continue', 'Request received, please continue' + PROCESSING = 102, 'Processing' + EARLY_HINTS = 103, 'Early Hints' + SOME_HINTS = 103, 'Some Early Hints' + # + @_simple_enum(IntEnum) + class ComplexStatus: + def __new__(cls, value, phrase, description=''): + obj = int.__new__(cls, value) + obj._value_ = value + obj.phrase = phrase + obj.description = description + return obj + CONTINUE = 100, 'Continue', 'Request received, please continue' + PROCESSING = 102, 'Processing' + EARLY_HINTS = 103, 'Early Hints' + SOME_HINTS = 103, 'Some Early Hints' + _test_simple_enum(CheckedComplexStatus, ComplexStatus) + # + # + class CheckedComplexFlag(IntFlag): + def __new__(cls, value, label): + obj = int.__new__(cls, value) + obj._value_ = value + obj.label = label + return obj + SHIRT = 1, 'upper half' + VEST = 1, 'outer upper half' + PANTS = 2, 'lower half' + self.assertIs(CheckedComplexFlag.SHIRT, CheckedComplexFlag.VEST) + # + @_simple_enum(IntFlag) + class ComplexFlag: + def __new__(cls, value, label): + obj = int.__new__(cls, value) + obj._value_ = value + obj.label = label + return obj + SHIRT = 1, 'upper half' + VEST = 1, 'uppert half' + PANTS = 2, 'lower half' + _test_simple_enum(CheckedComplexFlag, ComplexFlag) class MiscTestCase(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2024-04-08-19-12-26.gh-issue-117663.CPfc_p.rst b/Misc/NEWS.d/next/Library/2024-04-08-19-12-26.gh-issue-117663.CPfc_p.rst new file mode 100644 index 00000000000000..2c7a5224b5a6eb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-08-19-12-26.gh-issue-117663.CPfc_p.rst @@ -0,0 +1,2 @@ +Fix ``_simple_enum`` to detect aliases when multiple arguments are present +but only one is the member value. From b81989891a937749b8ad6ad117d03345864fd8e3 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Tue, 9 Apr 2024 06:49:12 -0700 Subject: [PATCH 2/3] improve code --- Lib/enum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index fda7f552d8a166..98a49eafbb9897 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -1812,9 +1812,9 @@ def convert_class(cls): member._value_ = value # now check if alias try: - contained = None contained = value2member_map.get(member._value_) except TypeError: + contained = None if member._value_ in unhashable_values: for m in enum_class: if m._value_ == member._value_: @@ -1868,9 +1868,9 @@ def convert_class(cls): member._value_ = value # now check if alias try: - contained = None contained = value2member_map.get(member._value_) except TypeError: + contained = None if member._value_ in unhashable_values: for m in enum_class: if m._value_ == member._value_: From c0a86c4cb8531eb533cfdee43cb376cf37cb36a4 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Tue, 9 Apr 2024 07:31:22 -0700 Subject: [PATCH 3/3] undo whitespace change --- Lib/http/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/http/__init__.py b/Lib/http/__init__.py index f3fbd7606432b3..e093a1fec4dffc 100644 --- a/Lib/http/__init__.py +++ b/Lib/http/__init__.py @@ -26,6 +26,7 @@ class HTTPStatus: def __new__(cls, value, phrase, description=''): obj = int.__new__(cls, value) obj._value_ = value + obj.phrase = phrase obj.description = description return obj