Skip to content

Commit f43f05f

Browse files
committed
fix enum typing
1 parent 8440d36 commit f43f05f

2 files changed

Lines changed: 63 additions & 49 deletions

File tree

fortnite_api/enums.py

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
import types
2929
from collections.abc import Iterator, Mapping
30-
from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, TypeVar
30+
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
3131

3232
from typing_extensions import Self
3333

@@ -53,32 +53,50 @@
5353

5454

5555
def _create_value_cls(name: str, comparable: bool) -> type[NewValue]:
56-
class _EnumValue(NamedTuple):
57-
# Denotes an internal marker used to create the value class. The definition
58-
# of this must be localized in this function because its methods
59-
# are changed multiple times at runtime. This is exposed outside of this
60-
# function as a type "NewValue", which denotes the type of the value class.
61-
name: str
62-
value: Any
63-
64-
cls = _EnumValue
65-
cls.__name__ = '_EnumValue_' + name
66-
cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>'
67-
cls.__str__ = lambda self: f'{name}.{self.name}'
68-
if comparable:
69-
cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value
70-
cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value
71-
cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value
72-
cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value
73-
74-
return cls
75-
76-
77-
def _is_descriptor(obj: type[object]) -> bool:
56+
# All the type ignores here are due to the type checker being unable to recognise
57+
# Runtime type creation without exploding.
58+
59+
class EnumValue:
60+
__slots__ = ("name", "value")
61+
62+
def __init__(self, name: str, value: EnumValue) -> None:
63+
self.name: str = name
64+
self.value: EnumValue = value
65+
66+
def __repr__(self) -> str:
67+
return f'<{name}.{self.name}: {self.value!r}>'
68+
69+
def __str__(self) -> str:
70+
return f'{name}.{self.name}'
71+
72+
if comparable:
73+
74+
def __le__(self, other: object) -> bool:
75+
return isinstance(other, self.__class__) and self.value <= other.value
76+
77+
def __ge__(self, other: object) -> bool:
78+
return isinstance(other, self.__class__) and self.value >= other.value
79+
80+
def __lt__(self, other: object) -> bool:
81+
return isinstance(other, self.__class__) and self.value < other.value
82+
83+
def __gt__(self, other: object) -> bool:
84+
return isinstance(other, self.__class__) and self.value > other.value
85+
86+
EnumValue.__name__ = '_EnumValue_' + name
87+
return EnumValue
88+
89+
90+
def _is_descriptor(obj: type[object]):
7891
return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__')
7992

8093

8194
class EnumMeta(type):
95+
if TYPE_CHECKING:
96+
_enum_member_names_: ClassVar[list[str]]
97+
_enum_member_map_: ClassVar[dict[str, Any]]
98+
_enum_value_map_: ClassVar[dict[Any, Any]]
99+
82100
def __new__(
83101
cls,
84102
name: str,
@@ -124,29 +142,29 @@ def __new__(
124142
value_cls._actual_enum_cls_ = actual_cls
125143
return actual_cls
126144

127-
def __iter__(cls: type[Enum]) -> Iterator[Any]:
145+
def __iter__(cls) -> Iterator[Any]:
128146
return (cls._enum_member_map_[name] for name in cls._enum_member_names_)
129147

130-
def __reversed__(cls: type[Enum]) -> Iterator[Any]:
148+
def __reversed__(cls) -> Iterator[Any]:
131149
return (cls._enum_member_map_[name] for name in reversed(cls._enum_member_names_))
132150

133-
def __len__(cls: type[Enum]) -> int:
151+
def __len__(cls) -> int:
134152
return len(cls._enum_member_names_)
135153

136154
def __repr__(cls) -> str:
137155
return f'<enum {cls.__name__}>'
138156

139157
@property
140-
def __members__(cls: type[Enum]) -> Mapping[str, Any]:
158+
def __members__(cls) -> Mapping[str, Any]:
141159
return types.MappingProxyType(cls._enum_member_map_)
142160

143-
def __call__(cls: type[Enum], value: str) -> Any:
161+
def __call__(cls, value: str) -> Any:
144162
try:
145163
return cls._enum_value_map_[value]
146164
except (KeyError, TypeError):
147-
raise ValueError(f"{value!r} is not a valid {cls.__name__}")
165+
raise ValueError(f'{value!r} is not a valid {cls.__name__}')
148166

149-
def __getitem__(cls: type[Enum], key: str) -> Any:
167+
def __getitem__(cls, key: str) -> Any:
150168
return cls._enum_member_map_[key]
151169

152170
def __setattr__(cls, name: str, value: Any) -> None:
@@ -164,21 +182,17 @@ def __instancecheck__(self, instance: Any) -> bool:
164182
return False
165183

166184

167-
class Enum(metaclass=EnumMeta):
168-
if TYPE_CHECKING:
169-
# Set in the metaclass when __new__ is called. The newly
170-
# created cls has these attributes set.
171-
_enum_member_names_: ClassVar[list[str]]
172-
_enum_member_map_: ClassVar[dict[str, NewValue]]
173-
_enum_value_map_: ClassVar[dict[OldValue, NewValue]]
174-
_enum_value_cls_: ClassVar[type[NewValue]]
185+
if TYPE_CHECKING:
186+
from enum import Enum
187+
else:
175188

176-
@classmethod
177-
def try_value(cls, value: Any) -> Any:
178-
try:
179-
return cls._enum_value_map_[value]
180-
except (KeyError, TypeError):
181-
return value
189+
class Enum(metaclass=EnumMeta):
190+
@classmethod
191+
def try_value(cls, value):
192+
try:
193+
return cls._enum_value_map_[value]
194+
except (KeyError, TypeError):
195+
return value
182196

183197

184198
class KeyFormat(Enum):
@@ -580,9 +594,9 @@ def _from_str(cls: type[Self], string: str) -> Self:
580594

581595

582596
def create_unknown_value(cls: type[E], val: Any) -> NewValue:
583-
value_cls = cls._enum_value_cls_
597+
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
584598
name = f'UNKNOWN_{val}'
585-
return value_cls(name=name, value=val)
599+
return value_cls(name=name, value=val) # type: ignore
586600

587601

588602
def try_enum(cls: type[E], val: Any) -> E:
@@ -591,6 +605,6 @@ def try_enum(cls: type[E], val: Any) -> E:
591605
If it fails it returns a proxy invalid value instead.
592606
"""
593607
try:
594-
return cls._enum_value_map_[val]
608+
return cls._enum_value_map_[val] # type: ignore # All errors are caught below
595609
except (KeyError, TypeError, AttributeError):
596610
return create_unknown_value(cls, val)

tests/test_enum.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def test_dummy_enum():
4747

4848
# Test immutability
4949
with pytest.raises(TypeError):
50-
DummyEnum.FOO = "new"
50+
DummyEnum.FOO = "new" # type: ignore # This should raise an error
5151
with pytest.raises(TypeError):
52-
del DummyEnum.FOO
52+
del DummyEnum.FOO # type: ignore # This should raise an error
5353

5454
# Test try_enum functionality
5555
valid_value = "foo"

0 commit comments

Comments
 (0)