Context API¶
The request context provides a per-request key-value store that passes data between middleware, guards, and handlers. It is the primary mechanism for middleware to communicate with downstream handlers.
Overview¶
Every incoming request carries a context dictionary. Middleware can write to it (e.g., storing the authenticated user), and handlers can read from it.
Accessing Context in Handlers¶
The context is available as a property on the Request object.
from cello import App
app = App()
@app.get("/me")
def profile(request):
user = request.context.get("user")
if not user:
return Response.json({"error": "Not authenticated"}, status=401)
return {"name": user["name"], "email": user["email"]}
Context Properties¶
request.context¶
| Property | Type | Description |
|---|---|---|
request.context | dict | Mutable dictionary scoped to the current request |
The context dictionary supports all standard dict operations:
# Read a value
user = request.context.get("user")
# Read with default
role = request.context.get("role", "anonymous")
# Check existence
if "user" in request.context:
...
# Iterate
for key, value in request.context.items():
...
Writing to Context from Middleware¶
Middleware writes to context by modifying it during request processing. The Rust middleware chain passes the context through each layer.
JWT Middleware¶
When JWT authentication is enabled, the JWT middleware automatically populates context["user"] with the decoded token payload.
from cello import App, JwtConfig
app = App()
app.enable_jwt(JwtConfig(secret="my-secret", algorithm="HS256", expiration=3600))
@app.get("/me")
def profile(request):
# context["user"] is set by JWT middleware
user = request.context.get("user", {})
return {
"sub": user.get("sub"),
"roles": user.get("roles", []),
}
Session Middleware¶
When sessions are enabled, context["session"] contains the session data.
from cello import App, SessionConfig
app = App()
app.enable_session(SessionConfig(secret="session-secret"))
@app.get("/dashboard")
def dashboard(request):
session = request.context.get("session", {})
visits = session.get("visits", 0)
return {"visits": visits}
Custom Context Data¶
You can store arbitrary data in the context. Use it to pass computed values from middleware to handlers without repeating work.
import time
@app.before_request
def add_timing(request):
request.context["request_start"] = time.time()
@app.get("/slow")
def slow_endpoint(request):
# ... do work ...
elapsed = time.time() - request.context["request_start"]
return {"elapsed_ms": round(elapsed * 1000, 2)}
Context with Guards¶
Guards access the context to make authorization decisions. The JWT middleware populates context["user"], and guards inspect it.
from cello.guards import Role
@app.get("/admin", guards=[Role(["admin"])])
def admin_panel(request):
# Role guard already verified context["user"]["roles"] contains "admin"
return {"admin": True}
If the guard fails (e.g., the user lacks the required role), it raises a GuardError before the handler executes.
Context with Dependency Injection¶
Singletons registered with app.register_singleton() are injected via Depends, not via context. Use context for per-request data and Depends for application-scoped dependencies.
| Use Case | Mechanism |
|---|---|
| Authenticated user | request.context["user"] |
| Session data | request.context["session"] |
| Request ID | request.context["request_id"] |
| Database connection | Depends("database") |
| Service instances | Depends("user_service") |
Request ID in Context¶
When request ID middleware is enabled, each request gets a unique identifier.
app.enable_request_id()
@app.get("/debug")
def debug(request):
return {"request_id": request.context.get("request_id")}
The same ID is included in the X-Request-ID response header for end-to-end tracing.
Summary¶
| Feature | Description |
|---|---|
request.context | Per-request mutable dict |
| Populated by | Middleware (JWT, sessions, request ID, custom) |
| Read by | Handlers and guards |
| Scope | Single request lifetime |
| Thread safety | Each request has its own context instance |