1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Modernized enumeration helpers on Python 3.11+.

- use @enum.property

https://docs.python.org/3/library/enum.html#enum.property

- use @enum.nonmember

Using @property on an enum class does not yield the expected result.
do_not_call_in_templates attribute works because a @property instance
is truthy. We can make this a literal True value as expected by using
@enum.nonmember in Python 3.11+.

https://docs.python.org/3/library/enum.html#enum.nonmember

- used enum.IntEnum/StrEnum

Python 3.11+ has ReprEnum which uses int.__str__() and str.__str__()
for __str__() in the `IntEnum` and `StrEnum` subclasses. We can emulate
that for Python < 3.11.

https://docs.python.org/3/library/enum.html#enum.ReprEnum
https://docs.python.org/3/library/enum.html#enum.IntEnum
https://docs.python.org/3/library/enum.html#enum.StrEnum
This commit is contained in:
Nick Pope 2023-08-23 10:58:17 +01:00 committed by Mariusz Felisiak
parent 170b0a47b0
commit fe19b33e2f
2 changed files with 37 additions and 16 deletions

View File

@ -1,15 +1,27 @@
import enum
import warnings
from types import DynamicClassAttribute
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.functional import Promise
from django.utils.version import PY311, PY312
if PY311:
from enum import EnumType
from enum import EnumType, IntEnum, StrEnum
from enum import property as enum_property
else:
from enum import EnumMeta as EnumType
from types import DynamicClassAttribute as enum_property
class ReprEnum(enum.Enum):
def __str__(self):
return str(self.value)
class IntEnum(int, ReprEnum):
pass
class StrEnum(str, ReprEnum):
pass
__all__ = ["Choices", "IntegerChoices", "TextChoices"]
@ -69,33 +81,30 @@ class ChoicesType(EnumType):
class Choices(enum.Enum, metaclass=ChoicesType):
"""Class for creating enumerated choices."""
@DynamicClassAttribute
if PY311:
do_not_call_in_templates = enum.nonmember(True)
else:
@property
def do_not_call_in_templates(self):
return True
@enum_property
def label(self):
return self._label_
@property
def do_not_call_in_templates(self):
return True
def __str__(self):
"""
Use value when cast to str, so that Choices set as model instance
attributes are rendered as expected in templates and similar contexts.
"""
return str(self.value)
# A similar format was proposed for Python 3.10.
def __repr__(self):
return f"{self.__class__.__qualname__}.{self._name_}"
class IntegerChoices(int, Choices):
class IntegerChoices(Choices, IntEnum):
"""Class for creating enumerated integer choices."""
pass
class TextChoices(str, Choices):
class TextChoices(Choices, StrEnum):
"""Class for creating enumerated string choices."""
@staticmethod

View File

@ -9,6 +9,7 @@ from django.test import SimpleTestCase
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.functional import Promise
from django.utils.translation import gettext_lazy as _
from django.utils.version import PY311
class Suit(models.IntegerChoices):
@ -187,6 +188,7 @@ class ChoicesTests(SimpleTestCase):
def test_do_not_call_in_templates_member(self):
# do_not_call_in_templates is not implicitly treated as a member.
Special = models.IntegerChoices("Special", "do_not_call_in_templates")
self.assertIn("do_not_call_in_templates", Special.__members__)
self.assertEqual(
Special.do_not_call_in_templates.label,
"Do Not Call In Templates",
@ -197,6 +199,16 @@ class ChoicesTests(SimpleTestCase):
"do_not_call_in_templates",
)
def test_do_not_call_in_templates_nonmember(self):
self.assertNotIn("do_not_call_in_templates", Suit.__members__)
if PY311:
self.assertIs(Suit.do_not_call_in_templates, True)
else:
# Using @property on an enum does not behave as expected.
self.assertTrue(Suit.do_not_call_in_templates)
self.assertIsNot(Suit.do_not_call_in_templates, True)
self.assertIsInstance(Suit.do_not_call_in_templates, property)
class Separator(bytes, models.Choices):
FS = b"\x1c", "File Separator"