summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/enum.rst26
-rw-r--r--Doc/whatsnew/3.13.rst6
-rw-r--r--Lib/enum.py10
-rw-r--r--Lib/test/test_enum.py33
-rw-r--r--Misc/NEWS.d/next/Library/2024-09-04-14-13-14.gh-issue-121720.z9hhXQ.rst1
5 files changed, 60 insertions, 16 deletions
diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
index 51292a1..8ca9493 100644
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -110,6 +110,10 @@ Module Contents
``KEEP`` which allows for more fine-grained control over how invalid values
are dealt with in an enumeration.
+ :class:`EnumDict`
+
+ A subclass of :class:`dict` for use when subclassing :class:`EnumType`.
+
:class:`auto`
Instances are replaced with an appropriate value for Enum members.
@@ -149,14 +153,10 @@ Module Contents
Return a list of all power-of-two integers contained in a flag.
- :class:`EnumDict`
-
- A subclass of :class:`dict` for use when subclassing :class:`EnumType`.
-
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``ReprEnum``, ``FlagBoundary``, ``property``, ``member``, ``nonmember``, ``global_enum``, ``show_flag_values``
-.. versionadded:: 3.14 ``EnumDict``
+.. versionadded:: 3.13 ``EnumDict``
---------------
@@ -830,13 +830,23 @@ Data Types
.. class:: EnumDict
- *EnumDict* is a subclass of :class:`dict` for use when subclassing :class:`EnumType`.
+ *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
- Return list of member names.
+ A list of member names.
- .. versionadded:: 3.14
+ .. versionadded:: 3.13
---------------
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index a291122..c8e0f94 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -879,11 +879,13 @@ email
(Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve
the :cve:`2023-27043` fix.)
+
enum
----
-* :class:`~enum.EnumDict` has been made public in :mod:`enum` to better support
- subclassing :class:`~enum.EnumType`.
+* :class:`~enum.EnumDict` has been made public to better support subclassing
+ :class:`~enum.EnumType`.
+
fractions
---------
diff --git a/Lib/enum.py b/Lib/enum.py
index ccc1da4..0444347 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -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:
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index b9e13fb..8884295 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -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):
diff --git a/Misc/NEWS.d/next/Library/2024-09-04-14-13-14.gh-issue-121720.z9hhXQ.rst b/Misc/NEWS.d/next/Library/2024-09-04-14-13-14.gh-issue-121720.z9hhXQ.rst
new file mode 100644
index 0000000..96da94a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-09-04-14-13-14.gh-issue-121720.z9hhXQ.rst
@@ -0,0 +1 @@
+:class:`enum.EnumDict` can now be used without resorting to private API.