Browse documentation

Documentation overview

Tutorial

Reference

Project

API reference

The signatures, return values, and edge cases in one place. The API is small enough that this page does not require its own executive summary.

Imports and scope

Python
from wred import App, Request

This reference describes the implementation in wred.py for Wred 0.10. Handlers normally use App and the request they receive. The low-level dispatch helpers are documented at the end for readers working directly with ASGI.

Related guides: first API, errors and troubleshooting, ASGI middleware.

App()

Create an HTTP-only ASGI application. The constructor takes no configuration arguments.

Python
app = App()

Export the instance, or a middleware wrapper around it, to your server. The ASGI entry point is await app(scope, receive, send); its return value is None. It creates a Request, finds a handler, awaits the handler, and sends the response.

Scope types other than http raise RuntimeError before receiving or sending messages. Use an outer application for lifespan, WebSockets, or other protocol handling. See the ASGI specification for the callable interface.

App.route(path, methods=("GET",))

Python
@app.route('/users/{user_id}', methods=['GET', 'PATCH'])
async def user(request, user_id):
    return {'id': user_id, 'method': request.method}
ArgumentMeaning
path: strA route such as /users or /users/{user_id}. Use a leading slash and valid Python parameter names.
methods: list[str] | tuple[str, ...]Defaults to ("GET",). Registration uppercases each method name.
Decorated handlerAn async callable accepting the request and any path parameters as keyword arguments. The decorator returns the original handler.

Routes are case-sensitive. Wred does not implement named routes, URL generation, subrouters, or an application mount() method. Use an outer ASGI router for composition.

Mounted paths and root_path

When the incoming path starts with the scope’s root_path on a segment boundary, Wred removes that prefix for route matching. The original scope and request.path retain the incoming path.

Registered routeIncoming scopeResult
/userspath="/api/users", root_path="/api"Matches /users; request.path remains /api/users.
/users/{user_id}path="/api/users/42", root_path="/api"Passes user_id="42".

The prefix and the server/router configuration must agree. See the Starlette mount example. Arbitrarily trimming slashes is not a deployment strategy, although it is a traditional source of one.

Request(scope, receive, send)

Wred creates this object for each HTTP request. Direct construction is mainly useful when working at the ASGI boundary. The constructor stores the supplied scope and callables; it does not read the body.

AttributeValue
request.path: strThe original scope["path"], without the query string. It is not rewritten to the mount-relative route.
request.method: strThe incoming scope["method"].
request.scope: dictThe supplied ASGI scope, including client, scheme, root_path, and any middleware state.
request.receiveThe ASGI receive callable. Consuming events yourself affects subsequent body reads.
request.sendThe ASGI send callable. Normally let the return-value handling or a callable response send the response.

Wred has no built-in request.user, request.session, request.cookies, request.form(), or request.is_disconnected() interface. Middleware or your own decorators can add context. The notebook’s request.user is added by its authentication decorator.

request.headers → dict[str, str]

A property, not a coroutine. On first access it decodes ASGI header bytes with Latin-1, lowercases names, and caches the resulting dict. Repeated Cookie fields are joined with ; ; repeated headers with other names keep their last value.

Inside a handler
authorization = request.headers.get('authorization', '')
content_type = request.headers.get('content-type', '')

This is a convenience view, not a multi-value header container. Use request.scope.get("headers", []) for the original byte pairs when duplicate fields matter. Changing the cached dict does not rewrite those original ASGI header pairs.

request.query → dict[str, str]

A cached_property, like headers. Parse the query string on first attribute access and cache the resulting dict. Blank values are preserved; repeated keys keep the last value. The parser strictly decodes UTF-8, including percent-encoded values. Invalid UTF-8 raises UnicodeDecodeError.

Inside a handler
# /search?q=first&q=last&empty=
query = request.query
# {'q': 'last', 'empty': ''}

No await is needed. Results are strings. Apply your own numeric ranges, field-count limits, and repeated-key policy. Repeated accesses return the cached dict, so mutate a copy if you need a separate working view.

await request.body() → bytes

Read successive http.request messages until more_body is false, join their bytes, and cache the complete body. Later calls return the cached bytes.

Inside a handler
body = await request.body()

The body is buffered in memory. Wred imposes no body-size limit; configure a limit at the server, proxy, or a suitable middleware layer. An incoming event other than http.request, including http.disconnect, raises ConnectionError.

await request.json() → Any

Read the cached body and parse it with orjson.loads(). An empty body returns {}. The result may be an object, array, string, number, boolean, or None; Wred does not enforce an object schema.

Inside a handler
data = await request.json()
if not isinstance(data, dict):
    return {'error': 'Send a JSON object'}, 'application/json', 400

The body is cached; the parsed JSON value is not. Each json() call parses those bytes again. Malformed JSON, including invalid UTF-8 JSON input, raises orjson.JSONDecodeError. Parsing does not depend on the Content-Type header. See orjson’s reference for its supported JSON behavior.

Handler return values

ReturnBody / content type / status
dict or listSerialized by orjson; application/json; 200.
bytesSent directly; application/octet-stream; 200.
(body, content_type, status)Explicit media type and status. A dict/list body always uses application/json.
(body, content_type, status, headers)As above, with an iterable of string header pairs.
Callable ASGI responseWred awaits response(scope, receive, send); the response owns the outgoing messages.
Alternative returns inside handlers
# JSON creation response with a Location header.
return {'id': 'n1'}, 'application/json', 201, [('Location', '/notes/n1')]

# Empty deletion response.
return b'', 'application/json', 204

# Text: encode it yourself.
return 'Hello'.encode('utf-8'), 'text/plain; charset=utf-8', 200

Tuple bodies must be bytes, dicts, or lists. Bare strings, integers, booleans, and None are unsupported response bodies. If you need a JSON scalar, use a callable response such as Starlette’s JSONResponse.

Use the tuple’s media type field to set Content-Type. Additional headers are lowercased and encoded with ASCII names and Latin-1 values. A list of pairs preserves repeated Set-Cookie headers.

Wred sends one response-start message followed by one body message for its own return-value handling; it does not add a Content-Length header itself. Your ASGI server handles HTTP transport details. A callable response can stream instead; see files and SSE.

Low-level App helpers

Most handlers should return a response value. The methods below support dispatch and explicit ASGI integrations; use them when you actually own the message flow. Sending a response yourself and then returning another one is not a two-for-one offer.

await app.send_response(send, response, status=200) → None

Send a supported body or response tuple through an ASGI send callable. A tuple’s status takes precedence over the status argument. This helper handles serialization and the two outgoing messages; it does not invoke callable response objects. Normal app dispatch invokes callable responses separately.

app.match_path(pattern, parts) → dict[str, str] | None

Compare a tuple of pattern segments with a list of incoming segments. Return string parameters when segment counts and fixed segments match, otherwise None. Inputs are already split; this helper does not normalize trailing slashes or remove a root path.

Python
app.match_path(('', 'users', '{user_id}'), ['', 'users', '42'])
# {'user_id': '42'}

app.routes and app.pattern_routes hold dispatch tables. Register handlers with route() rather than editing these tables directly.