Enum class decorator that checks user-selectable constraints on an
enumeration.
+ :func:`member`
+
+ Make `obj` a member. Can be used as a decorator.
+
+ :func:`nonmember`
+
+ Do not make `obj` a member. Can be used as a decorator.
+
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``FlagBoundary``, ``property``
+.. versionadded:: 3.11 ``member``, ``nonmember``
---------------
.. versionadded:: 3.11
+.. decorator:: member
+
+ A decorator for use in enums: it's target will become a member.
+
+ .. versionadded:: 3.11
+
+.. decorator:: nonmember
+
+ A decorator for use in enums: it's target will not become a member.
+
+ .. versionadded:: 3.11
+
---------------
Notes
__all__ = [
'EnumType', 'EnumMeta',
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum',
- 'auto', 'unique', 'property', 'verify',
+ 'auto', 'unique', 'property', 'verify', 'member', 'nonmember',
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
'global_flag_repr', 'global_enum_repr', 'global_str', 'global_enum',
'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE',
# This is also why there are checks in EnumType like `if Enum is not None`
Enum = Flag = EJECT = _stdlib_enums = ReprEnum = None
+class nonmember(object):
+ """
+ Protects item from becaming an Enum member during class creation.
+ """
+ def __init__(self, value):
+ self.value = value
+
+class member(object):
+ """
+ Forces item to became an Enum member during class creation.
+ """
+ def __init__(self, value):
+ self.value = value
+
def _is_descriptor(obj):
"""
Returns True if obj is a descriptor, False otherwise.
name[-2:-1] != '_'
)
+def _is_internal_class(cls_name, obj):
+ # do not use `re` as `re` imports `enum`
+ if not isinstance(obj, type):
+ return False
+ qualname = getattr(obj, '__qualname__', '')
+ s_pattern = cls_name + '.' + getattr(obj, '__name__', '')
+ e_pattern = '.' + s_pattern
+ return qualname == s_pattern or qualname.endswith(e_pattern)
+
def _is_private(cls_name, name):
# do not use `re` as `re` imports `enum`
pattern = '_%s__' % (cls_name, )
lines[j] = l[i:]
return '\n'.join(lines)
+class _auto_null:
+ def __repr__(self):
+ return '_auto_null'
+_auto_null = _auto_null()
-_auto_null = object()
class auto:
"""
Instances are replaced with an appropriate value in Enum class suites.
"""
value = _auto_null
+ def __repr__(self):
+ return "auto(%r)" % self.value
+
class property(DynamicClassAttribute):
"""
This is a descriptor, used to define attributes that act differently
Single underscore (sunder) names are reserved.
"""
+ if _is_internal_class(self._cls_name, value):
+ import warnings
+ warnings.warn(
+ "In 3.13 classes created inside an enum will not become a member. "
+ "Use the `member` decorator to keep the current behavior.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
if _is_private(self._cls_name, key):
- # do nothing, name will be a normal attribute
+ # also do nothing, name will be a normal attribute
pass
elif _is_sunder(key):
if key not in (
raise TypeError('%r already defined as %r' % (key, self[key]))
elif key in self._ignore:
pass
- elif not _is_descriptor(value):
+ elif isinstance(value, nonmember):
+ # unwrap value here; it won't be processed by the below `else`
+ value = value.value
+ elif _is_descriptor(value):
+ pass
+ # TODO: uncomment next three lines in 3.12
+ # elif _is_internal_class(self._cls_name, value):
+ # # do nothing, name will be a normal attribute
+ # pass
+ else:
if key in self:
# enum overwriting a descriptor?
raise TypeError('%r already defined as %r' % (key, self[key]))
+ elif isinstance(value, member):
+ # unwrap value here -- it will become a member
+ value = value.value
if isinstance(value, auto):
if value.value == _auto_null:
value.value = self._generate_next_value(
from enum import Enum, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto
from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum
from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum
+from enum import member, nonmember
from io import StringIO
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
from test import support
raise Theory
self.assertEqual(Theory.__qualname__, 'spanish_inquisition')
+ def test_enum_of_types(self):
+ """Support using Enum to refer to types deliberately."""
+ class MyTypes(Enum):
+ i = int
+ f = float
+ s = str
+ self.assertEqual(MyTypes.i.value, int)
+ self.assertEqual(MyTypes.f.value, float)
+ self.assertEqual(MyTypes.s.value, str)
+ class Foo:
+ pass
+ class Bar:
+ pass
+ class MyTypes2(Enum):
+ a = Foo
+ b = Bar
+ self.assertEqual(MyTypes2.a.value, Foo)
+ self.assertEqual(MyTypes2.b.value, Bar)
+ class SpamEnumNotInner:
+ pass
+ class SpamEnum(Enum):
+ spam = SpamEnumNotInner
+ self.assertEqual(SpamEnum.spam.value, SpamEnumNotInner)
+
+ @unittest.skipIf(
+ python_version >= (3, 13),
+ 'inner classes are not members',
+ )
+ def test_nested_classes_in_enum_are_members(self):
+ """
+ Check for warnings pre-3.13
+ """
+ with self.assertWarnsRegex(DeprecationWarning, 'will not become a member'):
+ class Outer(Enum):
+ a = 1
+ b = 2
+ class Inner(Enum):
+ foo = 10
+ bar = 11
+ self.assertTrue(isinstance(Outer.Inner, Outer))
+ self.assertEqual(Outer.a.value, 1)
+ self.assertEqual(Outer.Inner.value.foo.value, 10)
+ self.assertEqual(
+ list(Outer.Inner.value),
+ [Outer.Inner.value.foo, Outer.Inner.value.bar],
+ )
+ self.assertEqual(
+ list(Outer),
+ [Outer.a, Outer.b, Outer.Inner],
+ )
+
+ @unittest.skipIf(
+ python_version < (3, 13),
+ 'inner classes are still members',
+ )
+ def test_nested_classes_in_enum_are_not_members(self):
+ """Support locally-defined nested classes."""
+ class Outer(Enum):
+ a = 1
+ b = 2
+ class Inner(Enum):
+ foo = 10
+ bar = 11
+ self.assertTrue(isinstance(Outer.Inner, type))
+ self.assertEqual(Outer.a.value, 1)
+ self.assertEqual(Outer.Inner.foo.value, 10)
+ self.assertEqual(
+ list(Outer.Inner),
+ [Outer.Inner.foo, Outer.Inner.bar],
+ )
+ self.assertEqual(
+ list(Outer),
+ [Outer.a, Outer.b],
+ )
+
+ def test_nested_classes_in_enum_with_nonmember(self):
+ class Outer(Enum):
+ a = 1
+ b = 2
+ @nonmember
+ class Inner(Enum):
+ foo = 10
+ bar = 11
+ self.assertTrue(isinstance(Outer.Inner, type))
+ self.assertEqual(Outer.a.value, 1)
+ self.assertEqual(Outer.Inner.foo.value, 10)
+ self.assertEqual(
+ list(Outer.Inner),
+ [Outer.Inner.foo, Outer.Inner.bar],
+ )
+ self.assertEqual(
+ list(Outer),
+ [Outer.a, Outer.b],
+ )
+
+ def test_enum_of_types_with_nonmember(self):
+ """Support using Enum to refer to types deliberately."""
+ class MyTypes(Enum):
+ i = int
+ f = nonmember(float)
+ s = str
+ self.assertEqual(MyTypes.i.value, int)
+ self.assertTrue(MyTypes.f is float)
+ self.assertEqual(MyTypes.s.value, str)
+ class Foo:
+ pass
+ class Bar:
+ pass
+ class MyTypes2(Enum):
+ a = Foo
+ b = nonmember(Bar)
+ self.assertEqual(MyTypes2.a.value, Foo)
+ self.assertTrue(MyTypes2.b is Bar)
+ class SpamEnumIsInner:
+ pass
+ class SpamEnum(Enum):
+ spam = nonmember(SpamEnumIsInner)
+ self.assertTrue(SpamEnum.spam is SpamEnumIsInner)
+
+ def test_nested_classes_in_enum_with_member(self):
+ """Support locally-defined nested classes."""
+ class Outer(Enum):
+ a = 1
+ b = 2
+ @member
+ class Inner(Enum):
+ foo = 10
+ bar = 11
+ self.assertTrue(isinstance(Outer.Inner, Outer))
+ self.assertEqual(Outer.a.value, 1)
+ self.assertEqual(Outer.Inner.value.foo.value, 10)
+ self.assertEqual(
+ list(Outer.Inner.value),
+ [Outer.Inner.value.foo, Outer.Inner.value.bar],
+ )
+ self.assertEqual(
+ list(Outer),
+ [Outer.a, Outer.b, Outer.Inner],
+ )
+
def test_enum_with_value_name(self):
class Huh(Enum):
name = 1
Kevin Walzer
Rodrigo Steinmuller Wanderley
Dingyuan Wang
+Edward C Wang
Jiahua Wang
Ke Wang
Liang-Bo Wang
--- /dev/null
+Deprecate nested classes in enum definitions becoming members -- in 3.13
+they will be normal classes; add `member` and `nonmember` functions to allow
+control over results now.