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