wred is a tiny asgi backend harrisonerd.com/wred

$ git clone https://gitman.io/git/patx/wred

Wred

Wred A tiny and fast Python/ASGI backend JavaScript web apps. Pronounced "red".

Your API needs to return some JSON. It probably doesn't need a goddamn cathedral.

What you get

  • Async handlers, explicit HTTP methods, and fixed or parameterized routes.
  • Headers, query parameters, cached body reads, and JSON parsing.
  • JSON responses, custom response tuples, and a small-file helper.
  • Callable ASGI response support and routing relative to mounted paths.
  • Depends on weft-server and orjson. You can use any ASGI server.

What you don't get

Auth, sessions, databases, validation schemas, templates, form or multipart parsing, CORS, CSRF protection, rate limiting, dependency injection, generated docs, background jobs, WebSockets, or built-in streaming.

Use existing ASGI middleware for CORS, authentication, sessions, or compression. Starlette's middleware can wrap Wred directly for example... Your app chooses its dependencies; Wred doesn't need to know they exist.

Install and run

Python 3.11+. For a new project:

pip install wred

Save the following in app.py:

from wred import App

app = App()

@app.route('/')
async def hello(request):
    return {'hello': 'world'}

Run it with Weft, or any other ASGI server you like:

weft app:app

Open http://127.0.0.1:8000. Wred is HTTP-only. Unsupported protocols raise RuntimeError without sending HTTP messages, following ASGI guidance. You can also export a middleware wrapper as app.

Routes

Handlers are async and receive a request. Methods default to ['GET']; path parameters are strings passed by keyword.

@app.route('/users/{user_id}')
async def get_user(request, user_id):
    return {'id': user_id}

@app.route('/users', methods=['POST'])
async def create_user(request):
    return await request.json(), 'application/json', 201

@app.route('/users/{user_id}', methods=['PUT', 'PATCH'])
async def update_user(request, user_id):
    return {'id': user_id, 'method': request.method}

Different methods can have separate handlers on the same path. Fixed routes win over dynamic routes for that method; dynamic routes follow registration order. Registering the same path and method again replaces its handler. Unmatched paths or methods return JSON 404. No automatic HEAD, OPTIONS, or 405.

If you mount your Wred application at /api, a /users route in that app would become /api/users. Routing respects root_path; request.path and the original scope keep the full incoming path.

Wred deliberately leaves out a mount method to keep the core focused on small HTTP APIs. To serve another ASGI app alongside Wred, use an external router such as Starlette's Router and Mount: mount the other app at /example and Wred at /, with the catch-all last. This keeps application composition and lifecycle decisions outside Wred's core. This works with pretty much every ASGI application you can think of.

Requests

API Returns
request.path Path without the query string.
request.method HTTP method.
request.headers Decoded header dict; use lowercase keys.
request.scope Original ASGI scope, including client, scheme, and middleware state.
request.query() Cached dict of string keys and values. No await.
await request.body() Cached raw bytes.
await request.json() Parsed JSON; an empty body becomes {}.
@app.route('/search')
async def search(request):
    return {'q': request.query().get('q', '')}

Query parameters preserve blank values; repeated keys keep the last value. JSON can be an object, array, or scalar. Validate inputs and convert types yourself.

Authentication middleware can populate the scope for handlers to check. Starlette's SessionMiddleware uses request.scope['session']; its cookies are signed, not encrypted. Wred doesn't supply a session property.

Responses

Dicts and lists become JSON with HTTP 200. Bytes default to application/octet-stream. Custom responses use (body, content_type, status[, headers]):

Handler return Response
{'hello': 'world'} JSON object, 200.
[{'id': 1}, {'id': 2}] JSON array, 200.
b'hello', 'text/plain', 200 Text response.
{'id': 3}, 'application/json', 201, [('Location', '/items/3')] Created resource with a custom header.
b'', 'text/plain', 303, [('Location', '/')] Redirect.
b'', 'application/json', 204 No content.

Dicts and lists always use application/json, including inside tuples. Other tuple bodies must be bytes. Headers are string pairs; repeated Set-Cookie headers are supported.

Return a callable ASGI response object to let it manage status, headers, and body through (scope, receive, send). Reuse external response classes for streaming, downloads, or SSE. Use ASGI middleware for behavior across routes.

Static files

from pathlib import Path

static_root = Path(__file__).parent / 'static'

@app.route('/')
async def index(request):
    return await app.static('index.html', root=static_root)

@app.route('/static/{filename}')
async def asset(request, filename):
    return await app.static(filename, root=static_root)

static(filename, root=...) returns (bytes, content_type, status). The root is required and accepts a string or Path. MIME types are guessed; missing, unreadable, or outside-root files return plain-text 404, including symlink escapes. Files are read in a worker thread, entirely into memory. Small files. Don't feed it a movie.

Errors

Malformed JSON, invalid UTF-8 query strings, and WredRequestError produce JSON 400 responses: {'error': message}. Raise WredRequestError from a handler for application validation failures. Unsupported response types produce JSON 500 errors; other exceptions propagate.

Performance

Since there isn't much to Wred, it's really fast. What it lacks in features it gains in speed.

Wred running on Weft with 8 workers achieved up to 579,708 requests/second, averaging approximately 479,414 requests/second across five benchmark runs. Testing was performed locally using wrk with 4 threads, 256 concurrent connections, and 20-second runs, with an average p99 latency of just 3.69 ms. Testing was done on a Dell XPS 13 9350 with an Intel Core Ultra 7 256V (8 cores, up to 4.8 GHz), running Ubuntu 26.04.1 LTS (Linux 6.17.0-1023-oem, x86-64).

With respect to the lack of features mentioned above, Wred is meant to be bare bones, it's really an experiment of how minimal and fast an ASGI framework can be without being raw ASGI. For an actual micro framework with lifespan, streaming, multipart and more check out MicroPie.