Routing¶
Kuí uses a Radix Tree (compressed prefix tree) for fast route matching.
Route Registration¶
Decorator Style¶
The most common way to register routes:
from kui.asgi import Kui
app = Kui()
# Any HTTP method
@app.router.http("/health")
async def health():
return "ok"
# Specific methods
@app.router.http.get("/users")
async def list_users():
return []
@app.router.http.post("/users")
async def create_user():
return {}, 201
Available method shortcuts: .get(), .post(), .put(), .patch(), .delete().
Decorator parameters:
| Parameter | Type | Description |
|---|---|---|
path |
str |
URL path pattern |
name |
str |
Route name for URL generation |
middlewares |
list |
Route-level middleware list |
summary |
str |
OpenAPI operation summary |
description |
str |
OpenAPI operation description |
tags |
list[str] |
OpenAPI tags |
Route Objects with << Operator¶
from kui.asgi import HttpRoute, SocketRoute
app.router <<= HttpRoute("/", homepage)
app.router <<= SocketRoute("/ws", ws_handler)
# Chain multiple routes
app.router <<= (
app.router
<< HttpRoute("/a", handler_a)
<< HttpRoute("/b", handler_b)
)
HttpRoute accepts: path, endpoint, name, summary, description, tags.
Routes Container¶
Group routes together with shared configuration:
from kui.asgi import Routes, HttpRoute
api_routes = Routes(
HttpRoute("/users", list_users),
HttpRoute("/posts", list_posts),
namespace="api", # Prefixes route names
tags=["API"], # OpenAPI tags for all routes
http_middlewares=[auth_mw], # Shared middleware
)
Routes supports << and + operators:
Routes also support slicing: routes[1:3].
Path Parameters¶
Path parameters are declared with {name} or {name:type} syntax:
| Type | Syntax | Matches | Example |
|---|---|---|---|
str (default) |
{name} |
Any string except / |
/users/alice |
int |
{name:int} |
Integer digits | /users/42 |
decimal |
{name:decimal} |
Decimal number | /price/19.99 |
date |
{name:date} |
Date string | /events/2024-01-15 |
uuid |
{name:uuid} |
UUID string | /items/550e8400-... |
any |
{name:any} |
Anything including / |
/files/path/to/file.txt |
Note
{name:any} must be at the end of the path. Static routes take precedence over dynamic routes (e.g., /users/me matches before /users/{id}).
@app.router.http.get("/users/{user_id:int}")
async def get_user(user_id: Annotated[int, Path()]):
return {"id": user_id}
@app.router.http.get("/files/{filepath:any}")
async def get_file(filepath: Annotated[str, Path()]):
return FilePath(filepath)
Common Prefix with // Operator¶
Add a shared prefix to a group of routes:
from kui.asgi import Routes, HttpRoute
api = Routes(
HttpRoute("/users", list_users),
HttpRoute("/posts", list_posts),
)
# All routes prefixed with /api/v1
app.router <<= "/api/v1" // api
# Results: /api/v1/users, /api/v1/posts
This also works with OpenAPI routes:
Reverse URL Lookup¶
Generate URLs from route names:
app.router <<= HttpRoute("/users/{user_id:int}", get_user, name="user-detail")
url = app.router.url_for("user-detail", {"user_id": 42})
# Returns: "/users/42"
Inside a handler, use request.url_for():
from kui.asgi import request
@app.router.http.get("/")
async def homepage():
user_url = request.url_for("user-detail", {"user_id": 1})
return {"user_url": str(user_url)}
Route Middleware¶
Apply middleware to individual routes using the @ operator:
from kui.asgi import HttpRoute, required_method, allow_cors
route = HttpRoute("/data", handler) @ required_method("GET") @ allow_cors()
app.router <<= route
Or pass middleware in the decorator:
See Middleware for details on writing and applying middleware.
MultimethodRoutes¶
Merge multiple single-method handlers for the same path into one view automatically:
from kui.asgi import MultimethodRoutes as Routes, HttpView
routes = Routes(base_class=HttpView)
@routes.http.get("/users")
async def list_users():
return []
@routes.http.post("/users")
async def create_user():
return {}, 201
@routes.http.delete("/users/{user_id:int}")
async def delete_user():
return "", 204
app.router <<= routes
This is equivalent to writing a class-based HttpView but allows you to keep handlers as separate functions. Internally, they are merged into a single view class.
WebSocket Routing¶
ASGI only. Register WebSocket handlers:
@app.router.websocket("/ws")
async def ws_handler():
await websocket.accept()
data = await websocket.receive_json()
await websocket.send_json({"echo": data})
await websocket.close()
Or with route objects:
See WebSocket for full documentation.
CLI: Display Routes¶
View all registered routes from the command line: