Create basic sloth app with basic static routing
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from .app import SlothApp
|
||||
|
||||
@@ -1,86 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from turbosloth import SlothApp
|
||||
from turbosloth.types import Scope, Receive, Send
|
||||
|
||||
app = SlothApp()
|
||||
|
||||
|
||||
|
||||
# Экземпляр глобального роутера
|
||||
router = Router()
|
||||
|
||||
|
||||
# Декоратор для удобства регистрации
|
||||
def route(method: MethodType, path_pattern: str):
|
||||
def decorator(fn: HandlerType):
|
||||
router.add(method, path_pattern, fn)
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# Сам ASGI-приложение
|
||||
async def core_app(scope: Scope, receive: Receive, send: Send):
|
||||
assert scope["type"] == "http", "Unsupported scope type"
|
||||
method = scope["method"]
|
||||
path = scope["path"]
|
||||
|
||||
try:
|
||||
handler, path_params = router.match(method, path)
|
||||
except KeyError:
|
||||
# 404
|
||||
await send({
|
||||
"type": "http.response.start",
|
||||
"status": 404,
|
||||
"headers": [(b"content-type", b"text/plain")],
|
||||
})
|
||||
await send({
|
||||
"type": "http.response.body",
|
||||
"body": b"Not Found",
|
||||
})
|
||||
return
|
||||
|
||||
# Вызов хендлера
|
||||
# Можно передавать path_params, query, body и т.д.
|
||||
await handler(scope, receive, send, **path_params)
|
||||
|
||||
|
||||
def lifespan(app):
|
||||
async def wrapper(scope, receive, send):
|
||||
if scope['type'] == 'lifespan':
|
||||
while True:
|
||||
event = await receive()
|
||||
if event['type'] == 'lifespan.startup':
|
||||
# Инициализация глобальных ресурсов
|
||||
await on_startup()
|
||||
await send({'type': 'lifespan.startup.complete'})
|
||||
elif event['type'] == 'lifespan.shutdown':
|
||||
# Очистка перед завершением
|
||||
await on_shutdown()
|
||||
await send({'type': 'lifespan.shutdown.complete'})
|
||||
break
|
||||
else:
|
||||
await app(scope, receive, send)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# Пример функций-обработчиков
|
||||
async def on_startup():
|
||||
print('Подключаем БД, кэш, внешние сервисы')
|
||||
# await db.connect()
|
||||
# await cache.initialize()
|
||||
|
||||
|
||||
async def on_shutdown():
|
||||
print('Отключаем БД, очищаем кэш')
|
||||
# await db.disconnect()
|
||||
# await cache.close()
|
||||
|
||||
|
||||
app = lifespan(core_app)
|
||||
|
||||
|
||||
# Пример регистрации роутов
|
||||
|
||||
@route("GET", "/")
|
||||
@app.get("/")
|
||||
async def index(scope: Scope, receive: Receive, send: Send):
|
||||
body = b"Hello, ASGI Router!"
|
||||
await send({
|
||||
@@ -94,7 +20,7 @@ async def index(scope: Scope, receive: Receive, send: Send):
|
||||
})
|
||||
|
||||
|
||||
@route("GET", "/user/")
|
||||
@app.get("/user/")
|
||||
async def get_user(scope: Scope, receive: Receive, send: Send):
|
||||
text = f"User ID: ".encode("utf-8")
|
||||
await send({
|
||||
|
||||
128
src/turbosloth/app.py
Normal file
128
src/turbosloth/app.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from typing import Optional, Callable, Awaitable, Protocol
|
||||
|
||||
from .router import Router
|
||||
from .types import Scope, Receive, Send, MethodType, HandlerType
|
||||
|
||||
|
||||
class ASGIApp(Protocol):
|
||||
router: Router
|
||||
|
||||
def route(self, method: MethodType, path_pattern: str):
|
||||
raise RuntimeError('stub!')
|
||||
|
||||
|
||||
class HTTPApp(ASGIApp):
|
||||
async def _do_http(self, scope: Scope, receive: Receive, send: Send):
|
||||
method = scope['method']
|
||||
path = scope['path']
|
||||
|
||||
try:
|
||||
handler = self.router.match(method, path)
|
||||
except KeyError:
|
||||
# 404
|
||||
await send({
|
||||
'type': 'http.response.start',
|
||||
'status': 404,
|
||||
'headers': [(b'content-type', b'text/plain')],
|
||||
})
|
||||
await send({
|
||||
'type': 'http.response.body',
|
||||
'body': b'Not Found',
|
||||
})
|
||||
return
|
||||
|
||||
await handler(scope, receive, send)
|
||||
|
||||
|
||||
class WSApp(ASGIApp):
|
||||
async def _do_websocket(self, scope: Scope, receive: Receive, send: Send):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class LifespanApp:
|
||||
_on_startup: Optional[Callable[[], Awaitable[None]]]
|
||||
_on_shutdown: Optional[Callable[[], Awaitable[None]]]
|
||||
|
||||
async def _do_startup(self, send: Send):
|
||||
if self._on_startup:
|
||||
try:
|
||||
await self._on_startup()
|
||||
await send({'type': 'lifespan.startup.complete'})
|
||||
except Exception as e:
|
||||
await send({'type': 'lifespan.startup.failed', 'message': str(e)})
|
||||
else:
|
||||
await send({'type': 'lifespan.startup.complete'})
|
||||
|
||||
async def _do_shutdown(self, send: Send):
|
||||
if self._on_shutdown:
|
||||
await self._on_shutdown()
|
||||
await send({'type': 'lifespan.shutdown.complete'})
|
||||
|
||||
async def _do_lifespan(self, receive: Receive, send: Send):
|
||||
while True:
|
||||
event = await receive()
|
||||
if event['type'] == 'lifespan.startup':
|
||||
await self._do_startup(send)
|
||||
elif event['type'] == 'lifespan.shutdown':
|
||||
await self._do_shutdown(send)
|
||||
break
|
||||
|
||||
|
||||
class MethodRoutersApp(ASGIApp):
|
||||
def get(self, path_pattern: str):
|
||||
return self.route('GET', path_pattern)
|
||||
|
||||
def post(self, path_pattern: str):
|
||||
return self.route('POST', path_pattern)
|
||||
|
||||
def push(self, path_pattern: str):
|
||||
return self.route('PUSH', path_pattern)
|
||||
|
||||
def put(self, path_pattern: str):
|
||||
return self.route('PUT', path_pattern)
|
||||
|
||||
def patch(self, path_pattern: str):
|
||||
return self.route('PATCH', path_pattern)
|
||||
|
||||
def delete(self, path_pattern: str):
|
||||
return self.route('DELETE', path_pattern)
|
||||
|
||||
def head(self, path_pattern: str):
|
||||
return self.route('HEAD', path_pattern)
|
||||
|
||||
def connect(self, path_pattern: str):
|
||||
return self.route('CONNECT', path_pattern)
|
||||
|
||||
def options(self, path_pattern: str):
|
||||
return self.route('OPTIONS', path_pattern)
|
||||
|
||||
def trace(self, path_pattern: str):
|
||||
return self.route('TRACE', path_pattern)
|
||||
|
||||
|
||||
class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
|
||||
|
||||
def __init__(self,
|
||||
on_startup: Optional[Callable[[], Awaitable[None]]] = None,
|
||||
on_shutdown: Optional[Callable[[], Awaitable[None]]] = None):
|
||||
self.router = Router()
|
||||
self._on_startup = on_startup
|
||||
self._on_shutdown = on_shutdown
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send):
|
||||
t = scope['type']
|
||||
if t == 'http':
|
||||
await self._do_http(scope, receive, send)
|
||||
elif t == 'lifespan':
|
||||
await self._do_lifespan(receive, send)
|
||||
elif t == 'websocket':
|
||||
await self._do_websocket(scope, receive, send)
|
||||
|
||||
raise RuntimeError(f'Unsupported scope type: {t}')
|
||||
|
||||
def route(self, method: MethodType, path_pattern: str):
|
||||
def decorator(fn: HandlerType):
|
||||
self.router.add(method, path_pattern, fn)
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
Reference in New Issue
Block a user