2026-04-07 18:48:27 +07:00
|
|
|
# pyright: reportAny=false
|
2020-12-31 17:11:21 +01:00
|
|
|
import enum
|
|
|
|
|
import typing as t
|
2021-01-01 23:46:43 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
from typing_extensions import Self, override
|
2023-08-03 09:21:30 +02:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
from .generic_wrapper import Construct, Adapter, Context, PathType
|
2020-12-31 17:11:21 +01:00
|
|
|
|
2021-01-01 13:55:19 +01:00
|
|
|
|
2021-01-02 12:01:20 +01:00
|
|
|
# ## TEnum ############################################################################################################
|
2022-12-24 12:00:23 +01:00
|
|
|
class EnumValue:
|
|
|
|
|
"""
|
|
|
|
|
This is a helper class for adding documentation to an enum value.
|
|
|
|
|
"""
|
|
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
def __init__(self, value: int, doc: str | None = None) -> None:
|
|
|
|
|
self.value: int = value
|
2022-12-24 12:00:23 +01:00
|
|
|
self.__doc__ = doc if doc else ""
|
|
|
|
|
|
|
|
|
|
|
2021-01-02 01:12:04 +01:00
|
|
|
class EnumBase(enum.IntEnum):
|
|
|
|
|
"""
|
|
|
|
|
Base class for an Enum used in `construct_typed.TEnum`.
|
2020-12-31 17:11:21 +01:00
|
|
|
|
2022-12-24 12:00:23 +01:00
|
|
|
This class extends the standard `enum.IntEnum` by.
|
|
|
|
|
- missing values are automatically generated
|
|
|
|
|
- possibility to add documentation for each enum value (see `EnumValue`)
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
|
|
>>> class State(EnumBase):
|
|
|
|
|
... Idle = 1
|
|
|
|
|
... Running = EnumValue(2, "This is the running state.")
|
|
|
|
|
|
|
|
|
|
>>> State(1)
|
|
|
|
|
<State.Idle: 1>
|
|
|
|
|
|
|
|
|
|
>>> State["Idle"]
|
|
|
|
|
<State.Idle: 1>
|
|
|
|
|
|
|
|
|
|
>>> State.Idle
|
|
|
|
|
<State.Idle: 1>
|
|
|
|
|
|
|
|
|
|
>>> State(3) # missing value
|
|
|
|
|
<State.3: 3>
|
|
|
|
|
|
|
|
|
|
>>> State.Running.__doc__ # documentation
|
|
|
|
|
'This is the running state.'
|
2021-01-02 01:12:04 +01:00
|
|
|
"""
|
2020-12-31 17:11:21 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
def __new__(cls, val: EnumValue | int) -> "Self":
|
2022-12-24 12:00:23 +01:00
|
|
|
if isinstance(val, EnumValue):
|
2023-01-09 08:58:11 +01:00
|
|
|
obj = int.__new__(cls, val.value)
|
|
|
|
|
obj._value_ = val.value
|
|
|
|
|
obj.__doc__ = val.__doc__
|
2022-12-24 12:00:23 +01:00
|
|
|
else:
|
2023-01-09 08:58:11 +01:00
|
|
|
obj = int.__new__(cls, val)
|
|
|
|
|
obj._value_ = val
|
|
|
|
|
obj.__doc__ = ""
|
|
|
|
|
return obj
|
2022-12-24 12:00:23 +01:00
|
|
|
|
2022-10-25 19:26:37 +02:00
|
|
|
# Extend the enum type with _missing_ method. So if a enum value
|
2021-01-02 01:12:04 +01:00
|
|
|
# not found in the enum, a new pseudo member is created.
|
|
|
|
|
# The idea is taken from: https://stackoverflow.com/a/57179436
|
|
|
|
|
@classmethod
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
|
|
|
|
def _missing_(cls, value: t.Any) -> enum.Enum | None:
|
2021-01-02 01:12:04 +01:00
|
|
|
if isinstance(value, int):
|
2022-10-25 19:26:37 +02:00
|
|
|
pseudo_member = cls._value2member_map_.get(value, None)
|
|
|
|
|
if pseudo_member is None:
|
|
|
|
|
new_member = int.__new__(cls, value)
|
|
|
|
|
# I expect a name attribute to hold a string, hence str(value)
|
|
|
|
|
# However, new_member._name_ = value works, too
|
|
|
|
|
new_member._name_ = str(value)
|
|
|
|
|
new_member._value_ = value
|
2022-12-24 12:00:23 +01:00
|
|
|
new_member.__doc__ = "missing value"
|
2022-10-25 19:26:37 +02:00
|
|
|
pseudo_member = cls._value2member_map_.setdefault(value, new_member)
|
|
|
|
|
return pseudo_member
|
2021-01-02 01:12:04 +01:00
|
|
|
return None # will raise the ValueError in Enum.__new__
|
2020-12-31 17:11:21 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
|
|
|
|
def __reduce_ex__(self, proto: t.Any) -> tuple[t.Any, ...]:
|
2023-05-09 10:46:36 +02:00
|
|
|
"""
|
|
|
|
|
Pickle enums by value instead of name (restores pre-3.11 behavior).
|
|
|
|
|
See https://github.com/python/cpython/pull/26658 for why this exists.
|
|
|
|
|
"""
|
|
|
|
|
return self.__class__, (self._value_,)
|
|
|
|
|
|
2021-03-24 20:15:23 +01:00
|
|
|
|
2021-01-02 01:12:04 +01:00
|
|
|
EnumType = t.TypeVar("EnumType", bound=EnumBase)
|
2020-12-31 17:11:21 +01:00
|
|
|
|
2021-03-24 20:15:23 +01:00
|
|
|
|
2021-01-02 01:12:04 +01:00
|
|
|
class TEnum(Adapter[int, int, EnumType, EnumType]):
|
|
|
|
|
"""
|
|
|
|
|
Typed enum.
|
|
|
|
|
"""
|
2026-04-07 18:48:27 +07:00
|
|
|
def __init__(self, subcon: Construct[int, int], enum_type: type[EnumType]):
|
2020-12-31 17:11:21 +01:00
|
|
|
# save enum type
|
2026-04-07 18:48:27 +07:00
|
|
|
self.enum_type: type[EnumType] = enum_type
|
2020-12-31 17:11:21 +01:00
|
|
|
|
|
|
|
|
# init adatper
|
2021-01-10 13:31:56 +01:00
|
|
|
super(TEnum, self).__init__(subcon) # type: ignore
|
2020-12-31 17:11:21 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
2021-01-02 01:12:04 +01:00
|
|
|
def _decode(self, obj: int, context: Context, path: PathType) -> EnumType:
|
2021-01-02 21:31:26 +01:00
|
|
|
return self.enum_type(obj)
|
2020-12-31 17:11:21 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
2020-12-31 17:11:21 +01:00
|
|
|
def _encode(
|
|
|
|
|
self,
|
2021-01-02 01:12:04 +01:00
|
|
|
obj: EnumType,
|
2021-01-02 12:01:20 +01:00
|
|
|
context: Context,
|
|
|
|
|
path: PathType,
|
|
|
|
|
) -> int:
|
|
|
|
|
if isinstance(obj, self.enum_type):
|
|
|
|
|
return int(obj)
|
2021-03-24 20:15:23 +01:00
|
|
|
raise TypeError(
|
|
|
|
|
"'{}' has to be of type {}".format(repr(obj), repr(self.enum_type))
|
|
|
|
|
)
|
2021-01-02 12:01:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ## TFlagsEnum #######################################################################################################
|
|
|
|
|
class FlagsEnumBase(enum.IntFlag):
|
2022-12-24 12:00:23 +01:00
|
|
|
"""
|
|
|
|
|
Base class for an Enum used in `construct_typed.TFlagsEnum`.
|
|
|
|
|
|
|
|
|
|
This class extends the standard `enum.IntFlag` by.
|
|
|
|
|
- possibility to add documentation for each enum value (see `EnumValue`)
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
|
|
>>> class Option(FlagsEnumBase):
|
|
|
|
|
... OptOne = 1
|
|
|
|
|
... OptTwo = EnumValue(2, "This is option two.")
|
|
|
|
|
|
|
|
|
|
>>> Option(1)
|
|
|
|
|
<Option.OptOne: 1>
|
|
|
|
|
|
|
|
|
|
>>> Option["OptOne"]
|
|
|
|
|
<Option.OptOne: 1>
|
|
|
|
|
|
|
|
|
|
>>> Option.OptOne
|
|
|
|
|
<Option.OptOne: 1>
|
|
|
|
|
|
|
|
|
|
>>> Option(3)
|
|
|
|
|
<Option.OptTwo|OptOne: 3>
|
|
|
|
|
|
|
|
|
|
>>> Option(4)
|
|
|
|
|
<Option.4: 4>
|
|
|
|
|
|
|
|
|
|
>>> Option.OptTwo.__doc__ # documentation
|
|
|
|
|
'This is option two.'
|
|
|
|
|
"""
|
|
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
def __new__(cls, val: EnumValue | int) -> "Self":
|
2022-12-24 12:00:23 +01:00
|
|
|
if isinstance(val, EnumValue):
|
2023-01-09 08:58:11 +01:00
|
|
|
obj = int.__new__(cls, val.value)
|
|
|
|
|
obj._value_ = val.value
|
|
|
|
|
obj.__doc__ = val.__doc__
|
2022-12-24 12:00:23 +01:00
|
|
|
else:
|
2023-01-09 08:58:11 +01:00
|
|
|
obj = int.__new__(cls, val)
|
|
|
|
|
obj._value_ = val
|
|
|
|
|
obj.__doc__ = ""
|
|
|
|
|
return obj
|
2022-12-24 12:00:23 +01:00
|
|
|
|
|
|
|
|
@classmethod
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
2022-12-24 12:00:23 +01:00
|
|
|
def _missing_(cls, value: t.Any) -> t.Any:
|
|
|
|
|
"""
|
|
|
|
|
Returns member (possibly creating it) if one can be found for value.
|
|
|
|
|
"""
|
|
|
|
|
new_member = super()._missing_(value)
|
|
|
|
|
new_member.__doc__ = "missing value"
|
|
|
|
|
return new_member
|
2021-01-02 12:01:20 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
|
|
|
|
def __reduce_ex__(self, proto: t.Any) -> tuple[t.Any, ...]:
|
2023-05-09 10:46:36 +02:00
|
|
|
"""
|
|
|
|
|
Pickle enums by value instead of name (restores pre-3.11 behavior).
|
|
|
|
|
See https://github.com/python/cpython/pull/26658 for why this exists.
|
|
|
|
|
"""
|
|
|
|
|
return self.__class__, (self._value_,)
|
|
|
|
|
|
2021-01-02 12:01:20 +01:00
|
|
|
|
|
|
|
|
FlagsEnumType = t.TypeVar("FlagsEnumType", bound=FlagsEnumBase)
|
|
|
|
|
|
2021-03-24 20:15:23 +01:00
|
|
|
|
2021-01-02 12:01:20 +01:00
|
|
|
class TFlagsEnum(Adapter[int, int, FlagsEnumType, FlagsEnumType]):
|
|
|
|
|
"""
|
|
|
|
|
Typed enum.
|
|
|
|
|
"""
|
2026-04-07 18:48:27 +07:00
|
|
|
def __init__(self, subcon: Construct[int, int], enum_type: type[FlagsEnumType]):
|
2021-01-02 12:01:20 +01:00
|
|
|
# save enum type
|
2026-04-07 18:48:27 +07:00
|
|
|
self.enum_type: type[FlagsEnumType] = enum_type
|
2021-01-02 12:01:20 +01:00
|
|
|
|
|
|
|
|
# init adatper
|
2021-01-10 13:31:56 +01:00
|
|
|
super(TFlagsEnum, self).__init__(subcon) # type: ignore
|
2021-01-02 12:01:20 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
2021-01-02 12:01:20 +01:00
|
|
|
def _decode(self, obj: int, context: Context, path: PathType) -> FlagsEnumType:
|
2021-01-02 21:31:26 +01:00
|
|
|
return self.enum_type(obj)
|
2021-01-02 12:01:20 +01:00
|
|
|
|
2026-04-07 18:48:27 +07:00
|
|
|
@override
|
2021-01-02 12:01:20 +01:00
|
|
|
def _encode(
|
|
|
|
|
self,
|
|
|
|
|
obj: FlagsEnumType,
|
|
|
|
|
context: Context,
|
|
|
|
|
path: PathType,
|
2020-12-31 17:11:21 +01:00
|
|
|
) -> int:
|
2021-01-02 01:12:04 +01:00
|
|
|
if isinstance(obj, self.enum_type):
|
|
|
|
|
return int(obj)
|
2021-03-24 20:15:23 +01:00
|
|
|
raise TypeError(
|
|
|
|
|
"'{}' has to be of type {}".format(repr(obj), repr(self.enum_type))
|
|
|
|
|
)
|