Skip to content

xnano.requests

xnano.requests

xnano.requests


Declare HTTP routes on grids and return text, bytes, or JSON responses.

Classes:

  • Request

    Immutable parsed HTTP request.

  • Response

    HTTP response returned from a request hook.

  • RequestEvent

    Event payload carrying a parsed Request.

Functions:

Attributes:

HttpMethod module-attribute

HttpMethod: TypeAlias = Literal[
    "GET",
    "HEAD",
    "POST",
    "PUT",
    "DELETE",
    "CONNECT",
    "OPTIONS",
    "TRACE",
    "PATCH",
    "QUERY",
]

A standard HTTP request method.

RequestDecorator module-attribute

RequestDecorator: TypeAlias = (
    EventHookFunction
    | Callable[[EventHookFunction], EventHookFunction]
)

A request handler or a decorator that creates one.

Request dataclass

Request(
    method: str,
    path: str,
    query: Mapping[str, tuple[str, ...]] = dict(),
    headers: Mapping[str, str] = dict(),
    body: bytes = b"",
)

Immutable parsed HTTP request.

Attributes:

Example

request = Request.from_parts("GET", "/search", query_string="q=xnano") request.method, request.query["q"] ('GET', ('xnano',))

Methods:

  • text

    Decode body as text.

  • json

    Parse body as JSON via stdlib json.

  • from_parts

    Build a request from raw HTTP parts with body limits.

method instance-attribute

method: str

Uppercase HTTP method.

path instance-attribute

path: str

Normalized path beginning with /.

query class-attribute instance-attribute

query: Mapping[str, tuple[str, ...]] = dataclasses.field(
    default_factory=dict
)

Multi-value query parameters.

headers class-attribute instance-attribute

headers: Mapping[str, str] = dataclasses.field(
    default_factory=dict
)

Request headers with lowercase names.

body class-attribute instance-attribute

body: bytes = b''

Raw request body.

text

text(encoding: str = 'utf-8') -> str

Decode body as text.

Source code in xnano/requests.py
def text(self, encoding: str = "utf-8") -> str:
    """Decode ``body`` as text."""
    return self.body.decode(encoding)

json

json() -> Any

Parse body as JSON via stdlib json.

Source code in xnano/requests.py
def json(self) -> Any:
    """Parse ``body`` as JSON via stdlib ``json``."""
    if not self.body:
        return None
    return json.loads(self.body.decode("utf-8"))

from_parts classmethod

from_parts(
    method: str,
    path: str,
    *,
    query_string: str = "",
    headers: Mapping[str, str] | None = None,
    body: bytes = b"",
    max_body: int = 1048576
) -> "Request"

Build a request from raw HTTP parts with body limits.

Raises:

Source code in xnano/requests.py
@classmethod
def from_parts(
    cls,
    method: str,
    path: str,
    *,
    query_string: str = "",
    headers: Mapping[str, str] | None = None,
    body: bytes = b"",
    max_body: int = 1_048_576,
) -> "Request":
    """Build a request from raw HTTP parts with body limits.

    Raises:
        ValueError: If ``body`` exceeds ``max_body``.
    """
    if len(body) > max_body:
        raise ValueError(f"Request body exceeds limit of {max_body} bytes")
    cleaned = _normalize_request_path(path)
    parsed = urllib.parse.parse_qs(query_string, keep_blank_values=True)
    query = {key: tuple(values) for key, values in parsed.items()}
    header_map = {
        str(key).lower(): str(value)
        for key, value in (headers or {}).items()
    }
    return cls(
        method=method.upper(),
        path=cleaned,
        query=query,
        headers=header_map,
        body=body,
    )

Response dataclass

Response(
    body: bytes | str = b"",
    status: int = 200,
    headers: dict[str, str] = dict(),
)

HTTP response returned from a request hook.

Attributes:

Example

response = Response.json({"ready": True}, status=201) response.status 201

Methods:

  • as_bytes

    Return the body as bytes.

  • json

    Build a JSON response.

body class-attribute instance-attribute

body: bytes | str = b''

Response body.

status class-attribute instance-attribute

status: int = 200

HTTP status code.

headers class-attribute instance-attribute

headers: dict[str, str] = dataclasses.field(
    default_factory=dict
)

Response headers.

as_bytes

as_bytes() -> bytes

Return the body as bytes.

Source code in xnano/requests.py
def as_bytes(self) -> bytes:
    """Return the body as bytes."""
    if isinstance(self.body, bytes):
        return self.body
    return self.body.encode("utf-8")

json classmethod

json(
    data: Any,
    *,
    status: int = 200,
    headers: Mapping[str, str] | None = None
) -> "Response"

Build a JSON response.

Source code in xnano/requests.py
@classmethod
def json(
    cls,
    data: Any,
    *,
    status: int = 200,
    headers: Mapping[str, str] | None = None,
) -> "Response":
    """Build a JSON response."""
    payload = json.dumps(data).encode("utf-8")
    merged = {"content-type": "application/json; charset=utf-8"}
    if headers:
        merged.update({str(k).lower(): str(v) for k, v in headers.items()})
    return cls(body=payload, status=status, headers=merged)

RequestEvent dataclass

RequestEvent(request: Request, type: str = 'request')

Event payload carrying a parsed Request.

Attributes:

request instance-attribute

request: Request

Parsed HTTP request.

type class-attribute instance-attribute

type: str = 'request'

Payload category.

on_get_request

on_get_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP GET request.

Source code in xnano/requests.py
def on_get_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``GET`` request."""
    return _register_request_hook("GET", handler_or_path, path)

on_head_request

on_head_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP HEAD request.

Source code in xnano/requests.py
def on_head_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``HEAD`` request."""
    return _register_request_hook("HEAD", handler_or_path, path)

on_post_request

on_post_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP POST request.

Source code in xnano/requests.py
def on_post_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``POST`` request."""
    return _register_request_hook("POST", handler_or_path, path)

on_put_request

on_put_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP PUT request.

Source code in xnano/requests.py
def on_put_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``PUT`` request."""
    return _register_request_hook("PUT", handler_or_path, path)

on_delete_request

on_delete_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP DELETE request.

Source code in xnano/requests.py
def on_delete_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``DELETE`` request."""
    return _register_request_hook("DELETE", handler_or_path, path)

on_connect_request

on_connect_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP CONNECT request.

Source code in xnano/requests.py
def on_connect_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``CONNECT`` request."""
    return _register_request_hook("CONNECT", handler_or_path, path)

on_options_request

on_options_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP OPTIONS request.

Source code in xnano/requests.py
def on_options_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``OPTIONS`` request."""
    return _register_request_hook("OPTIONS", handler_or_path, path)

on_trace_request

on_trace_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP TRACE request.

Source code in xnano/requests.py
def on_trace_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``TRACE`` request."""
    return _register_request_hook("TRACE", handler_or_path, path)

on_patch_request

on_patch_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP PATCH request.

Source code in xnano/requests.py
def on_patch_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``PATCH`` request."""
    return _register_request_hook("PATCH", handler_or_path, path)

on_query_request

on_query_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator

Decorate a handler for an HTTP QUERY request.

Source code in xnano/requests.py
def on_query_request(
    handler_or_path: EventHookFunction | str | None = None,
    /,
    *,
    path: str | None = None,
) -> RequestDecorator:
    """Decorate a handler for an HTTP ``QUERY`` request."""
    return _register_request_hook("QUERY", handler_or_path, path)

has_request_hooks

has_request_hooks(grid_or_class: Any) -> bool

Return whether a grid (instance or class) declares any request hook.

Source code in xnano/requests.py
def has_request_hooks(grid_or_class: Any) -> bool:
    """Return whether a grid (instance or class) declares any request hook."""
    grid_class = (
        grid_or_class
        if isinstance(grid_or_class, type)
        else type(grid_or_class)
    )
    return bool(
        _RequestHooksRegistry.from_component_class(grid_class).all_hooks()
    )

collect_request_routes

collect_request_routes(
    grid_class: type,
) -> list[_OnRequestHookEntry]

Collect @on_*_request route entries declared on a grid class.

Source code in xnano/requests.py
def collect_request_routes(grid_class: type) -> list[_OnRequestHookEntry]:
    """Collect ``@on_*_request`` route entries declared on a grid class."""
    return _RequestHooksRegistry.from_component_class(grid_class).all_hooks()

request

request(
    method: str, path: str = "/"
) -> Callable[[EventHookFunction], EventHookFunction]

Register an HTTP request hook for an arbitrary method.

Parameters:

  • method (str) –

    HTTP method name.

  • path (str, default: '/' ) –

    URL path (normalized to a leading slash).

Returns:

  • Callable[[EventHookFunction], EventHookFunction]

    A decorator that marks the function as a request hook.

Source code in xnano/requests.py
def request(
    method: str,
    path: str = "/",
) -> Callable[[EventHookFunction], EventHookFunction]:
    """Register an HTTP request hook for an arbitrary method.

    Args:
        method: HTTP method name.
        path: URL path (normalized to a leading slash).

    Returns:
        A decorator that marks the function as a request hook.
    """
    method_upper = method.upper()
    factories: dict[str, Callable[..., Any]] = {
        "GET": on_get_request,
        "HEAD": on_head_request,
        "POST": on_post_request,
        "PUT": on_put_request,
        "DELETE": on_delete_request,
        "CONNECT": on_connect_request,
        "OPTIONS": on_options_request,
        "TRACE": on_trace_request,
        "PATCH": on_patch_request,
        "QUERY": on_query_request,
    }
    factory = factories.get(method_upper)
    if factory is None:
        raise ValueError(f"Unsupported HTTP method: {method!r}")
    return factory(path)

dispatch_request

dispatch_request(
    grid: Any,
    method: str,
    path: str,
    *,
    request_obj: Request | None = None,
    runtime: Any | None = None
) -> Response | bool

Run the request hook matching method and path.

Pass a runtime to make it available through the handler's Context. A handler that returns None produces an empty successful response.

Parameters:

  • grid (Any) –

    Grid instance declaring request hooks.

  • method (str) –

    HTTP method.

  • path (str) –

    Request path.

  • request_obj (Request | None, default: None ) –

    Optional parsed request for ctx.request.

  • runtime (Any | None, default: None ) –

    Optional runtime exposed through the hook context.

Returns:

  • Response | bool

    The handler response, or whether a route matched when no runtime was

  • Response | bool

    supplied.

Source code in xnano/requests.py
def dispatch_request(
    grid: Any,
    method: str,
    path: str,
    *,
    request_obj: Request | None = None,
    runtime: Any | None = None,
) -> Response | bool:
    """Run the request hook matching ``method`` and ``path``.

    Pass a runtime to make it available through the handler's ``Context``.
    A handler that returns ``None`` produces an empty successful response.

    Args:
        grid: Grid instance declaring request hooks.
        method: HTTP method.
        path: Request path.
        request_obj: Optional parsed request for ``ctx.request``.
        runtime: Optional runtime exposed through the hook context.

    Returns:
        The handler response, or whether a route matched when no runtime was
        supplied.
    """
    from xnano.context import Context
    from xnano.events import AbstractEventData, Event
    from xnano.utils.dispatch import invoke_hook

    method = method.upper()
    cleaned = _normalize_request_path(path)
    routes: Sequence[_OnRequestHookEntry] = collect_request_routes(type(grid))
    matched = False
    last_response: Response | None = None

    facade = runtime if runtime is not None else grid
    ctx = Context(
        event=Event.from_data(AbstractEventData()),
        terminal=facade,
        state=_resolve_context_state(runtime, grid),
        request=request_obj,
    )

    for entry in routes:
        if entry["method"] != method or entry["path"] != cleaned:
            continue
        name = getattr(entry["handler"], "__name__", "")
        handler = getattr(grid, name, None)
        if handler is None:
            continue
        result = invoke_hook(handler, grid, ctx)
        matched = True
        if isinstance(result, Response):
            last_response = result
        elif result is not None and not isinstance(result, bool):
            last_response = Response(body=str(result))

    if runtime is not None:
        if last_response is not None:
            return last_response
        if matched:
            return Response()
        return Response(status=404, body=b"Not Found")
    return matched