Context Reference ¶
The Context (c) provides a unified interface for request data, Datastar signals, and telemetry.
Request Data (c.req) ¶
| Property | Type | Description | Example |
|---|---|---|---|
.method |
str |
HTTP method | "GET", "POST" |
.path |
str |
Request path | "/api/v1/users" |
.query |
QueryParams |
Parsed query string | c.req.query.get("q") |
.tail |
str |
Path tail for /* |
c.req.tail |
.headers |
Headers |
HTTP headers | c.req.headers.get("X-ID") |
.cookies |
dict[str, str] |
Cookies | c.req.cookies.get("sid") |
Query Parameters ¶
c.req.query returns a QueryParams object with consistent access:
c.req.query.get("page") # First value → str | None
c.req.query.get("page", "1") # With default → str
c.req.query.getlist("tags") # All values → list[str]
"page" in c.req.query # Membership check → bool
len(c.req.query) # Number of unique keys
Why get() and getlist()? Query strings can have repeated keys — ?tags=a&tags=b is valid HTTP. Internally, all values are stored as lists keyed by parameter name. .get() returns the first value for convenience (the common case for single-valued params like page or id). .getlist() returns all values for that key, which is what you want for multi-valued params like checkboxes or tag filters.
# /search?tags=python&tags=async&page=2
c.req.query.get("tags") # "python" (first value)
c.req.query.getlist("tags") # ["python", "async"] (all values)
c.req.query.get("page") # "2"
c.req.query.getlist("page") # ["2"] (always a list)
c.req.query.get("missing") # None
c.req.query.getlist("missing") # []
Headers ¶
c.req.headers returns a Headers object. .get() and .getlist() return decoded strings:
c.req.headers.get("Authorization") # First value → str | None
c.req.headers.get("Accept", "text/html") # With default → str
c.req.headers.getlist("Set-Cookie") # All values → list[str]
"Content-Type" in c.req.headers # Membership check → bool
Headers follow the same pattern as query parameters — HTTP allows multiple values for the same header name. When a header appears once, it's stored as a single bytes value internally. When a second value is added (via .add()), the storage promotes to a list[bytes]. .get() returns the first value (decoded as a string), while .getlist() returns all of them. Header names are case-insensitive — they're normalized to lowercase bytes internally.
# Request with: Accept-Language: en, Accept-Language: fr
c.req.headers.get("Accept-Language") # "en" (first value)
c.req.headers.getlist("Accept-Language") # ["en", "fr"]
Body Access ¶
await c.req.json(): Parses body as JSON.await c.req.form(): Parses multipart or url-encoded forms.await c.req.body(): Returns rawbytes.
Datastar Signals ¶
Signals are synced reactive state from the client.
# 1. As a dictionary
signals = await c.signals()
# 2. As a typed Dataclass
@dataclass
class Filters:
query: str
page: int = 1
data = await c.signals(Filters)
Tracing & Telemetry ¶
Stario uses trace-based logging. Use c.span and c.step to observe performance.
Spans & Steps ¶
# Set attributes on the request span
c["user_id"] = 42
# Create a child span for an operation
with c.step("db.query", {"table": "users"}) as s:
res = await db.fetch()
s["count"] = len(res)
Events ¶
Record specific moments in time:
c("user.logged_in")
c("cache.miss", {"key": "..."})
Custom State ¶
Store handler-specific data in c.state (common in middleware):
def auth_middleware(next):
async def h(c, w):
c.state["user"] = await get_user(c)
await next(c, w)
return h
