[3.12] gh-108682: [Enum] raise TypeError if super().__new__ called in custom __new__ (GH-108704) (#108733)

gh-108682: [Enum] raise TypeError if super().__new__ called in custom __new__ (GH-108704)

When overriding the `__new__` method of an enum, the underlying data type should be created directly; i.e. .

    member = object.__new__(cls)
    member = int.__new__(cls, value)
    member = str.__new__(cls, value)

Calling `super().__new__()` finds the lookup version of `Enum.__new__`, and will now raise an exception when detected.
(cherry picked from commit d48760b2f1)

Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
This commit is contained in:
Miss Islington (bot) 2023-08-31 15:04:55 -07:00 committed by GitHub
parent a92c60c99b
commit 8c3793a539
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 260 additions and 56 deletions

View file

@ -236,11 +236,82 @@ class _EnumTests:
values = None
def setUp(self):
class BaseEnum(self.enum_type):
if self.__class__.__name__[-5:] == 'Class':
class BaseEnum(self.enum_type):
@enum.property
def first(self):
return '%s is first!' % self.name
class MainEnum(BaseEnum):
first = auto()
second = auto()
third = auto()
if issubclass(self.enum_type, Flag):
dupe = 3
else:
dupe = third
self.MainEnum = MainEnum
#
class NewStrEnum(self.enum_type):
def __str__(self):
return self.name.upper()
first = auto()
self.NewStrEnum = NewStrEnum
#
class NewFormatEnum(self.enum_type):
def __format__(self, spec):
return self.name.upper()
first = auto()
self.NewFormatEnum = NewFormatEnum
#
class NewStrFormatEnum(self.enum_type):
def __str__(self):
return self.name.title()
def __format__(self, spec):
return ''.join(reversed(self.name))
first = auto()
self.NewStrFormatEnum = NewStrFormatEnum
#
class NewBaseEnum(self.enum_type):
def __str__(self):
return self.name.title()
def __format__(self, spec):
return ''.join(reversed(self.name))
class NewSubEnum(NewBaseEnum):
first = auto()
self.NewSubEnum = NewSubEnum
#
class LazyGNV(self.enum_type):
def _generate_next_value_(name, start, last, values):
pass
self.LazyGNV = LazyGNV
#
class BusyGNV(self.enum_type):
@staticmethod
def _generate_next_value_(name, start, last, values):
pass
self.BusyGNV = BusyGNV
#
self.is_flag = False
self.names = ['first', 'second', 'third']
if issubclass(MainEnum, StrEnum):
self.values = self.names
elif MainEnum._member_type_ is str:
self.values = ['1', '2', '3']
elif issubclass(self.enum_type, Flag):
self.values = [1, 2, 4]
self.is_flag = True
self.dupe2 = MainEnum(5)
else:
self.values = self.values or [1, 2, 3]
#
if not getattr(self, 'source_values', False):
self.source_values = self.values
elif self.__class__.__name__[-8:] == 'Function':
@enum.property
def first(self):
return '%s is first!' % self.name
class MainEnum(BaseEnum):
BaseEnum = self.enum_type('BaseEnum', {'first':first})
#
first = auto()
second = auto()
third = auto()
@ -248,63 +319,60 @@ class _EnumTests:
dupe = 3
else:
dupe = third
self.MainEnum = MainEnum
#
class NewStrEnum(self.enum_type):
self.MainEnum = MainEnum = BaseEnum('MainEnum', dict(first=first, second=second, third=third, dupe=dupe))
#
def __str__(self):
return self.name.upper()
first = auto()
self.NewStrEnum = NewStrEnum
#
class NewFormatEnum(self.enum_type):
self.NewStrEnum = self.enum_type('NewStrEnum', (('first',first),('__str__',__str__)))
#
def __format__(self, spec):
return self.name.upper()
first = auto()
self.NewFormatEnum = NewFormatEnum
#
class NewStrFormatEnum(self.enum_type):
self.NewFormatEnum = self.enum_type('NewFormatEnum', [('first',first),('__format__',__format__)])
#
def __str__(self):
return self.name.title()
def __format__(self, spec):
return ''.join(reversed(self.name))
first = auto()
self.NewStrFormatEnum = NewStrFormatEnum
#
class NewBaseEnum(self.enum_type):
self.NewStrFormatEnum = self.enum_type('NewStrFormatEnum', dict(first=first, __format__=__format__, __str__=__str__))
#
def __str__(self):
return self.name.title()
def __format__(self, spec):
return ''.join(reversed(self.name))
class NewSubEnum(NewBaseEnum):
first = auto()
self.NewSubEnum = NewSubEnum
#
class LazyGNV(self.enum_type):
NewBaseEnum = self.enum_type('NewBaseEnum', dict(__format__=__format__, __str__=__str__))
class NewSubEnum(NewBaseEnum):
first = auto()
self.NewSubEnum = NewBaseEnum('NewSubEnum', 'first')
#
def _generate_next_value_(name, start, last, values):
pass
self.LazyGNV = LazyGNV
#
class BusyGNV(self.enum_type):
self.LazyGNV = self.enum_type('LazyGNV', {'_generate_next_value_':_generate_next_value_})
#
@staticmethod
def _generate_next_value_(name, start, last, values):
pass
self.BusyGNV = BusyGNV
#
self.is_flag = False
self.names = ['first', 'second', 'third']
if issubclass(MainEnum, StrEnum):
self.values = self.names
elif MainEnum._member_type_ is str:
self.values = ['1', '2', '3']
elif issubclass(self.enum_type, Flag):
self.values = [1, 2, 4]
self.is_flag = True
self.dupe2 = MainEnum(5)
self.BusyGNV = self.enum_type('BusyGNV', {'_generate_next_value_':_generate_next_value_})
#
self.is_flag = False
self.names = ['first', 'second', 'third']
if issubclass(MainEnum, StrEnum):
self.values = self.names
elif MainEnum._member_type_ is str:
self.values = ['1', '2', '3']
elif issubclass(self.enum_type, Flag):
self.values = [1, 2, 4]
self.is_flag = True
self.dupe2 = MainEnum(5)
else:
self.values = self.values or [1, 2, 3]
#
if not getattr(self, 'source_values', False):
self.source_values = self.values
else:
self.values = self.values or [1, 2, 3]
#
if not getattr(self, 'source_values', False):
self.source_values = self.values
raise ValueError('unknown enum style: %r' % self.__class__.__name__)
def assertFormatIsValue(self, spec, member):
self.assertEqual(spec.format(member), spec.format(member.value))
@ -332,6 +400,17 @@ class _EnumTests:
with self.assertRaises(AttributeError):
del Season.SPRING.name
def test_bad_new_super(self):
with self.assertRaisesRegex(
TypeError,
'has no members defined',
):
class BadSuper(self.enum_type):
def __new__(cls, value):
obj = super().__new__(cls, value)
return obj
failed = 1
def test_basics(self):
TE = self.MainEnum
if self.is_flag:
@ -387,7 +466,7 @@ class _EnumTests:
MainEnum = self.MainEnum
self.assertIn(MainEnum.first, MainEnum)
self.assertTrue(self.values[0] in MainEnum)
if type(self) is not TestStrEnum:
if type(self) not in (TestStrEnumClass, TestStrEnumFunction):
self.assertFalse('first' in MainEnum)
val = MainEnum.dupe
self.assertIn(val, MainEnum)
@ -909,15 +988,23 @@ class _FlagTests:
self.assertTrue(~OpenXYZ(0), (X|Y|Z))
class TestPlainEnum(_EnumTests, _PlainOutputTests, unittest.TestCase):
class TestPlainEnumClass(_EnumTests, _PlainOutputTests, unittest.TestCase):
enum_type = Enum
class TestPlainFlag(_EnumTests, _PlainOutputTests, _FlagTests, unittest.TestCase):
class TestPlainEnumFunction(_EnumTests, _PlainOutputTests, unittest.TestCase):
enum_type = Enum
class TestPlainFlagClass(_EnumTests, _PlainOutputTests, _FlagTests, unittest.TestCase):
enum_type = Flag
class TestIntEnum(_EnumTests, _MinimalOutputTests, unittest.TestCase):
class TestPlainFlagFunction(_EnumTests, _PlainOutputTests, _FlagTests, unittest.TestCase):
enum_type = Flag
class TestIntEnumClass(_EnumTests, _MinimalOutputTests, unittest.TestCase):
enum_type = IntEnum
#
def test_shadowed_attr(self):
@ -929,7 +1016,17 @@ class TestIntEnum(_EnumTests, _MinimalOutputTests, unittest.TestCase):
self.assertIs(Number.numerator.divisor, Number.divisor)
class TestStrEnum(_EnumTests, _MinimalOutputTests, unittest.TestCase):
class TestIntEnumFunction(_EnumTests, _MinimalOutputTests, unittest.TestCase):
enum_type = IntEnum
#
def test_shadowed_attr(self):
Number = IntEnum('Number', ('divisor', 'numerator'))
#
self.assertEqual(Number.divisor.numerator, 1)
self.assertIs(Number.numerator.divisor, Number.divisor)
class TestStrEnumClass(_EnumTests, _MinimalOutputTests, unittest.TestCase):
enum_type = StrEnum
#
def test_shadowed_attr(self):
@ -942,64 +1039,141 @@ class TestStrEnum(_EnumTests, _MinimalOutputTests, unittest.TestCase):
self.assertIs(Book.title.author, Book.author)
class TestIntFlag(_EnumTests, _MinimalOutputTests, _FlagTests, unittest.TestCase):
class TestStrEnumFunction(_EnumTests, _MinimalOutputTests, unittest.TestCase):
enum_type = StrEnum
#
def test_shadowed_attr(self):
Book = StrEnum('Book', ('author', 'title'))
#
self.assertEqual(Book.author.title(), 'Author')
self.assertEqual(Book.title.title(), 'Title')
self.assertIs(Book.title.author, Book.author)
class TestIntFlagClass(_EnumTests, _MinimalOutputTests, _FlagTests, unittest.TestCase):
enum_type = IntFlag
class TestMixedInt(_EnumTests, _MixedOutputTests, unittest.TestCase):
class TestIntFlagFunction(_EnumTests, _MinimalOutputTests, _FlagTests, unittest.TestCase):
enum_type = IntFlag
class TestMixedIntClass(_EnumTests, _MixedOutputTests, unittest.TestCase):
class enum_type(int, Enum): pass
class TestMixedStr(_EnumTests, _MixedOutputTests, unittest.TestCase):
class TestMixedIntFunction(_EnumTests, _MixedOutputTests, unittest.TestCase):
enum_type = Enum('enum_type', type=int)
class TestMixedStrClass(_EnumTests, _MixedOutputTests, unittest.TestCase):
class enum_type(str, Enum): pass
class TestMixedIntFlag(_EnumTests, _MixedOutputTests, _FlagTests, unittest.TestCase):
class TestMixedStrFunction(_EnumTests, _MixedOutputTests, unittest.TestCase):
enum_type = Enum('enum_type', type=str)
class TestMixedIntFlagClass(_EnumTests, _MixedOutputTests, _FlagTests, unittest.TestCase):
class enum_type(int, Flag): pass
class TestMixedDate(_EnumTests, _MixedOutputTests, unittest.TestCase):
class TestMixedIntFlagFunction(_EnumTests, _MixedOutputTests, _FlagTests, unittest.TestCase):
enum_type = Flag('enum_type', type=int)
class TestMixedDateClass(_EnumTests, _MixedOutputTests, unittest.TestCase):
#
values = [date(2021, 12, 25), date(2020, 3, 15), date(2019, 11, 27)]
source_values = [(2021, 12, 25), (2020, 3, 15), (2019, 11, 27)]
#
class enum_type(date, Enum):
@staticmethod
def _generate_next_value_(name, start, count, last_values):
values = [(2021, 12, 25), (2020, 3, 15), (2019, 11, 27)]
return values[count]
class TestMinimalDate(_EnumTests, _MinimalOutputTests, unittest.TestCase):
class TestMixedDateFunction(_EnumTests, _MixedOutputTests, unittest.TestCase):
#
values = [date(2021, 12, 25), date(2020, 3, 15), date(2019, 11, 27)]
source_values = [(2021, 12, 25), (2020, 3, 15), (2019, 11, 27)]
#
# staticmethod decorator will be added by EnumType if not present
def _generate_next_value_(name, start, count, last_values):
values = [(2021, 12, 25), (2020, 3, 15), (2019, 11, 27)]
return values[count]
#
enum_type = Enum('enum_type', {'_generate_next_value_':_generate_next_value_}, type=date)
class TestMinimalDateClass(_EnumTests, _MinimalOutputTests, unittest.TestCase):
#
values = [date(2023, 12, 1), date(2016, 2, 29), date(2009, 1, 1)]
source_values = [(2023, 12, 1), (2016, 2, 29), (2009, 1, 1)]
#
class enum_type(date, ReprEnum):
# staticmethod decorator will be added by EnumType if absent
def _generate_next_value_(name, start, count, last_values):
values = [(2023, 12, 1), (2016, 2, 29), (2009, 1, 1)]
return values[count]
class TestMixedFloat(_EnumTests, _MixedOutputTests, unittest.TestCase):
class TestMinimalDateFunction(_EnumTests, _MinimalOutputTests, unittest.TestCase):
#
values = [date(2023, 12, 1), date(2016, 2, 29), date(2009, 1, 1)]
source_values = [(2023, 12, 1), (2016, 2, 29), (2009, 1, 1)]
#
@staticmethod
def _generate_next_value_(name, start, count, last_values):
values = [(2023, 12, 1), (2016, 2, 29), (2009, 1, 1)]
return values[count]
#
enum_type = ReprEnum('enum_type', {'_generate_next_value_':_generate_next_value_}, type=date)
class TestMixedFloatClass(_EnumTests, _MixedOutputTests, unittest.TestCase):
#
values = [1.1, 2.2, 3.3]
#
class enum_type(float, Enum):
def _generate_next_value_(name, start, count, last_values):
values = [1.1, 2.2, 3.3]
return values[count]
class TestMinimalFloat(_EnumTests, _MinimalOutputTests, unittest.TestCase):
class TestMixedFloatFunction(_EnumTests, _MixedOutputTests, unittest.TestCase):
#
values = [1.1, 2.2, 3.3]
#
def _generate_next_value_(name, start, count, last_values):
values = [1.1, 2.2, 3.3]
return values[count]
#
enum_type = Enum('enum_type', {'_generate_next_value_':_generate_next_value_}, type=float)
class TestMinimalFloatClass(_EnumTests, _MinimalOutputTests, unittest.TestCase):
#
values = [4.4, 5.5, 6.6]
#
class enum_type(float, ReprEnum):
def _generate_next_value_(name, start, count, last_values):
values = [4.4, 5.5, 6.6]
return values[count]
class TestMinimalFloatFunction(_EnumTests, _MinimalOutputTests, unittest.TestCase):
#
values = [4.4, 5.5, 6.6]
#
def _generate_next_value_(name, start, count, last_values):
values = [4.4, 5.5, 6.6]
return values[count]
#
enum_type = ReprEnum('enum_type', {'_generate_next_value_':_generate_next_value_}, type=float)
class TestSpecial(unittest.TestCase):
"""
various operations that are not attributable to every possible enum