Ushka

Guide

Dependency Injection

A handler declares what it needs in its signature, and Ushka passes it in. There is nothing to register and no container to configure.

What you can ask for

Parameter Matched by You get
request name request or annotation Request The current request.
response name response or annotation Response A fresh, empty Response to fill and return.
config name config or annotation Config The application configuration.
path parameters the name in brackets, e.g. item_id for [item_id] The value from the URL, as an Untrusted string.

Parameters can come in any order, and you only list the ones you use. A handler with no parameters is fine too.

# routes/hello.py
from ushka import Config, Request, Response


async def get(request: Request, response: Response, config: Config):
    name = request.query.get("name", "guest")

    response.body = f"Hello, {name}! Welcome to {config.APP_NAME}."
    response.headers["X-Greeting"] = "hello"
    return response

GET /hello?name=Ada answers Hello, Ada! Welcome to Ushka App. as text/plain. Untrusted text served as plain text is fine; the same string served as HTML would be refused (USHKA-G001).

The annotations are optional. This is equivalent:

async def get(request, response, config):
    ...

And since a matching annotation is enough, you can also use other names:

async def get(req: Request, cfg: Config):
    return {"path": req.path, "debug": cfg.APP_DEBUG}

Path parameters

Path parameters are matched by name, so the parameter must be spelled like the bracket in the file name:

# routes/users/[user_id]/posts/[post_id].py
from ushka.guard import safe


async def get(request, user_id, post_id):
    return {
        "user": safe.integer(user_id, min=1),
        "post": safe.integer(post_id, min=1),
        "fields": request.query.get("fields", "all"),
    }

Values are always strings; annotations such as user_id: int do not convert them. Use safe.integer() and the other sanitisers.

Note

The names request, response and config are reserved for the injected objects. Avoid them as path parameter names.

The injected Response

The injected response is a new Response() (status 200, empty body). Only what you return is sent, so return it after filling it in:

async def get(request, response):
    response.status_code = 202
    response.body = {"queued": True}     # dict and list bodies become JSON
    response.headers["Retry-After"] = "30"
    return response

Cookies and the session are saved automatically on whatever response you return; set them through the request:

async def post(request):
    request.cookies.set("theme", "dark", max_age=3600)
    request.session["visits"] = request.session.get("visits", 0) + 1
    return {"ok": True}

Edit this page