Routing
The openviper.routing package provides a fast, regex-backed URL router
with support for typed path parameters, HTTP method filtering, sub-routers
(blueprints), and per-router middleware.
Overview
Router is the central class. Register
handlers with method-specific decorators (@router.get, @router.post,
etc.) or with the generic @router.route decorator. Routers can be
composed hierarchically via include_router or the include() helper.
Path parameters are declared inside curly braces. An optional type specifier converts the value automatically:
Syntax |
Converter |
Example |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The router resolves routes by specificity: paths with more literal segments
are tried before paths with parameters, so /users/me beats
/users/{id:int}.
Key Classes
- class openviper.routing.router.Router(prefix='', middlewares=None)
URL router.
- route(path, methods, name=None, middlewares=None)
Register a handler for path + methods. middlewares is an optional list of per-route ASGI middleware callables applied only to this route.
- get(path, **kwargs)
- post(path, **kwargs)
- put(path, **kwargs)
- patch(path, **kwargs)
- delete(path, **kwargs)
- options(path, **kwargs)
Convenience decorators for the respective HTTP methods. All accept
name=andmiddlewares=keyword arguments.
- any(path, **kwargs)
Register a handler that matches
GET,POST,PUT,PATCH,DELETE,HEAD, andOPTIONS.
- add(path, handler, methods=None, namespace=None, middlewares=None)
Register a handler programmatically (non-decorator form).
- include_router(router)
Merge all routes from another
Routerinto this one, applying this router’s prefix.
- resolve(method, path) tuple[Route, dict]
Match method + path against registered routes. Returns the matched
Routeand a dict of extracted path parameters. RaisesNotFoundorMethodNotAllowedon failure.
- url_for(name, **path_params) str
Reverse-generate a URL from a named route. Returns the path string with parameters filled in. Raises
KeyErrorif the name is not registered.
- routes -> list[Route]
All routes including sub-router routes, flattened and cached.
- class openviper.routing.router.Route
Immutable dataclass representing a single route registration.
Attributes:
path,handler,methods(set of uppercase strings),name,middlewares.
Example Usage
See also
Working projects that demonstrate routing patterns:
examples/flexible/ — decorator-based routing (
@app.get,@app.post)examples/ai_moderation_platform/ —
Routerclass, class-based views, typed path paramsexamples/ecommerce_clone/ — multi-router mounting at
/api
Basic Route Registration
from openviper.routing.router import Router
from openviper.http.request import Request
from openviper.http.response import JSONResponse
router = Router()
@router.get("/")
async def index(request: Request) -> JSONResponse:
return JSONResponse({"status": "ok"})
@router.get("/users/{user_id:int}")
async def get_user(request: Request, user_id: int) -> JSONResponse:
user = await User.objects.get(id=user_id)
return JSONResponse(user._to_dict())
@router.post("/users")
async def create_user(request: Request) -> JSONResponse:
data = await request.json()
user = await User.objects.create(**data)
return JSONResponse(user._to_dict(), status_code=201)
Named Routes and URL Reversal
@router.get("/posts/{post_id:int}", name="post-detail")
async def post_detail(request: Request, post_id: int) -> JSONResponse: ...
# Reverse the URL
url = router.url_for("post-detail", post_id=42) # "/posts/42"
# Slug-based route
@router.get("/blog/{slug:slug}", name="blog-post")
async def blog_post(request: Request, slug: str) -> JSONResponse: ...
url = router.url_for("blog-post", slug="my-first-post")
Non-Decorator Registration
async def my_handler(request: Request) -> JSONResponse:
return JSONResponse({"hello": "world"})
router.add("/hello", my_handler, methods=["GET", "POST"], namespace="hello")
Sub-Router / Blueprint Pattern
from openviper.routing.router import Router, include
api_v1 = Router(prefix="/api/v1")
blog_router = Router()
@blog_router.get("/posts")
async def list_posts(request: Request) -> JSONResponse: ...
@blog_router.get("/posts/{post_id:int}")
async def get_post(request: Request, post_id: int) -> JSONResponse: ...
api_v1.include_router(include(blog_router, prefix="/blog"))
# Routes now at /api/v1/blog/posts and /api/v1/blog/posts/{post_id:int}
Router-level Middleware
from openviper.middleware.ratelimit import RateLimitMiddleware
# Attach middleware to the entire sub-router
api_router = Router(prefix="/api", middlewares=[my_auth_middleware])
@api_router.get("/data")
async def get_data(request: Request) -> JSONResponse: ...
Per-Route Middleware
from openviper.middleware.ratelimit import rate_limit
@router.get(
"/expensive",
middlewares=[some_custom_middleware],
)
async def expensive_view(request: Request) -> JSONResponse: ...
Class-Based Views
Use View with the router. See HTTP — Requests, Responses & Views
for the full View API.
from openviper.http.views import View
from openviper.http.response import JSONResponse
class PostView(View):
async def get(self, request: Request, post_id: int) -> JSONResponse:
post = await Post.objects.get(id=post_id)
return JSONResponse(post._to_dict())
async def put(self, request: Request, post_id: int) -> JSONResponse:
post = await Post.objects.get(id=post_id)
data = await request.json()
for k, v in data.items():
setattr(post, k, v)
await post.save()
return JSONResponse(post._to_dict())
async def delete(self, request: Request, post_id: int) -> JSONResponse:
post = await Post.objects.get(id=post_id)
await post.delete()
return JSONResponse({"deleted": True})
# Register all implemented HTTP methods automatically
PostView.register(router, "/posts/{post_id:int}", name="post-detail")
# Or manually
router.route(
"/posts/{post_id:int}",
methods=["GET", "PUT", "DELETE"],
)(PostView.as_view())
Mounting in the Application
# settings.py or app setup
from openviper.routing.router import Router
from myapp.views import router as app_router
main_router = Router()
main_router.include_router(app_router)
# routes.py (used by OpenViper app discovery)
route_paths = [
("/", main_router),
]