Skip to content

Commit

Permalink
Provide magic converters
Browse files Browse the repository at this point in the history
  • Loading branch information
devkral committed Oct 19, 2024
1 parent 1537453 commit 59ac908
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 3 deletions.
7 changes: 5 additions & 2 deletions esmerald/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from esmerald.config.openapi import OpenAPIConfig
from esmerald.config.static_files import StaticFilesConfig
from esmerald.contrib.schedulers.base import SchedulerConfig
from esmerald.datastructures import State
from esmerald.datastructures import MagicDict, MagicProperty, Secret, State
from esmerald.encoders import Encoder, MsgSpecEncoder, PydanticEncoder, register_esmerald_encoder
from esmerald.exception_handlers import (
improperly_configured_exception_handler,
Expand Down Expand Up @@ -68,7 +68,6 @@

if TYPE_CHECKING: # pragma: no cover
from esmerald.conf import EsmeraldLazySettings
from esmerald.datastructures import Secret
from esmerald.types import SettingsType, TemplateConfig

AppType = TypeVar("AppType", bound="Esmerald")
Expand Down Expand Up @@ -140,6 +139,10 @@ class Application(Lilya):
"encoders",
)

pluggables: MagicDict[Pluggable] = MagicProperty(
MagicDict[Pluggable], allowed_types=(Pluggable,), converter=Pluggable
)

def __init__(
self,
*,
Expand Down
6 changes: 6 additions & 0 deletions esmerald/datastructures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
Cookie,
FormData,
Header,
MagicDict,
MagicList,
MagicProperty,
QueryParam,
ResponseContainer,
ResponseHeader,
Expand All @@ -25,6 +28,9 @@
"FormData",
"Header",
"JSON",
"MagicList",
"MagicDict",
"MagicProperty",
"QueryParam",
"Redirect",
"ResponseContainer",
Expand Down
105 changes: 105 additions & 0 deletions esmerald/datastructures/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from esmerald.enums import MediaType

R = TypeVar("R", bound=LilyaResponse)
L = TypeVar("L")

if TYPE_CHECKING: # pragma: no cover
from esmerald.applications import Esmerald
Expand Down Expand Up @@ -130,3 +131,107 @@ class ResponseHeader(BaseModel):
def validate_value(cls, value: Any, values: Dict[str, Any]) -> Any:
if value is not None:
return value


class MagicList(list[L]):
"""Converts like magick"""

def __init__(
self,
iterable: Iterable[Any] = (),
*,
allowed_types: tuple[type, ...],
converter: Callable[[Any], L],
):
self.allowed_types = allowed_types
self.converter = converter
super().__init__(
entry if isinstance(entry, self.allowed_types) else self.converter(entry)
for entry in iterable
)

def append(self, value: Any) -> None:
if not isinstance(value, self.allowed_types):
value = self.converter(value)
super().append(value)

def insert(self, position: int, value: Any) -> None:
if not isinstance(value, self.allowed_types):
value = self.converter(value)
super().insert(position, value)

def __setitem__(self, position: int, value: Any) -> None:
if not isinstance(value, self.allowed_types):
value = self.converter(value)
super().__setitem__(position, value)

def extend(self, value: Iterable[Any]) -> None:
super().extend(
entry if isinstance(entry, self.allowed_types) else self.converter(entry)
for entry in value
)


class MagicDict(dict[str, L]):
"""Converts like magick"""

def __init__(
self,
inp: Any = None,
*,
allowed_types: tuple[type, ...],
converter: Callable[[Any], L],
):
self.allowed_types = allowed_types
self.converter = converter
super().__init__()
inp = dict(inp) if inp else {}
for k, v in inp.items():
self[k] = v

def __setitem__(self, name: str, value: Any) -> None:
if not isinstance(value, self.allowed_types):
value = self.converter(value)
super().__setitem__(name, value)

def update(self, value: Any) -> None:
value = dict(value)
for k, v in value.items():
self[k] = v


MagicP = TypeVar("MagicP", MagicList[L], MagicDict[L])


class MagicProperty(Generic[MagicP]):
def __init__(
self,
type_: MagicP,
allowed_types: tuple[type, ...],
converter: Callable[[Any], L],
):
self.type_ = type_
self.allowed_types = allowed_types
self.converter = converter

def __set_name__(self, owner: Any, name: str) -> None:
self.name = name

def __get__(self, instance: Any, owner: Any = None) -> MagicP:
if getattr(self.instance, self.name, None) is None:
setattr(
self.instance,
self.name,
self.type_(allowed_types=self.allowed_types, converter=self.converter),
)
return getattr(self.instance, self.name)

def __set__(self, instance: Any, value: Any) -> None:
setattr(
self.instance,
self.name,
self.type_(value, allowed_types=self.allowed_types, converter=self.converter),
)

def __delete__(self, instance: Any) -> None:
delattr(self.instance, self.name)
2 changes: 1 addition & 1 deletion esmerald/routing/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2897,7 +2897,7 @@ def resolve_route_path_handler(

for route in routes: # pragma: no cover
if not isinstance(route, (Include, Gateway, WebSocketGateway)):
raise ImproperlyConfigured("The route must be of type Gateway or Include")
route = Gateway(handler=route)

route.parent = self
if isinstance(route, Include):
Expand Down

0 comments on commit 59ac908

Please sign in to comment.