Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/enums.py: 58%

1 import enum

2 from types import DynamicClassAttribute

3

4 from plain.utils.functional import Promise

5

6 __all__ = ["Choices", "IntegerChoices", "TextChoices"]

7

8

9 class ChoicesMeta(enum.EnumMeta):

10 """A metaclass for creating a enum choices."""

11

12 def __new__(metacls, classname, bases, classdict, **kwds):

13 labels = []

14 for key in classdict._member_names:

15 value = classdict[key]

16 if (

17 isinstance(value, list | tuple)

18 and len(value) > 1

19 and isinstance(value[-1], Promise | str)

20 ):

21 *value, label = value

22 value = tuple(value)

23 else:

24 label = key.replace("_", " ").title()

25 labels.append(label)

26 # Use dict.__setitem__() to suppress defenses against double

27 # assignment in enum's classdict.

28 dict.__setitem__(classdict, key, value)

29 cls = super().__new__(metacls, classname, bases, classdict, **kwds)

30 for member, label in zip(cls.__members__.values(), labels):

31 member._label_ = label

32 return enum.unique(cls)

33

34 def __contains__(cls, member):

35 if not isinstance(member, enum.Enum):

36 # Allow non-enums to match against member values.

37 return any(x.value == member for x in cls)

38 return super().__contains__(member)

39

40 @property

41 def names(cls):

42 empty = ["__empty__"] if hasattr(cls, "__empty__") else []

43 return empty + [member.name for member in cls]

44

45 @property

46 def choices(cls):

47 empty = [(None, cls.__empty__)] if hasattr(cls, "__empty__") else []

48 return empty + [(member.value, member.label) for member in cls]

49

50 @property

51 def labels(cls):

52 return [label for _, label in cls.choices]

53

54 @property

55 def values(cls):

56 return [value for value, _ in cls.choices]

57

58

59 class Choices(enum.Enum, metaclass=ChoicesMeta):

60 """Class for creating enumerated choices."""

61

62 @DynamicClassAttribute

63 def label(self):

64 return self._label_

65

66 @property

67 def do_not_call_in_templates(self):

68 return True

69

70 def __str__(self):

71 """

72 Use value when cast to str, so that Choices set as model instance

73 attributes are rendered as expected in templates and similar contexts.

74 """

75 return str(self.value)

76

77 # A similar format was proposed for Python 3.10.

78 def __repr__(self):

79 return f"{self.__class__.__qualname__}.{self._name_}"

80

81

82 class IntegerChoices(int, Choices):

83 """Class for creating enumerated integer choices."""

84

85 pass

86

87

88 class TextChoices(str, Choices):

89 """Class for creating enumerated string choices."""

90

91 def _generate_next_value_(name, start, count, last_values):

92 return name