From b57c5d963933b7636f6e74de0f8d4309008cabe5 Mon Sep 17 00:00:00 2001 From: nikto_b Date: Wed, 16 Jul 2025 04:47:53 +0300 Subject: [PATCH] Create basic request/response abstract models --- src/turbosloth/__main__.py | 28 +++++--------------- src/turbosloth/app.py | 32 +++++++++++++--------- src/turbosloth/types.py | 54 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/turbosloth/__main__.py b/src/turbosloth/__main__.py index 30a4f70..7c3498b 100644 --- a/src/turbosloth/__main__.py +++ b/src/turbosloth/__main__.py @@ -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!') diff --git a/src/turbosloth/app.py b/src/turbosloth/app.py index a47ae75..82956ab 100644 --- a/src/turbosloth/app.py +++ b/src/turbosloth/app.py @@ -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): diff --git a/src/turbosloth/types.py b/src/turbosloth/types.py index 91212cc..4d728b8 100644 --- a/src/turbosloth/types.py +++ b/src/turbosloth/types.py @@ -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 + }