Skip to content

Commit dbac8f4

Browse files
authored
bpo-38659: [Enum] add _simple_enum decorator (GH-25285)
add: _simple_enum decorator to transform a normal class into an enum _test_simple_enum function to compare _old_convert_ to enable checking _convert_ generated enums _simple_enum takes a normal class and converts it into an enum: @simple_enum(Enum) class Color: RED = 1 GREEN = 2 BLUE = 3 _old_convert_ works much like _convert_ does, using the original logic: # in a test file import socket, enum CheckedAddressFamily = enum._old_convert_( enum.IntEnum, 'AddressFamily', 'socket', lambda C: C.isupper() and C.startswith('AF_'), source=_socket, ) test_simple_enum takes a traditional enum and a simple enum and compares the two: # in the REPL or the same module as Color class CheckedColor(Enum): RED = 1 GREEN = 2 BLUE = 3 _test_simple_enum(CheckedColor, Color) _test_simple_enum(CheckedAddressFamily, socket.AddressFamily) Any important differences will raise a TypeError
1 parent 7a04116 commit dbac8f4

File tree

20 files changed

+871
-34
lines changed

20 files changed

+871
-34
lines changed

Doc/library/enum.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,4 +621,3 @@ Utilites and Decorators
621621
Traceback (most recent call last):
622622
...
623623
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE
624-

Lib/ast.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import sys
2828
from _ast import *
2929
from contextlib import contextmanager, nullcontext
30-
from enum import IntEnum, auto
30+
from enum import IntEnum, auto, _simple_enum
3131

3232

3333
def parse(source, filename='<unknown>', mode='exec', *,
@@ -636,7 +636,8 @@ class Param(expr_context):
636636
# We unparse those infinities to INFSTR.
637637
_INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
638638

639-
class _Precedence(IntEnum):
639+
@_simple_enum(IntEnum)
640+
class _Precedence:
640641
"""Precedence table that originated from python grammar."""
641642

642643
TUPLE = auto()

Lib/enum.py

Lines changed: 319 additions & 7 deletions
Large diffs are not rendered by default.

Lib/http/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from enum import IntEnum
1+
from enum import IntEnum, _simple_enum
22

33
__all__ = ['HTTPStatus']
44

5-
class HTTPStatus(IntEnum):
5+
6+
@_simple_enum(IntEnum)
7+
class HTTPStatus:
68
"""HTTP status codes and reason phrases
79
810
Status codes from the following RFCs are all observed:

Lib/pstats.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@
2626
import marshal
2727
import re
2828

29-
from enum import Enum
29+
from enum import StrEnum, _simple_enum
3030
from functools import cmp_to_key
3131
from dataclasses import dataclass
3232
from typing import Dict
3333

3434
__all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"]
3535

36-
class SortKey(str, Enum):
36+
@_simple_enum(StrEnum)
37+
class SortKey:
3738
CALLS = 'calls', 'ncalls'
3839
CUMULATIVE = 'cumulative', 'cumtime'
3940
FILENAME = 'filename', 'module'

Lib/re.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@
143143
__version__ = "2.2.1"
144144

145145
@enum.global_enum
146-
class RegexFlag(enum.IntFlag, boundary=enum.KEEP):
146+
@enum._simple_enum(enum.IntFlag, boundary=enum.KEEP)
147+
class RegexFlag:
147148
ASCII = A = sre_compile.SRE_FLAG_ASCII # assume ascii "locale"
148149
IGNORECASE = I = sre_compile.SRE_FLAG_IGNORECASE # ignore case
149150
LOCALE = L = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale

Lib/ssl.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import os
9595
from collections import namedtuple
9696
from enum import Enum as _Enum, IntEnum as _IntEnum, IntFlag as _IntFlag
97+
from enum import _simple_enum, _test_simple_enum
9798

9899
import _ssl # if we can't import it, let the error propagate
99100

@@ -155,7 +156,8 @@
155156
_SSLv2_IF_EXISTS = getattr(_SSLMethod, 'PROTOCOL_SSLv2', None)
156157

157158

158-
class TLSVersion(_IntEnum):
159+
@_simple_enum(_IntEnum)
160+
class TLSVersion:
159161
MINIMUM_SUPPORTED = _ssl.PROTO_MINIMUM_SUPPORTED
160162
SSLv3 = _ssl.PROTO_SSLv3
161163
TLSv1 = _ssl.PROTO_TLSv1
@@ -165,7 +167,8 @@ class TLSVersion(_IntEnum):
165167
MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED
166168

167169

168-
class _TLSContentType(_IntEnum):
170+
@_simple_enum(_IntEnum)
171+
class _TLSContentType:
169172
"""Content types (record layer)
170173
171174
See RFC 8446, section B.1
@@ -179,7 +182,8 @@ class _TLSContentType(_IntEnum):
179182
INNER_CONTENT_TYPE = 0x101
180183

181184

182-
class _TLSAlertType(_IntEnum):
185+
@_simple_enum(_IntEnum)
186+
class _TLSAlertType:
183187
"""Alert types for TLSContentType.ALERT messages
184188
185189
See RFC 8466, section B.2
@@ -220,7 +224,8 @@ class _TLSAlertType(_IntEnum):
220224
NO_APPLICATION_PROTOCOL = 120
221225

222226

223-
class _TLSMessageType(_IntEnum):
227+
@_simple_enum(_IntEnum)
228+
class _TLSMessageType:
224229
"""Message types (handshake protocol)
225230
226231
See RFC 8446, section B.3

Lib/test/test_ast.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ast
22
import builtins
33
import dis
4+
import enum
45
import os
56
import sys
67
import types
@@ -698,6 +699,35 @@ def test_constant_as_name(self):
698699
with self.assertRaisesRegex(ValueError, f"Name node can't be used with '{constant}' constant"):
699700
compile(expr, "<test>", "eval")
700701

702+
def test_precedence_enum(self):
703+
class _Precedence(enum.IntEnum):
704+
"""Precedence table that originated from python grammar."""
705+
TUPLE = enum.auto()
706+
YIELD = enum.auto() # 'yield', 'yield from'
707+
TEST = enum.auto() # 'if'-'else', 'lambda'
708+
OR = enum.auto() # 'or'
709+
AND = enum.auto() # 'and'
710+
NOT = enum.auto() # 'not'
711+
CMP = enum.auto() # '<', '>', '==', '>=', '<=', '!=',
712+
# 'in', 'not in', 'is', 'is not'
713+
EXPR = enum.auto()
714+
BOR = EXPR # '|'
715+
BXOR = enum.auto() # '^'
716+
BAND = enum.auto() # '&'
717+
SHIFT = enum.auto() # '<<', '>>'
718+
ARITH = enum.auto() # '+', '-'
719+
TERM = enum.auto() # '*', '@', '/', '%', '//'
720+
FACTOR = enum.auto() # unary '+', '-', '~'
721+
POWER = enum.auto() # '**'
722+
AWAIT = enum.auto() # 'await'
723+
ATOM = enum.auto()
724+
def next(self):
725+
try:
726+
return self.__class__(self + 1)
727+
except ValueError:
728+
return self
729+
enum._test_simple_enum(_Precedence, ast._Precedence)
730+
701731

702732
class ASTHelpers_Test(unittest.TestCase):
703733
maxDiff = None

Lib/test/test_enum.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import threading
99
from collections import OrderedDict
1010
from enum import Enum, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto
11-
from enum import STRICT, CONFORM, EJECT, KEEP
11+
from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum
1212
from io import StringIO
1313
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
1414
from test import support
@@ -2511,10 +2511,13 @@ class Bizarre(Flag, boundary=KEEP):
25112511
d = 6
25122512
#
25132513
self.assertRaisesRegex(ValueError, 'invalid value: 7', Iron, 7)
2514+
#
25142515
self.assertIs(Water(7), Water.ONE|Water.TWO)
25152516
self.assertIs(Water(~9), Water.TWO)
2517+
#
25162518
self.assertEqual(Space(7), 7)
25172519
self.assertTrue(type(Space(7)) is int)
2520+
#
25182521
self.assertEqual(list(Bizarre), [Bizarre.c])
25192522
self.assertIs(Bizarre(3), Bizarre.b)
25202523
self.assertIs(Bizarre(6), Bizarre.d)
@@ -3053,16 +3056,20 @@ class Space(IntFlag, boundary=EJECT):
30533056
EIGHT = 8
30543057
self.assertIs(Space._boundary_, EJECT)
30553058
#
3059+
#
30563060
class Bizarre(IntFlag, boundary=KEEP):
30573061
b = 3
30583062
c = 4
30593063
d = 6
30603064
#
30613065
self.assertRaisesRegex(ValueError, 'invalid value: 5', Iron, 5)
3066+
#
30623067
self.assertIs(Water(7), Water.ONE|Water.TWO)
30633068
self.assertIs(Water(~9), Water.TWO)
3069+
#
30643070
self.assertEqual(Space(7), 7)
30653071
self.assertTrue(type(Space(7)) is int)
3072+
#
30663073
self.assertEqual(list(Bizarre), [Bizarre.c])
30673074
self.assertIs(Bizarre(3), Bizarre.b)
30683075
self.assertIs(Bizarre(6), Bizarre.d)
@@ -3577,6 +3584,41 @@ def test_inspect_classify_class_attrs(self):
35773584
if failed:
35783585
self.fail("result does not equal expected, see print above")
35793586

3587+
def test_test_simple_enum(self):
3588+
@_simple_enum(Enum)
3589+
class SimpleColor:
3590+
RED = 1
3591+
GREEN = 2
3592+
BLUE = 3
3593+
class CheckedColor(Enum):
3594+
RED = 1
3595+
GREEN = 2
3596+
BLUE = 3
3597+
self.assertTrue(_test_simple_enum(CheckedColor, SimpleColor) is None)
3598+
SimpleColor.GREEN._value_ = 9
3599+
self.assertRaisesRegex(
3600+
TypeError, "enum mismatch",
3601+
_test_simple_enum, CheckedColor, SimpleColor,
3602+
)
3603+
class CheckedMissing(IntFlag, boundary=KEEP):
3604+
SIXTY_FOUR = 64
3605+
ONE_TWENTY_EIGHT = 128
3606+
TWENTY_FORTY_EIGHT = 2048
3607+
ALL = 2048 + 128 + 64 + 12
3608+
CM = CheckedMissing
3609+
self.assertEqual(list(CheckedMissing), [CM.SIXTY_FOUR, CM.ONE_TWENTY_EIGHT, CM.TWENTY_FORTY_EIGHT])
3610+
#
3611+
@_simple_enum(IntFlag, boundary=KEEP)
3612+
class Missing:
3613+
SIXTY_FOUR = 64
3614+
ONE_TWENTY_EIGHT = 128
3615+
TWENTY_FORTY_EIGHT = 2048
3616+
ALL = 2048 + 128 + 64 + 12
3617+
M = Missing
3618+
self.assertEqual(list(CheckedMissing), [M.SIXTY_FOUR, M.ONE_TWENTY_EIGHT, M.TWENTY_FORTY_EIGHT])
3619+
#
3620+
_test_simple_enum(CheckedMissing, Missing)
3621+
35803622

35813623
class MiscTestCase(unittest.TestCase):
35823624
def test__all__(self):
@@ -3592,6 +3634,13 @@ def test__all__(self):
35923634
CONVERT_TEST_NAME_E = 5
35933635
CONVERT_TEST_NAME_F = 5
35943636

3637+
CONVERT_STRING_TEST_NAME_D = 5
3638+
CONVERT_STRING_TEST_NAME_C = 5
3639+
CONVERT_STRING_TEST_NAME_B = 5
3640+
CONVERT_STRING_TEST_NAME_A = 5 # This one should sort first.
3641+
CONVERT_STRING_TEST_NAME_E = 5
3642+
CONVERT_STRING_TEST_NAME_F = 5
3643+
35953644
class TestIntEnumConvert(unittest.TestCase):
35963645
def test_convert_value_lookup_priority(self):
35973646
test_type = enum.IntEnum._convert_(
@@ -3639,14 +3688,16 @@ def test_convert_raise(self):
36393688
filter=lambda x: x.startswith('CONVERT_TEST_'))
36403689

36413690
def test_convert_repr_and_str(self):
3691+
# reset global constants, as previous tests could have converted the
3692+
# integer values to enums
36423693
module = ('test.test_enum', '__main__')[__name__=='__main__']
36433694
test_type = enum.IntEnum._convert_(
36443695
'UnittestConvert',
36453696
module,
3646-
filter=lambda x: x.startswith('CONVERT_TEST_'))
3647-
self.assertEqual(repr(test_type.CONVERT_TEST_NAME_A), '%s.CONVERT_TEST_NAME_A' % module)
3648-
self.assertEqual(str(test_type.CONVERT_TEST_NAME_A), 'CONVERT_TEST_NAME_A')
3649-
self.assertEqual(format(test_type.CONVERT_TEST_NAME_A), '5')
3697+
filter=lambda x: x.startswith('CONVERT_STRING_TEST_'))
3698+
self.assertEqual(repr(test_type.CONVERT_STRING_TEST_NAME_A), '%s.CONVERT_STRING_TEST_NAME_A' % module)
3699+
self.assertEqual(str(test_type.CONVERT_STRING_TEST_NAME_A), 'CONVERT_STRING_TEST_NAME_A')
3700+
self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5')
36503701

36513702
# global names for StrEnum._convert_ test
36523703
CONVERT_STR_TEST_2 = 'goodbye'

0 commit comments

Comments
 (0)