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 __future__ import annotations
|
||||||
|
|
||||||
|
from turbosloth import SlothApp
|
||||||
|
from turbosloth.types import Scope, Receive, Send
|
||||||
|
|
||||||
|
app = SlothApp()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
# Экземпляр глобального роутера
|
|
||||||
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", "/")
|
|
||||||
async def index(scope: Scope, receive: Receive, send: Send):
|
async def index(scope: Scope, receive: Receive, send: Send):
|
||||||
body = b"Hello, ASGI Router!"
|
body = b"Hello, ASGI Router!"
|
||||||
await send({
|
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):
|
async def get_user(scope: Scope, receive: Receive, send: Send):
|
||||||
text = f"User ID: ".encode("utf-8")
|
text = f"User ID: ".encode("utf-8")
|
||||||
await send({
|
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