mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
[3.13] gh-112328: Make EnumDict usable on its own and document it (GH-123669) (GH-128142)
Co-authored-by: Petr Viktorin <pviktori@redhat.com>
This commit is contained in:
parent
a52d663d0c
commit
875e49fb63
5 changed files with 72 additions and 8 deletions
|
@ -110,6 +110,10 @@ Module Contents
|
||||||
``KEEP`` which allows for more fine-grained control over how invalid values
|
``KEEP`` which allows for more fine-grained control over how invalid values
|
||||||
are dealt with in an enumeration.
|
are dealt with in an enumeration.
|
||||||
|
|
||||||
|
:class:`EnumDict`
|
||||||
|
|
||||||
|
A subclass of :class:`dict` for use when subclassing :class:`EnumType`.
|
||||||
|
|
||||||
:class:`auto`
|
:class:`auto`
|
||||||
|
|
||||||
Instances are replaced with an appropriate value for Enum members.
|
Instances are replaced with an appropriate value for Enum members.
|
||||||
|
@ -152,6 +156,7 @@ Module Contents
|
||||||
|
|
||||||
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
|
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
|
||||||
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``ReprEnum``, ``FlagBoundary``, ``property``, ``member``, ``nonmember``, ``global_enum``, ``show_flag_values``
|
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``ReprEnum``, ``FlagBoundary``, ``property``, ``member``, ``nonmember``, ``global_enum``, ``show_flag_values``
|
||||||
|
.. versionadded:: 3.13 ``EnumDict``
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -823,6 +828,26 @@ Data Types
|
||||||
|
|
||||||
.. versionadded:: 3.11
|
.. versionadded:: 3.11
|
||||||
|
|
||||||
|
.. class:: EnumDict
|
||||||
|
|
||||||
|
*EnumDict* is a subclass of :class:`dict` that is used as the namespace
|
||||||
|
for defining enum classes (see :ref:`prepare`).
|
||||||
|
It is exposed to allow subclasses of :class:`EnumType` with advanced
|
||||||
|
behavior like having multiple values per member.
|
||||||
|
It should be called with the name of the enum class being created, otherwise
|
||||||
|
private names and internal classes will not be handled correctly.
|
||||||
|
|
||||||
|
Note that only the :class:`~collections.abc.MutableMapping` interface
|
||||||
|
(:meth:`~object.__setitem__` and :meth:`~dict.update`) is overridden.
|
||||||
|
It may be possible to bypass the checks using other :class:`!dict`
|
||||||
|
operations like :meth:`|= <object.__ior__>`.
|
||||||
|
|
||||||
|
.. attribute:: EnumDict.member_names
|
||||||
|
|
||||||
|
A list of member names.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Supported ``__dunder__`` names
|
Supported ``__dunder__`` names
|
||||||
|
@ -966,7 +991,6 @@ Utilities and Decorators
|
||||||
Should only be used when the enum members are exported
|
Should only be used when the enum members are exported
|
||||||
to the module global namespace (see :class:`re.RegexFlag` for an example).
|
to the module global namespace (see :class:`re.RegexFlag` for an example).
|
||||||
|
|
||||||
|
|
||||||
.. versionadded:: 3.11
|
.. versionadded:: 3.11
|
||||||
|
|
||||||
.. function:: show_flag_values(value)
|
.. function:: show_flag_values(value)
|
||||||
|
@ -975,6 +999,7 @@ Utilities and Decorators
|
||||||
|
|
||||||
.. versionadded:: 3.11
|
.. versionadded:: 3.11
|
||||||
|
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
|
|
|
@ -889,6 +889,13 @@ email
|
||||||
the :cve:`2023-27043` fix.)
|
the :cve:`2023-27043` fix.)
|
||||||
|
|
||||||
|
|
||||||
|
enum
|
||||||
|
----
|
||||||
|
|
||||||
|
* :class:`~enum.EnumDict` has been made public to better support subclassing
|
||||||
|
:class:`~enum.EnumType`.
|
||||||
|
|
||||||
|
|
||||||
fractions
|
fractions
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
10
Lib/enum.py
10
Lib/enum.py
|
@ -343,12 +343,13 @@ class EnumDict(dict):
|
||||||
EnumType will use the names found in self._member_names as the
|
EnumType will use the names found in self._member_names as the
|
||||||
enumeration member names.
|
enumeration member names.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, cls_name=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7
|
self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7
|
||||||
self._last_values = []
|
self._last_values = []
|
||||||
self._ignore = []
|
self._ignore = []
|
||||||
self._auto_called = False
|
self._auto_called = False
|
||||||
|
self._cls_name = cls_name
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
"""
|
"""
|
||||||
|
@ -359,7 +360,7 @@ class EnumDict(dict):
|
||||||
|
|
||||||
Single underscore (sunder) names are reserved.
|
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
|
# do nothing, name will be a normal attribute
|
||||||
pass
|
pass
|
||||||
elif _is_sunder(key):
|
elif _is_sunder(key):
|
||||||
|
@ -413,7 +414,7 @@ class EnumDict(dict):
|
||||||
'old behavior', FutureWarning, stacklevel=2)
|
'old behavior', FutureWarning, stacklevel=2)
|
||||||
elif _is_descriptor(value):
|
elif _is_descriptor(value):
|
||||||
pass
|
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
|
# do nothing, name will be a normal attribute
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -485,8 +486,7 @@ class EnumType(type):
|
||||||
# check that previous enum members do not exist
|
# check that previous enum members do not exist
|
||||||
metacls._check_for_existing_members_(cls, bases)
|
metacls._check_for_existing_members_(cls, bases)
|
||||||
# create the namespace dict
|
# create the namespace dict
|
||||||
enum_dict = EnumDict()
|
enum_dict = EnumDict(cls)
|
||||||
enum_dict._cls_name = cls
|
|
||||||
# inherit previous flags and _generate_next_value_ function
|
# inherit previous flags and _generate_next_value_ function
|
||||||
member_type, first_enum = metacls._get_mixins_(cls, bases)
|
member_type, first_enum = metacls._get_mixins_(cls, bases)
|
||||||
if first_enum is not None:
|
if first_enum is not None:
|
||||||
|
|
|
@ -15,7 +15,7 @@ from functools import partial
|
||||||
from enum import Enum, EnumMeta, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto
|
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 STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum
|
||||||
from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum
|
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 io import StringIO
|
||||||
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
|
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
|
||||||
from test import support
|
from test import support
|
||||||
|
@ -5454,6 +5454,37 @@ class TestConvert(unittest.TestCase):
|
||||||
self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5')
|
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
|
# helpers
|
||||||
|
|
||||||
def enum_dir(cls):
|
def enum_dir(cls):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
:class:`enum.EnumDict` can now be used without resorting to private API.
|
Loading…
Add table
Add a link
Reference in a new issue