Response System

Version: 2.0.0 Status: SOURCE OF TRUTH Last Updated: 2026-06-28


Overview

There is a single response class, Response, in response.py. There is no Starlette-style subclass hierarchy: content type is auto-detected from the result type (and from route metadata) by set_result(), not chosen by picking a subclass. The module exports exactly:

  • Response — the one response class (ASGI callable)

  • make_cookie() — helper that builds a Set-Cookie header tuple

__all__ = ["Response", "make_cookie"]

A Response is normally created by the request, configured with set_header() / set_result() / set_error(), and then awaited as an ASGI callable.


Response

class Response:
    __slots__ = ("body", "status_code", "_media_type", "_headers", "request")
    media_type: str | None = None
    charset: str = "utf-8"

    def __init__(
        self,
        content: bytes | str | None = None,
        status_code: int = 200,
        headers: Mapping[str, str] | list[tuple[str, str]] | None = None,
        media_type: str | None = None,
        request: Any = None,
    ) -> None:
        ...

Key points:

  • content accepts bytes, str, or None; strings are encoded with charset (utf-8). None becomes b"".

  • headers accepts a dict or a list of tuples (the list form is how multiple Set-Cookie headers are passed).

  • content-type (when a media type is set) and content-length are added automatically if absent.

  • __call__(scope, receive, send) sends http.response.start then http.response.body — a single body message (non-streaming).

set_result(result, metadata=None)

This is how handler return values become a response body. The content type is resolved by _guess_mime_type() (handler-set media_typemetadata["mime_type"] → for a Path, the file extension) with a type-based fallback:

result type

Body

Default media type

dict / list

JSON (orjson if available, else stdlib json)

application/json

dict / list, when request.tytx_mode

TYTX-encoded (to_tytx, transport from request.tytx_transport)

application/vnd.tytx+<transport>

Path

result.read_bytes()

application/octet-stream (or guessed)

bytes

as-is

application/octet-stream

str

utf-8 encoded

text/plain

None

b""

text/plain

other

str(result) utf-8

text/plain

After setting the body, content-type and content-length are recomputed.

set_error(error)

Maps an exception type to an HTTP status via ERROR_MAP, defaulting to 500 (and logging) for unknown types, then sets a JSON body {"error": "..."}:

ERROR_MAP = {
    "NotFound": 404,
    "NotAuthorized": 403,
    "ValueError": 400,
    "TypeError": 400,
    "PermissionError": 403,
    "FileNotFoundError": 404,
}

Headers

set_header(name, value) appends a header (multiple headers with the same name are allowed — e.g. several Set-Cookie). _build_headers() lowercases names and latin-1 encodes name/value for ASGI.



Notes

  • No JSONResponse / HTMLResponse / FileResponse / StreamingResponse classes exist. Returning a dict/list/str/bytes/Path from a handler is the supported way to set the body; set_result() picks the content type.

  • No streaming response class is currently implemented: __call__ sends one body message. Streaming is a roadmap item (see 07-streaming.md).

  • No aiofiles dependency: a Path result is read with read_bytes().


Copyright: Softwell S.r.l. (2025-2026) License: Apache License 2.0