Added documentation for route path pattern matching.

2026-02-08 13:44:55 -05:00
parent 1b9bf1d837
commit 25e541790a
2 changed files with 72 additions and 0 deletions
+1
@@ -119,6 +119,7 @@ async def stats_page(ctx: RouteContext) -> dict:
| Field | Type | Description |
|-------|------|-------------|
| `request` | `aiohttp.web.Request` | The aiohttp request object. Access query params, body, headers, path info, etc. |
| `match_info` | `dict[str, str]` | Captured path parameters from `{name}` patterns. Empty dict for plain routes. |
| `module` | `ModuleContext` | The full module context with all services. |
All `ModuleContext` services are available directly on `ctx` (see [Proxied Properties](#proxied-properties) below).
+71
@@ -43,6 +43,60 @@ Routes are automatically prefixed under `/owlbot/<module_name>/`. So if the modu
This avoids conflicts between modules and with the webhook endpoint. Only the relative path is needed in the decorator.
## Path Parameters
Route paths support `{name}` patterns for capturing dynamic segments, using the same syntax as aiohttp. When a request arrives, the URL path is matched against registered route patterns and the captured segments populate `ctx.match_info`.
Given the route `@on_route("/users/{id}/posts/{slug}")` and a request to `/owlbot/my_module/users/42/posts/hello-world`:
| Property | Value |
|----------|-------|
| `ctx.match_info["id"]` | `"42"` |
| `ctx.match_info["slug"]` | `"hello-world"` |
For plain routes without `{...}` patterns, `ctx.match_info` is an empty dict `{}`.
### Single Segment
`{name}` matches a single path segment (no `/`):
```python
@on_route("/users/{id}")
async def get_user(ctx: RouteContext) -> dict:
user_id = ctx.match_info["id"] # e.g., "123"
return {"user_id": user_id}
```
Registered as `/owlbot/my_module/users/{id}`, this matches `/owlbot/my_module/users/123` but not `/owlbot/my_module/users/123/posts`.
### Custom Regex
`{name:regex}` matches with a custom regex pattern:
```python
@on_route("/users/{id:\\d+}")
async def get_user(ctx: RouteContext) -> dict:
user_id = ctx.match_info["id"] # only digits
return {"user_id": int(user_id)}
```
### Catch-All
`{name:.*}` matches the remaining path, including `/` characters. This is useful for prefix-style routing:
```python
@on_route("/api/{path:.*}", methods=["GET", "POST"])
async def api_catchall(ctx: RouteContext) -> dict:
remaining = ctx.match_info["path"] # e.g., "users/123/posts"
return {"path": remaining}
```
> **Note:** The catch-all requires at least one character after the prefix. `/api/{path:.*}` matches `/api/x` but not `/api`.
### Match Order
Routes are matched in registration order (first match wins). Routes defined with `@on_route` at import time are registered before dynamically registered routes, so they naturally get priority.
## Response Types
Route handlers support three return types:
@@ -128,6 +182,9 @@ async def list_page(ctx: RouteContext) -> web.Response:
`ctx.request` is a standard `aiohttp.web.Request`. Some common patterns:
```python
# Path parameters (from {name} patterns):
user_id = ctx.match_info["id"] # e.g., "123"
# Query parameters:
format = ctx.request.query.get("format", "json")
@@ -165,6 +222,20 @@ async def setup(ctx: ModuleContext) -> None:
)
```
Dynamic registration also supports path patterns:
```python
async def user_handler(ctx: RouteContext) -> dict:
return {"user_id": ctx.match_info["id"]}
@on_setup
async def setup(ctx: ModuleContext) -> None:
ctx.routes.register(
path="/users/{id}",
handler=user_handler,
)
```
### Available Methods
| Method | Description |