]> granicus.if.org Git - python/commitdiff
issue26981: add _order_ compatibility shim to enum.Enum
authorEthan Furman <ethan@stoneleaf.us>
Sat, 20 Aug 2016 14:19:31 +0000 (07:19 -0700)
committerEthan Furman <ethan@stoneleaf.us>
Sat, 20 Aug 2016 14:19:31 +0000 (07:19 -0700)
Doc/library/enum.rst
Lib/enum.py
Lib/test/test_enum.py
Misc/NEWS

index 2111d1c04e3aa416e7653e0c5818dd268c9734cc..827bab0c90c9ce4d516b19f507d516564be6ada4 100644 (file)
@@ -257,7 +257,7 @@ members are not integers (but see `IntEnum`_ below)::
     >>> Color.red < Color.blue
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
-    TypeError: unorderable types: Color() < Color()
+    TypeError: '<' not supported between instances of 'Color' and 'Color'
 
 Equality comparisons are defined though::
 
@@ -776,3 +776,22 @@ appropriately.
 If you wish to change how :class:`Enum` members are looked up you should either
 write a helper function or a :func:`classmethod` for the :class:`Enum`
 subclass.
+
+To help keep Python 2 / Python 3 code in sync a user-specified :attr:`_order_`,
+if provided, will be checked to ensure the actual order of the enumeration
+matches::
+
+    >>> class Color(Enum):
+    ...     _order_ = 'red green blue'
+    ...     red = 1
+    ...     blue = 3
+    ...     green = 2
+    ...
+    Traceback (most recent call last):
+    ...
+    TypeError: member order does not match _order_
+
+.. note::
+
+    In Python 2 code the :attr:`_order_` attribute is necessary as definition
+    order is lost during class creation.
index 99db9e6b7f5a60d0d8a2ac792e81cf8ae5e3c500..e7889a8dc77113289ed0917da988bdac54c59186 100644 (file)
@@ -64,9 +64,11 @@ class _EnumDict(dict):
 
         """
         if _is_sunder(key):
-            raise ValueError('_names_ are reserved for future Enum use')
+            if key not in ('_order_', ):
+                raise ValueError('_names_ are reserved for future Enum use')
         elif _is_dunder(key):
-            pass
+            if key == '__order__':
+                key = '_order_'
         elif key in self._member_names:
             # descriptor overwriting an enum?
             raise TypeError('Attempted to reuse key: %r' % key)
@@ -106,6 +108,9 @@ class EnumMeta(type):
         for name in classdict._member_names:
             del classdict[name]
 
+        # adjust the sunders
+        _order_ = classdict.pop('_order_', None)
+
         # check for illegal enum names (any others?)
         invalid_names = set(members) & {'mro', }
         if invalid_names:
@@ -210,6 +215,14 @@ class EnumMeta(type):
             if save_new:
                 enum_class.__new_member__ = __new__
             enum_class.__new__ = Enum.__new__
+
+        # py3 support for definition order (helps keep py2/py3 code in sync)
+        if _order_ is not None:
+            if isinstance(_order_, str):
+                _order_ = _order_.replace(',', ' ').split()
+            if _order_ != enum_class._member_names_:
+                raise TypeError('member order does not match _order_')
+
         return enum_class
 
     def __bool__(self):
index 564c0e9f7da7a6f1d240c6d7cf667c6792e9c768..2d4519e9ed4fc6971a403c3f0ade0c507dcddeb0 100644 (file)
@@ -1571,6 +1571,68 @@ class TestEnum(unittest.TestCase):
         self.assertEqual(LabelledList(1), LabelledList.unprocessed)
 
 
+class TestOrder(unittest.TestCase):
+
+    def test_same_members(self):
+        class Color(Enum):
+            _order_ = 'red green blue'
+            red = 1
+            green = 2
+            blue = 3
+
+    def test_same_members_with_aliases(self):
+        class Color(Enum):
+            _order_ = 'red green blue'
+            red = 1
+            green = 2
+            blue = 3
+            verde = green
+
+    def test_same_members_wrong_order(self):
+        with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
+            class Color(Enum):
+                _order_ = 'red green blue'
+                red = 1
+                blue = 3
+                green = 2
+
+    def test_order_has_extra_members(self):
+        with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
+            class Color(Enum):
+                _order_ = 'red green blue purple'
+                red = 1
+                green = 2
+                blue = 3
+
+    def test_order_has_extra_members_with_aliases(self):
+        with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
+            class Color(Enum):
+                _order_ = 'red green blue purple'
+                red = 1
+                green = 2
+                blue = 3
+                verde = green
+
+    def test_enum_has_extra_members(self):
+        with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
+            class Color(Enum):
+                _order_ = 'red green blue'
+                red = 1
+                green = 2
+                blue = 3
+                purple = 4
+
+    def test_enum_has_extra_members_with_aliases(self):
+        with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
+            class Color(Enum):
+                _order_ = 'red green blue'
+                red = 1
+                green = 2
+                blue = 3
+                purple = 4
+                verde = green
+
+
 class TestUnique(unittest.TestCase):
 
     def test_unique_clean(self):
index 292519fbac0815ba9346d5b78d2196038cfbb3bc..6c871cc0da3252347e361e00050e4fd453728372 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -137,6 +137,9 @@ Library
 - Issue #26800: Undocumented support of general bytes-like objects
   as paths in os functions is now deprecated.
 
+- Issue #26981: Add _order_ compatibility ship to enum.Enum for
+  Python 2/3 code bases.
+
 - Issue #27661: Added tzinfo keyword argument to datetime.combine.
 
 - In the curses module, raise an error if window.getstr() or window.instr() is