Create basic request/response abstract models

This commit is contained in:
2025-07-16 04:47:53 +03:00
parent 0e53731ea4
commit b57c5d9639
3 changed files with 78 additions and 36 deletions

View File

@@ -2,34 +2,18 @@ from __future__ import annotations
from turbosloth import SlothApp
from turbosloth.exceptions import NotFoundException
from turbosloth.types import Scope, Receive, Send
from turbosloth.types import Scope, Receive, Send, BasicRequest, BasicResponse
app = SlothApp()
@app.get("/")
async def index(scope: Scope, receive: Receive, send: Send):
body = b"Hello, ASGI Router!"
await send({
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain")],
})
await send({
"type": "http.response.body",
"body": body,
})
async def index(req: BasicRequest) -> BasicResponse:
return BasicResponse(200, {}, b'Hello, ASGI Router!')
@app.get("/user/")
async def get_user(scope: Scope, receive: Receive, send: Send):
async def get_user(req: BasicRequest) -> BasicResponse:
text = f"User ID: ".encode("utf-8")
await send({
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain")],
})
await send({
"type": "http.response.body",
"body": text,
})
return BasicResponse(200, {}, b'Hello, User!')

View File

@@ -1,8 +1,9 @@
import json
from typing import Optional, Callable, Awaitable, Protocol
from .exceptions import HTTPException
from .router import Router
from .types import Scope, Receive, Send, MethodType, HandlerType
from .types import Scope, Receive, Send, MethodType, HandlerType, BasicRequest, BasicResponse
class ASGIApp(Protocol):
@@ -17,20 +18,27 @@ class HTTPApp(ASGIApp):
method = scope['method']
path = scope['path']
body = b''
while True:
event = await receive()
body += event.get('body', b'')
if not event.get('more_body', False):
break
scope['body'] = body
req = BasicRequest.from_scope(scope)
try:
handler = self.router.match(method, path)
await handler(scope, receive, send)
resp = await handler(req)
except HTTPException as e:
await send({
'type': 'http.response.start',
'status': e.code,
'headers': [(b'content-type', b'text/plain')],
})
await send({
'type': 'http.response.body',
'body': str(e).encode(),
})
return
resp = BasicResponse(e.code, {'content-type': 'text/plain'}, str(e).encode())
await send(resp.into_start_message())
await send({
'type': 'http.response.body',
'body': resp.body,
})
class WSApp(ASGIApp):

View File

@@ -1,9 +1,15 @@
from typing import Callable, Awaitable, Dict, Literal
from __future__ import annotations
import collections
from dataclasses import dataclass
from typing import Callable, Awaitable, Dict, Literal, Any, TypeVar
import urllib.parse
type Scope = Dict
type Receive = Callable[[], Awaitable[Dict]]
type Send = Callable[[Dict], Awaitable[None]]
type HandlerType = Callable[[Scope, Receive, Send], Awaitable[None]]
type HandlerType = Callable[[BasicRequest], Awaitable[BasicResponse]]
type MethodType = (
Literal['GET'] |
@@ -16,3 +22,47 @@ type MethodType = (
Literal['CONNECT'] |
Literal['OPTIONS'] |
Literal['TRACE'])
K = TypeVar('K')
V = TypeVar('V')
@dataclass
class BasicRequest:
method: str
path: str
headers: dict[str, str]
query: dict[str, list[str]]
body: dict[str, Any]
@classmethod
def from_scope(cls, scope: Scope) -> BasicRequest:
path = scope['path']
method = scope['method']
headers = {}
for key, value in scope.get('headers', []):
headers[key.decode('latin1')] = value.decode('latin1')
query = urllib.parse.parse_qs(scope['query_string'].decode('latin1'))
body = scope['body']
return BasicRequest(method, path, headers, query, body)
@dataclass
class BasicResponse:
code: int
headers: dict[str, str]
body: bytes
def into_start_message(self) -> dict:
enc_headers = []
for k, v in self.headers.items():
enc_headers.append((k.encode('latin1'), v.encode('latin1')))
return {
'type': 'http.response.start',
'status': self.code,
'headers': enc_headers
}