import abc
import enum
import functools
import inspect
import struct
import types
import typing as t
import hikari.traits
from flare import exceptions
T = t.TypeVar("T")
__all__: t.Final[t.Sequence[str]] = (
"Converter",
"add_converter",
"StringConverter",
"IntConverter",
"EnumConverter",
)
[docs]
class Converter(abc.ABC, t.Generic[T]):
"""
Converters are used to convert types between a python object and string.
.. code-block:: python
import flare
import hikari
class IntConverter(flare.Converter[int]):
async def to_str(self, obj: int) -> str:
return str(obj)
async def from_str(self, obj: str) -> int:
return int(obj)
flare.add_converter(int, IntConverter)
@flare.button(label="Button", style=hikari.ButtonStyle.PRIMARY)
async def button(
ctx: flare.MessageContext,
# `IntConverter` will be used to serialize and deserialize this kwarg.
number: int,
):
...
Attributes:
type:
The type that is currently being serialized/deserialized. This will be
different than the generic type if a subclass of the generic type is being
serialized/deserialized.
"""
app: t.ClassVar[hikari.traits.EventManagerAware]
def __init__(self, type: T) -> None:
super().__init__()
self.type = type
@abc.abstractmethod
async def to_str(self, obj: T) -> str:
...
@abc.abstractmethod
async def from_str(self, obj: str) -> T:
...
_converters: dict[t.Any, tuple[type[Converter[t.Any]], bool]] = {}
[docs]
def add_converter(t: t.Any, converter: type[Converter[t.Any]], *, supports_subclass: bool = False) -> None:
"""
Set a converter to be used for a certain type hint and the subclasses of the
type hint.
Args:
t:
The type this converter supports.
converter: The converter object.
supports_subclass:
If `True`, this converter will be used for subclasses of `t`.
"""
_converters[t] = (converter, supports_subclass)
get_converter.cache_clear()
def _any_issubclass(t: t.Any, cls: t.Any) -> bool:
if not inspect.isclass(t):
return False
return issubclass(t, cls)
def _is_union(obj: t.Any) -> bool:
origin: t.Any = t.get_origin(obj)
return origin is types.UnionType or origin is t.Union
def _get_left(obj: t.Any) -> t.Any:
if not _is_union(obj):
return obj
return t.get_args(obj)[0]
@functools.lru_cache(maxsize=128)
def get_converter(type_: t.Any) -> Converter[t.Any]:
"""
Return the converter used for a certain type hint. If a Union is passed,
the left side of the Union will be used to find the converter.
"""
origin = _get_left(type_)
origin_: t.Any = t.get_origin(origin)
if origin_:
origin = origin_
if origin in _converters:
converter, _ = _converters[origin]
return converter(origin)
else:
for k, (converter, supports_subclass) in _converters.items():
if supports_subclass and _any_issubclass(origin, k):
return converter(origin)
raise exceptions.ConverterError(f"Could not find converter for type `{getattr(type_, '__name__', type_)}`.")
[docs]
class IntConverter(Converter[int]):
async def to_str(self, obj: int) -> str:
byte_length = obj.bit_length() // 8 + 1
return obj.to_bytes(byte_length, "little").decode("latin1")
async def from_str(self, obj: str) -> int:
return self.type.from_bytes(obj.encode("latin1"), "little")
class FloatConverter(Converter[float]):
async def to_str(self, obj: float) -> str:
return struct.pack("d", obj).decode("latin1")
async def from_str(self, obj: str) -> float:
return struct.unpack("d", obj.encode("latin1"))[0]
[docs]
class StringConverter(Converter[str]):
async def to_str(self, obj: str) -> str:
return obj
async def from_str(self, obj: str) -> str:
return obj
[docs]
class EnumConverter(Converter[enum.Enum]):
async def to_str(self, obj: enum.Enum) -> str:
return await get_converter(int).to_str(obj.value)
async def from_str(self, obj: str) -> enum.Enum:
return self.type(get_converter(int).from_str(obj)) # type: ignore
class BoolConverter(Converter[bool]):
async def to_str(self, obj: bool) -> str:
return "1" if obj else "0"
async def from_str(self, obj: str) -> bool:
return bool(int(obj))
add_converter(float, FloatConverter, supports_subclass=True)
add_converter(int, IntConverter, supports_subclass=True)
add_converter(str, StringConverter, supports_subclass=True)
add_converter(t.Literal, StringConverter)
add_converter(enum.Enum, EnumConverter, supports_subclass=True)
add_converter(bool, BoolConverter)
# MIT License
#
# Copyright (c) 2022-present Lunarmagpie
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.