gh-112328: Make EnumDict usable on its own and document it (GH-123669)

Co-authored-by: Rafi <rafi.promit@gmail.com>
Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <wk.cvs.github@sydorenko.org.ua>
Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
This commit is contained in:
Petr Viktorin 2024-12-20 20:40:58 +01:00 committed by GitHub
parent 3879ca0100
commit 2a66dd33df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 16 deletions

View file

@ -342,12 +342,13 @@ class EnumDict(dict):
EnumType will use the names found in self._member_names as the
enumeration member names.
"""
def __init__(self):
def __init__(self, cls_name=None):
super().__init__()
self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7
self._last_values = []
self._ignore = []
self._auto_called = False
self._cls_name = cls_name
def __setitem__(self, key, value):
"""
@ -358,7 +359,7 @@ class EnumDict(dict):
Single underscore (sunder) names are reserved.
"""
if _is_private(self._cls_name, key):
if self._cls_name is not None and _is_private(self._cls_name, key):
# do nothing, name will be a normal attribute
pass
elif _is_sunder(key):
@ -406,7 +407,7 @@ class EnumDict(dict):
value = value.value
elif _is_descriptor(value):
pass
elif _is_internal_class(self._cls_name, value):
elif self._cls_name is not None and _is_internal_class(self._cls_name, value):
# do nothing, name will be a normal attribute
pass
else:
@ -478,8 +479,7 @@ class EnumType(type):
# check that previous enum members do not exist
metacls._check_for_existing_members_(cls, bases)
# create the namespace dict
enum_dict = EnumDict()
enum_dict._cls_name = cls
enum_dict = EnumDict(cls)
# inherit previous flags and _generate_next_value_ function
member_type, first_enum = metacls._get_mixins_(cls, bases)
if first_enum is not None:

View file

@ -14,7 +14,7 @@ from datetime import date
from enum import Enum, EnumMeta, 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, _iter_bits_lsb
from enum import member, nonmember, _iter_bits_lsb, EnumDict
from io import StringIO
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
from test import support
@ -5440,6 +5440,37 @@ class TestConvert(unittest.TestCase):
self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5')
class TestEnumDict(unittest.TestCase):
def test_enum_dict_in_metaclass(self):
"""Test that EnumDict is usable as a class namespace"""
class Meta(type):
@classmethod
def __prepare__(metacls, cls, bases, **kwds):
return EnumDict(cls)
class MyClass(metaclass=Meta):
a = 1
with self.assertRaises(TypeError):
a = 2 # duplicate
with self.assertRaises(ValueError):
_a_sunder_ = 3
def test_enum_dict_standalone(self):
"""Test that EnumDict is usable on its own"""
enumdict = EnumDict()
enumdict['a'] = 1
with self.assertRaises(TypeError):
enumdict['a'] = 'other value'
# Only MutableMapping interface is overridden for now.
# If this stops passing, update the documentation.
enumdict |= {'a': 'other value'}
self.assertEqual(enumdict['a'], 'other value')
# helpers
def enum_dir(cls):