Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8a4f085

Browse files
authored
bpo-44356: [Enum] allow multiple data-type mixins if they are all the same (GH-26649)
This enables, for example, two base Enums to both inherit from `str`, and then both be mixed into the same final Enum: class Str1Enum(str, Enum): # some behavior here class Str2Enum(str, Enum): # some more behavior here class FinalStrEnum(Str1Enum, Str2Enum): # this now works
1 parent 42d5a4f commit 8a4f085

File tree

3 files changed

+52
-4
lines changed

3 files changed

+52
-4
lines changed

Lib/enum.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -819,27 +819,27 @@ def _get_mixins_(class_name, bases):
819819
return object, Enum
820820

821821
def _find_data_type(bases):
822-
data_types = []
822+
data_types = set()
823823
for chain in bases:
824824
candidate = None
825825
for base in chain.__mro__:
826826
if base is object:
827827
continue
828828
elif issubclass(base, Enum):
829829
if base._member_type_ is not object:
830-
data_types.append(base._member_type_)
830+
data_types.add(base._member_type_)
831831
break
832832
elif '__new__' in base.__dict__:
833833
if issubclass(base, Enum):
834834
continue
835-
data_types.append(candidate or base)
835+
data_types.add(candidate or base)
836836
break
837837
else:
838838
candidate = base
839839
if len(data_types) > 1:
840840
raise TypeError('%r: too many data types: %r' % (class_name, data_types))
841841
elif data_types:
842-
return data_types[0]
842+
return data_types.pop()
843843
else:
844844
return None
845845

Lib/test/test_enum.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2144,6 +2144,53 @@ def __new__(cls, value):
21442144
return member
21452145
self.assertEqual(Fee.TEST, 2)
21462146

2147+
def test_miltuple_mixin_with_common_data_type(self):
2148+
class CaseInsensitiveStrEnum(str, Enum):
2149+
@classmethod
2150+
def _missing_(cls, value):
2151+
for member in cls._member_map_.values():
2152+
if member._value_.lower() == value.lower():
2153+
return member
2154+
return super()._missing_(value)
2155+
#
2156+
class LenientStrEnum(str, Enum):
2157+
def __init__(self, *args):
2158+
self._valid = True
2159+
@classmethod
2160+
def _missing_(cls, value):
2161+
# encountered an unknown value!
2162+
# Luckily I'm a LenientStrEnum, so I won't crash just yet.
2163+
# You might want to add a new case though.
2164+
unknown = cls._member_type_.__new__(cls, value)
2165+
unknown._valid = False
2166+
unknown._name_ = value.upper()
2167+
unknown._value_ = value
2168+
cls._member_map_[value] = unknown
2169+
return unknown
2170+
@property
2171+
def valid(self):
2172+
return self._valid
2173+
#
2174+
class JobStatus(CaseInsensitiveStrEnum, LenientStrEnum):
2175+
ACTIVE = "active"
2176+
PENDING = "pending"
2177+
TERMINATED = "terminated"
2178+
#
2179+
JS = JobStatus
2180+
self.assertEqual(list(JobStatus), [JS.ACTIVE, JS.PENDING, JS.TERMINATED])
2181+
self.assertEqual(JS.ACTIVE, 'active')
2182+
self.assertEqual(JS.ACTIVE.value, 'active')
2183+
self.assertIs(JS('Active'), JS.ACTIVE)
2184+
self.assertTrue(JS.ACTIVE.valid)
2185+
missing = JS('missing')
2186+
self.assertEqual(list(JobStatus), [JS.ACTIVE, JS.PENDING, JS.TERMINATED])
2187+
self.assertEqual(JS.ACTIVE, 'active')
2188+
self.assertEqual(JS.ACTIVE.value, 'active')
2189+
self.assertIs(JS('Active'), JS.ACTIVE)
2190+
self.assertTrue(JS.ACTIVE.valid)
2191+
self.assertTrue(isinstance(missing, JS))
2192+
self.assertFalse(missing.valid)
2193+
21472194
def test_empty_globals(self):
21482195
# bpo-35717: sys._getframe(2).f_globals['__name__'] fails with KeyError
21492196
# when using compile and exec because f_globals is empty
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Enum] Allow multiple data-type mixins if they are all the same.

0 commit comments

Comments
 (0)