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
from wred import App, RequestThis 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.
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",))
@app.route('/users/{user_id}', methods=['GET', 'PATCH'])
async def user(request, user_id):
return {'id': user_id, 'method': request.method}| Argument | Meaning |
|---|---|
path: str | A 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 handler | An async callable accepting the request and any path parameters as keyword arguments. The decorator returns the original handler. |
- Parameters occupy a whole path segment and arrive as strings. Convert and validate them in the handler. There are no typed converters or catch-all path parameters.
- Trailing slashes are ignored at registration and dispatch.
/usersand/users/reach the same route without a redirect. - A fixed route wins when it has a handler for the incoming method. If it does not, a matching dynamic route with that method may handle the request.
- Dynamic routes are tried in registration order. Keep overlapping patterns intentional.
- Registering the same path and method again replaces that handler. Other registered methods remain.
- No matching path/method returns
{"error":"Not Found"}with 404. HEAD, OPTIONS, redirects, and 405 responses are not synthesized.
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 route | Incoming scope | Result |
|---|---|---|
/users | path="/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.
| Attribute | Value |
|---|---|
request.path: str | The original scope["path"], without the query string. It is not rewritten to the mount-relative route. |
request.method: str | The incoming scope["method"]. |
request.scope: dict | The supplied ASGI scope, including client, scheme, root_path, and any middleware state. |
request.receive | The ASGI receive callable. Consuming events yourself affects subsequent body reads. |
request.send | The 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.
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.
# /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.
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.
data = await request.json()
if not isinstance(data, dict):
return {'error': 'Send a JSON object'}, 'application/json', 400The 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
| Return | Body / content type / status |
|---|---|
dict or list | Serialized by orjson; application/json; 200. |
bytes | Sent 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 response | Wred awaits response(scope, receive, send); the response owns the outgoing messages. |
# 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', 200Tuple 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.
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.
