1from __future__ import annotations
2
3import sys
4from enum import (
5 EnumMeta,
6 IntEnum,
7)
8from types import MappingProxyType
9from typing import (
10 TYPE_CHECKING,
11 Any,
12 Dict,
13 Optional,
14 Tuple,
15)
16
17
18if TYPE_CHECKING:
19 from collections.abc import (
20 Generator,
21 Mapping,
22 )
23
24 from typing_extensions import (
25 Never,
26 Self,
27 )
28
29
30def _is_descriptor(obj: object) -> bool:
31 return (
32 hasattr(obj, "__get__") or hasattr(obj, "__set__") or hasattr(obj, "__delete__")
33 )
34
35
36class EnumType(EnumMeta if TYPE_CHECKING else type):
37 _value_map_: Mapping[int, Enum]
38 _member_map_: Mapping[str, Enum]
39
40 def __new__(
41 mcs, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]
42 ) -> Self:
43 value_map = {}
44 member_map = {}
45
46 new_mcs = type(
47 f"{name}Type",
48 tuple(
49 dict.fromkeys(
50 [base.__class__ for base in bases if base.__class__ is not type]
51 + [EnumType, type]
52 )
53 ), # reorder the bases so EnumType and type are last to avoid conflicts
54 {"_value_map_": value_map, "_member_map_": member_map},
55 )
56
57 members = {
58 name: value
59 for name, value in namespace.items()
60 if not _is_descriptor(value) and not name.startswith("__")
61 }
62
63 cls = type.__new__(
64 new_mcs,
65 name,
66 bases,
67 {key: value for key, value in namespace.items() if key not in members},
68 )
69 # this allows us to disallow member access from other members as
70 # members become proper class variables
71
72 for name, value in members.items():
73 member = value_map.get(value)
74 if member is None:
75 member = cls.__new__(cls, name=name, value=value) # type: ignore
76 value_map[value] = member
77 member_map[name] = member
78 type.__setattr__(new_mcs, name, member)
79
80 return cls
81
82 if not TYPE_CHECKING:
83
84 def __call__(cls, value: int) -> Enum:
85 try:
86 return cls._value_map_[value]
87 except (KeyError, TypeError):
88 raise ValueError(f"{value!r} is not a valid {cls.__name__}") from None
89
90 def __iter__(cls) -> Generator[Enum, None, None]:
91 yield from cls._member_map_.values()
92
93 if sys.version_info >= (3, 8): # 3.8 added __reversed__ to dict_values
94
95 def __reversed__(cls) -> Generator[Enum, None, None]:
96 yield from reversed(cls._member_map_.values())
97
98 else:
99
100 def __reversed__(cls) -> Generator[Enum, None, None]:
101 yield from reversed(tuple(cls._member_map_.values()))
102
103 def __getitem__(cls, key: str) -> Enum:
104 return cls._member_map_[key]
105
106 @property
107 def __members__(cls) -> MappingProxyType[str, Enum]:
108 return MappingProxyType(cls._member_map_)
109
110 def __repr__(cls) -> str:
111 return f"<enum {cls.__name__!r}>"
112
113 def __len__(cls) -> int:
114 return len(cls._member_map_)
115
116 def __setattr__(cls, name: str, value: Any) -> Never:
117 raise AttributeError(f"{cls.__name__}: cannot reassign Enum members.")
118
119 def __delattr__(cls, name: str) -> Never:
120 raise AttributeError(f"{cls.__name__}: cannot delete Enum members.")
121
122 def __contains__(cls, member: object) -> bool:
123 return isinstance(member, cls) and member.name in cls._member_map_
124
125
126class Enum(IntEnum if TYPE_CHECKING else int, metaclass=EnumType):
127 """
128 The base class for protobuf enumerations, all generated enumerations will
129 inherit from this. Emulates `enum.IntEnum`.
130 """
131
132 name: Optional[str]
133 value: int
134
135 if not TYPE_CHECKING:
136
137 def __new__(cls, *, name: Optional[str], value: int) -> Self:
138 self = super().__new__(cls, value)
139 super().__setattr__(self, "name", name)
140 super().__setattr__(self, "value", value)
141 return self
142
143 def __str__(self) -> str:
144 return self.name or "None"
145
146 def __repr__(self) -> str:
147 return f"{self.__class__.__name__}.{self.name}"
148
149 def __setattr__(self, key: str, value: Any) -> Never:
150 raise AttributeError(
151 f"{self.__class__.__name__} Cannot reassign a member's attributes."
152 )
153
154 def __delattr__(self, item: Any) -> Never:
155 raise AttributeError(
156 f"{self.__class__.__name__} Cannot delete a member's attributes."
157 )
158
159 def __copy__(self) -> Self:
160 return self
161
162 def __deepcopy__(self, memo: Any) -> Self:
163 return self
164
165 @classmethod
166 def try_value(cls, value: int = 0) -> Self:
167 """Return the value which corresponds to the value.
168
169 Parameters
170 -----------
171 value: :class:`int`
172 The value of the enum member to get.
173
174 Returns
175 -------
176 :class:`Enum`
177 The corresponding member or a new instance of the enum if
178 ``value`` isn't actually a member.
179 """
180 try:
181 return cls._value_map_[value]
182 except (KeyError, TypeError):
183 return cls.__new__(cls, name=None, value=value)
184
185 @classmethod
186 def from_string(cls, name: str) -> Self:
187 """Return the value which corresponds to the string name.
188
189 Parameters
190 -----------
191 name: :class:`str`
192 The name of the enum member to get.
193
194 Raises
195 -------
196 :exc:`ValueError`
197 The member was not found in the Enum.
198 """
199 try:
200 return cls._member_map_[name]
201 except KeyError as e:
202 raise ValueError(f"Unknown value {name} for enum {cls.__name__}") from e