upload
This commit is contained in:
35
app/components/base.py
Normal file
35
app/components/base.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import MutableMapping
|
||||
from typing import Any, TypeGuard
|
||||
|
||||
from app.types_ import AnyScope, AsyncCallable, Receive, Send
|
||||
|
||||
|
||||
class Component[S: AnyScope, R: Any](metaclass=ABCMeta):
|
||||
def __init__(self, *args: Any, **kwds: Any) -> None:
|
||||
pass
|
||||
|
||||
async def condition(self, scope: AnyScope) -> TypeGuard[S]: # pyright: ignore[reportUnusedParameter]
|
||||
"""Determine whether the component should run."""
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
async def handle(self, scope: S, receive: Receive[R], send: Send) -> None:
|
||||
"""Component processor."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class RouteComponent[S: AnyScope, Recv_T: Any, Route_T: MutableMapping[str, Any], Route_R: Any](Component[S, Recv_T], metaclass=ABCMeta):
|
||||
routes: Route_T
|
||||
|
||||
def __init__(self, *args: Any, **kwds: Any) -> None:
|
||||
super().__init__(*args, **kwds)
|
||||
|
||||
@abstractmethod
|
||||
async def route_dispatch(self, scope: S, receive: Receive[Recv_T], send: Send) -> Any:
|
||||
"""Route dispatcher"""
|
||||
raise NotImplementedError
|
||||
|
||||
def route_install(self, type_: str, route: str, target: AsyncCallable[..., Route_R]) -> None:
|
||||
"""Install route target for specific type and route."""
|
||||
self.routes.setdefault(type_, {})[route] = target
|
||||
99
app/components/http.py
Normal file
99
app/components/http.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from typing import TypeGuard, override
|
||||
|
||||
from app.subroutines.http import Response, SimpleRequest, SimpleResponse
|
||||
from app.types_ import (
|
||||
AnyScope,
|
||||
AsyncCallable,
|
||||
HTTPScope,
|
||||
PassthroughDecorator,
|
||||
Receive,
|
||||
ReceiveHTTP,
|
||||
RouteMapping,
|
||||
Send,
|
||||
)
|
||||
|
||||
from .base import RouteComponent as _RouteComponent
|
||||
|
||||
|
||||
class HTTPComponent(
|
||||
_RouteComponent[HTTPScope, ReceiveHTTP, dict[str, RouteMapping[Response]], Response]
|
||||
):
|
||||
routes: dict[str, RouteMapping[Response]]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.routes = {}
|
||||
super().__init__()
|
||||
|
||||
@override
|
||||
async def condition(self, scope: AnyScope) -> TypeGuard[HTTPScope]:
|
||||
return scope["type"] == "http"
|
||||
|
||||
@override
|
||||
async def handle(
|
||||
self, scope: HTTPScope, receive: Receive[ReceiveHTTP], send: Send
|
||||
) -> None:
|
||||
req = SimpleRequest()
|
||||
while not req.receive(await receive()):
|
||||
pass
|
||||
|
||||
print(scope, req.body)
|
||||
resp = await self.route_dispatch(scope, receive, send)
|
||||
if resp is None:
|
||||
resp = Response(status=404, body=b"404 Not Found\n")
|
||||
|
||||
async with SimpleResponse(send).prepare(
|
||||
resp.status, headers=resp.headers
|
||||
) as rsp:
|
||||
await rsp.finish(resp.body if resp.body is not None else b"")
|
||||
|
||||
return None
|
||||
|
||||
@override
|
||||
async def route_dispatch(
|
||||
self, scope: HTTPScope, receive: Receive[ReceiveHTTP], send: Send
|
||||
) -> Response | None:
|
||||
for k, callee in self.routes[scope["method"].upper()].items():
|
||||
if scope["path"] == k: # temporary impl.
|
||||
return await callee()
|
||||
|
||||
def route[T: AsyncCallable[..., Response]](
|
||||
self,
|
||||
route: str,
|
||||
*,
|
||||
get: bool = False,
|
||||
post: bool = False,
|
||||
put: bool = False,
|
||||
delete: bool = False,
|
||||
) -> PassthroughDecorator[T]:
|
||||
def __wrap_route(fn: T) -> T:
|
||||
if get:
|
||||
self.route_install("GET", route, fn)
|
||||
if post:
|
||||
self.route_install("POST", route, fn)
|
||||
if put:
|
||||
self.route_install("PUT", route, fn)
|
||||
if delete:
|
||||
self.route_install("DELETE", route, fn)
|
||||
return fn
|
||||
|
||||
return __wrap_route
|
||||
|
||||
def get[T: AsyncCallable[..., Response]](
|
||||
self, route: str
|
||||
) -> PassthroughDecorator[T]:
|
||||
return self.route(route, get=True)
|
||||
|
||||
def post[T: AsyncCallable[..., Response]](
|
||||
self, route: str
|
||||
) -> PassthroughDecorator[T]:
|
||||
return self.route(route, post=True)
|
||||
|
||||
def put[T: AsyncCallable[..., Response]](
|
||||
self, route: str
|
||||
) -> PassthroughDecorator[T]:
|
||||
return self.route(route, put=True)
|
||||
|
||||
def delete[T: AsyncCallable[..., Response]](
|
||||
self, route: str
|
||||
) -> PassthroughDecorator[T]:
|
||||
return self.route(route, delete=True)
|
||||
25
app/components/lifespan.py
Normal file
25
app/components/lifespan.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from typing import TypeGuard, override
|
||||
|
||||
from app.types_ import AnyScope, LifespanScope, Receive, ReceiveLifespan, Send
|
||||
|
||||
from .base import Component as _Component
|
||||
|
||||
|
||||
class LifespanComponent(_Component[LifespanScope, ReceiveLifespan]):
|
||||
@override
|
||||
async def condition(self, scope: AnyScope) -> TypeGuard[LifespanScope]:
|
||||
return scope["type"] == "lifespan"
|
||||
|
||||
@override
|
||||
async def handle(self, scope: LifespanScope, receive: Receive[ReceiveLifespan], send: Send) -> None:
|
||||
while True:
|
||||
message = await receive()
|
||||
if message['type'] == 'lifespan.startup':
|
||||
... # Do some startup here!
|
||||
print("Startup...")
|
||||
await send({'type': 'lifespan.startup.complete'})
|
||||
elif message['type'] == 'lifespan.shutdown':
|
||||
... # Do some shutdown here!
|
||||
print("Shutdown...")
|
||||
await send({'type': 'lifespan.shutdown.complete'})
|
||||
return
|
||||
Reference in New Issue
Block a user