Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-91456: Fix issue affecting the use of auto() alongside aliases in Enums #91457

Merged
merged 7 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/library/enum.rst
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,10 @@ Utilities and Decorators
``_generate_next_value_`` can be overridden to customize the values used by
*auto*.

.. note:: in 3.13 the default ``"generate_next_value_`` will always return
the highest member value incremented by 1, and will fail if any
member is an incompatible type.

.. decorator:: property

A decorator similar to the built-in *property*, but specifically for
Expand Down
34 changes: 26 additions & 8 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -1205,21 +1205,39 @@ def __new__(cls, value):
def __init__(self, *args, **kwds):
pass

def _generate_next_value_(name, start, count, last_values):
def _generate_next_value_(name, start, count, last_value):
"""
Generate the next value when not given.

name: the name of the member
start: the initial start value or None
count: the number of existing members
last_value: the last value assigned or None
last_value: the list of values assigned
"""
for last_value in reversed(last_values):
try:
return last_value + 1
except TypeError:
pass
else:
if not last_value:
return start
try:
last = last_value[-1]
last_value.sort()
if last == last_value[-1]:
# no difference between old and new methods
return last + 1
else:
# trigger old method (with warning)
raise TypeError
except TypeError:
import warnings
warnings.warn(
"In 3.13 the default `auto()`/`_generate_next_value_` will require all values to be sortable and support adding +1\n"
"and the value returned will be the largest value in the enum incremented by 1",
DeprecationWarning,
stacklevel=3,
)
for v in last_value:
try:
return v + 1
except TypeError:
pass
return start

@classmethod
Expand Down
65 changes: 56 additions & 9 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -3953,23 +3953,54 @@ class Color(AutoNameEnum):
self.assertEqual(Color.blue.value, 'blue')
self.assertEqual(Color.green.value, 'green')

def test_auto_garbage(self):
class Color(Enum):
red = 'red'
blue = auto()
@unittest.skipIf(
python_version >= (3, 13),
'mixed types with auto() no longer supported',
)
def test_auto_garbage_ok(self):
with self.assertWarnsRegex(DeprecationWarning, 'will require all values to be sortable'):
class Color(Enum):
red = 'red'
blue = auto()
self.assertEqual(Color.blue.value, 1)

def test_auto_garbage_corrected(self):
class Color(Enum):
red = 'red'
blue = 2
green = auto()
@unittest.skipIf(
python_version >= (3, 13),
'mixed types with auto() no longer supported',
)
def test_auto_garbage_corrected_ok(self):
with self.assertWarnsRegex(DeprecationWarning, 'will require all values to be sortable'):
class Color(Enum):
red = 'red'
blue = 2
green = auto()

self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])
self.assertEqual(Color.red.value, 'red')
self.assertEqual(Color.blue.value, 2)
self.assertEqual(Color.green.value, 3)

@unittest.skipIf(
python_version < (3, 13),
'mixed types with auto() will raise in 3.13',
)
def test_auto_garbage_fail(self):
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
class Color(Enum):
red = 'red'
blue = auto()

@unittest.skipIf(
python_version < (3, 13),
'mixed types with auto() will raise in 3.13',
)
def test_auto_garbage_corrected_fail(self):
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
class Color(Enum):
red = 'red'
blue = 2
green = auto()

def test_auto_order(self):
with self.assertRaises(TypeError):
class Color(Enum):
Expand All @@ -3991,6 +4022,22 @@ def _generate_next_value_(name, start, count, last):
self.assertEqual(Color.red.value, 'pathological case')
self.assertEqual(Color.blue.value, 'blue')

@unittest.skipIf(
python_version < (3, 13),
'auto() will return highest value + 1 in 3.13',
)
def test_auto_with_aliases(self):
class Color(Enum):
red = auto()
blue = auto()
oxford = blue
crimson = red
green = auto()
self.assertIs(Color.crimson, Color.red)
self.assertIs(Color.oxford, Color.blue)
self.assertIsNot(Color.green, Color.red)
self.assertIsNot(Color.green, Color.blue)

def test_duplicate_auto(self):
class Dupes(Enum):
first = primero = auto()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Deprecate current default auto() behavior: In 3.13 the default will be for
for auto() to always return the largest member value incremented by
1, and to raise if incompatible value types are used.