Request

HTTP request handling and utilities.

Transport-agnostic request system.

Classes:
BaseRequest (ABC) — transport-agnostic interface

├── HttpRequest — ASGI HTTP (stream-based, body via receive) └── MsgRequest — WSX messages (atomic, WebSocket/NATS)

RequestRegistry — factory and tracking for active requests

Every request has an associated Response (request.response). The handler returns a value; the app’s handle_request calls response.set_result() and sends the ASGI response (the server’s Dispatcher only demultiplexes on the first path segment).

Request/Response lifecycle (inside AsgiApplication.handle_request):

RequestRegistry.create(scope, receive, send)
    → factory()        # sync: allocate slots
    → request.init()   # async: read body, parse data
    → register in registry
set_current_request(request)
router.node(path) → handler
result = handler(**request.handler_kwargs())  # query + body (body_json/body_raw)
response.set_result(result)
response(scope, receive, send)
set_current_request(None)
registry.unregister()
Notification pattern (fire-and-forget over HTTP):

Some protocols (e.g., JSON-RPC 2.0) define messages that expect no response payload. The handler sets the status code and returns None; the app sends an empty HTTP response:

request = get_current_request()
request.response.status_code = 202
return None  # the app sends 202 with empty body
class genro_asgi.request.BaseRequest[source]

Bases: ABC

Abstract base for transport-agnostic requests.

Subclasses: HttpRequest (ASGI HTTP), MsgRequest (WSX messages). Handlers see only BaseRequest and work identically across transports.

Every instance carries an associated response (Response object). The handler controls the response via request.response: status code, headers, and — indirectly — body (the Dispatcher calls response.set_result() with whatever the handler returns).

For notification/fire-and-forget over HTTP the handler sets request.response.status_code = 202 and returns None.

Abstract properties (subclasses must implement):

id, method, path, headers, cookies, query, data, transport

Concrete attributes:

response: Associated Response object (created in __init__) auth_tags: Security tags injected by AuthMiddleware via scope env_capabilities: Environment flags injected via scope external_id: Client-provided correlation ID (optional) tytx_mode: True when request uses TYTX serialization tytx_transport: TYTX transport type (‘json’, ‘msgpack’) or None app_name: App handling this request (set after routing, for metrics) created_at: Timestamp (epoch) when request was created age: Seconds since creation (computed)

add_cleanup(callback)[source]

Register a callback to run at end of request (always, even on error).

Parameters:

callback (Any) – Callable to invoke. Called in reverse order of registration.

Return type:

None

add_onerr(callback)[source]

Register a callback to run only when the request ends with an error.

Parameters:

callback (Any) – Callable to invoke with the exception. Called in reverse order.

Return type:

None

property age: float

Seconds since request was created.

property app_name: str | None

Name of the app handling this request (set after routing).

property auth_tags: list[str]

Auth tags (set from scope during init by AuthMiddleware).

abstract property cookies: dict[str, str]

Request cookies.

property created_at: float

Timestamp when request was created.

property ctx: Any

Execution context (DictObj set by Dispatcher in scope[“ctx”]).

abstract property data: Any

Request body/payload.

property env_capabilities: list[str]

Environment capabilities (set from scope during init).

property external_id: str | None

Client-provided ID for correlation (e.g., WSX message id).

handler_kwargs()[source]

Build the keyword arguments a route handler is called with.

Query params are the base; how the body adds to them follows the content-type, not the Python shape of the decoded value:

  • application/x-www-form-urlencoded is a set of key/value pairs, the same shape as the query, so its fields are decoded (typed, via TYTX from_qs) and merged in — the body wins on a name clash;

  • a hydrated body (JSON/XML/msgpack, decoded by TYTX) is passed whole as body_data — a structured payload, one argument, never unpacked into parameters (so a model-typed handler binds it cleanly);

  • any other body (opaque bytes) is passed as body_raw;

  • an empty body adds neither key.

Only the form merges — it alone is parameter-shaped. The router drops whichever key the handler does not declare.

Return type:

dict[str, Any]

abstract property headers: dict[str, str]

Request headers (lowercase keys).

abstract property id: str

Correlation ID for request/response matching.

abstractmethod async init(scope, receive, send=None, **kwargs)[source]

Async initialization — parse transport data from ASGI scope.

Subclasses must override to read body/message, populate internal state (headers, cookies, query, data), and extract auth context.

Parameters:
Return type:

None

abstract property method: str

GET, POST, PUT, DELETE, PATCH.

Type:

HTTP method

abstract property path: str

Request path (e.g., ‘/users/42’).

abstract property query: dict[str, Any]

Query parameters.

response: Response
run_cleanups(error=None)[source]

Run registered callbacks. Called by Dispatcher in finally block.

Parameters:

error (BaseException | None) – The exception if request failed, None on success.

Return type:

None

abstract property transport: str

‘http’, ‘websocket’, ‘nats’.

Type:

Transport type

property tytx_mode: bool

True if request uses TYTX serialization.

property tytx_transport: str | None

TYTX transport type (‘json’, ‘msgpack’) or None.

class genro_asgi.request.HttpRequest[source]

Bases: ScopeConnectionMixin, BaseRequest

HTTP request adapter — wraps ASGI scope, parses body via asgi_data.

Supports both TYTX-encoded requests (type hydration for Decimal, date, datetime, time) and standard HTTP requests with plain JSON bodies.

TYTX mode is detected from the X-TYTX-Transport header. When present, Response uses the same transport to serialize the reply. Body parsing is handled by genro_tytx.asgi_data() in both cases (after genro-tytx 0.8.0, plain JSON is parsed correctly without TYTX markers).

Parsing flow (in init()):
  1. Headers decoded from ASGI scope (latin-1)

  2. TYTX mode detected from X-TYTX-Transport header

  3. asgi_data(scope, receive) parses headers, query, cookies, body

  4. Request ID extracted from x-request-id header or generated (UUID)

  5. Auth context read from scope (injected by AuthMiddleware)

Body availability:

asgi_data consumes the ASGI receive callable and never discards the payload: the decoded value (or the raw bytes, for content types it cannot hydrate) is available as self.data. Only the body property returns b"" (the raw stream is not kept separately).

Extra properties (beyond BaseRequest):

scope, body, scheme, url, headers_obj, query_params, client, state, content_type

property body: bytes

Raw body bytes.

property content_type: str | None

Content-Type header value.

property cookies: dict[str, str]

Request cookies.

property data: Any

Request body/payload.

property db: Any

Default db handler, lazy-loaded from scope["ctx"]._db.

On first access, reads ctx._db (the handler set by the app) and registers db.closeConnection as a request cleanup callback.

Returns:

The db handler, or None if ctx is absent or has no _db.

get_db(name)[source]

Return a db handler by name, resolved through the app.

Delegates to ctx.app.get_db(name) (app registry first, then server). Returns None if there is no context app.

Return type:

Any

property headers: dict[str, str]

Request headers (lowercase keys).

property headers_obj: Headers

Request headers as Headers object (case-insensitive).

property id: str

Correlation ID for request/response matching.

async init(scope, receive, send=None, **kwargs)[source]

Async init — parse headers, body, query, cookies via asgi_data.

Delegates to genro_tytx.asgi_data(scope, receive) which handles TYTX-encoded and plain JSON bodies transparently (since genro-tytx 0.8.0). After this method:

  • self._data: parsed body (hydrated value for JSON/XML/msgpack, raw bytes for content types TYTX cannot hydrate, None if empty)

  • self._headers, self._cookies, self._query: populated

  • self._tytx_mode: True if X-TYTX-Transport header was present

  • self._auth_tags, self._env_capabilities: from scope

  • self._body: b”” (the body lives in self._data — see class docstring)

Return type:

None

property method: str

GET, POST, PUT, DELETE, PATCH.

Type:

HTTP method

property path: str

Request path (e.g., ‘/users/42’).

property query: dict[str, Any]

Query parameters.

property scope: MutableMapping[str, Any]

Raw ASGI scope dict.

property server: Any

Parent AsgiServer instance.

property session: Any

Session object (set by SessionMiddleware via scope).

property transport: str

‘http’, ‘websocket’, ‘nats’.

Type:

Transport type

class genro_asgi.request.MsgRequest[source]

Bases: BaseRequest

Message-based request adapter (WSX over WebSocket, NATS, etc.).

Parses WSX:// formatted messages into BaseRequest interface. Transport-agnostic: works with any message-based protocol.

property client: tuple[str, int] | None

Client address as raw (host, port) tuple from WebSocket scope.

Unlike HttpRequest.client (which wraps in Address), WSX messages return the raw tuple because WebSocket connections are long-lived and the overhead of wrapping each message is unnecessary.

property cookies: dict[str, str]

Request cookies.

property data: Any

Request body/payload.

property headers: dict[str, str]

Request headers (lowercase keys).

property id: str

Correlation ID for request/response matching.

async init(scope, receive, send=None, **kwargs)[source]

Async initialization - parses WSX message.

Return type:

None

property method: str

GET, POST, PUT, DELETE, PATCH.

Type:

HTTP method

property path: str

Request path (e.g., ‘/users/42’).

property query: dict[str, Any]

Query parameters.

property scope: MutableMapping[str, Any]

Access to raw ASGI scope.

property transport: str

‘http’, ‘websocket’, ‘nats’.

Type:

Transport type

property websocket: WebSocket | None

Access to underlying WebSocket connection (if available).

class genro_asgi.request.RequestRegistry(factories=None)[source]

Bases: object

Registry for creating and tracking active requests.

Responsibilities: - Creates appropriate request based on scope[“type”] using factories dict - Calls async init() on the created request - Tracks active requests for monitoring and metrics - Provides iteration and lookup by request ID

Example

registry = RequestRegistry() request = await registry.create(scope, receive, send) print(f”Active: {len(registry)}”) registry.unregister()

count_by_app(app_name)[source]

Count active requests for a specific app.

Return type:

int

async create(scope, receive, send=None, **kwargs)[source]

Create and register a request from ASGI scope.

Return type:

BaseRequest

property current: BaseRequest | None

Current request from ContextVar.

factories
get(request_id)[source]

Get a request by id.

Return type:

BaseRequest | None

register_factory(scope_type, factory)[source]

Register a factory for a scope type.

Return type:

None

unregister()[source]

Unregister current request.

Return type:

BaseRequest | None

genro_asgi.request.get_current_request()[source]

Get the current request from context. Returns None if not in request context.

Return type:

BaseRequest | None

genro_asgi.request.set_current_request(request)[source]

Set the current request in context. Returns token for reset.

Return type:

Any