Routing#
Every Hayavega route maps a URL pattern to a plain Python function taking a
Request and returning a Response (or, for api_route, anything
JSON-serializable).
path() — HTML and general views#
from hayavega import path
def home(request):
return Response("<h1>Hello</h1>")
urlpatterns = [
path("", home, methods=("GET",)),
path("posts/<path>", post_detail, methods=("GET",)), # trailing wildcard segment
]
methods defaults to ("GET",). A path ending in /<path> matches any
suffix, handed to the view via the request's path info — the same
mechanism static_routes() uses internally to serve a whole directory
tree under one route.
api_route() — JSON views with automatic OpenAPI docs#
from hayavega import api_route
def list_items(request):
return {"items": [...]} # dict/list return values are JSON-encoded automatically
urlpatterns = [
api_route("api/items", list_items, methods=("GET",), secured=True, responses={200: {"description": "A list of items"}}),
]
Unlike path(), a view wired with api_route() may return a plain
dict/list directly — it's wrapped in json_response()
automatically, or you can return a Response yourself when you need
custom headers or a non-200 status.
secured=True, request_schema=, and responses= don't change runtime
behavior at all — they only feed the generated OpenAPI document at
/openapi.json and the Swagger UI at /docs. See
REST & OpenAPI for the full documentation story.
Async views#
Both path() and api_route() views may be async def when served under
ASGI. Every built-in decorator (csrf_protect,
login_required, permission_required, api_route itself) detects an
async def view via inspect.iscoroutinefunction and produces a matching
async wrapper, so composing them with async views works exactly like with
sync ones — see tests/test_asgi.py's
test_login_required_composes_with_an_async_view for the exact contract
this guarantees.
Static files#
from hayavega import static_routes
from hayavega.conf import load_settings
_settings = load_settings()
urlpatterns = [
*static_routes(_settings.base_dir / "static", "static"),
]
Serves an entire directory with correct MIME types, ETag/conditional
requests, and cache-control headers — the same static-serving
machinery the benchmark suite measures directly
against other frameworks' own static handling.
Request and Response#
from hayavega.request import Request
from hayavega.response import Response, json_response
def echo(request: Request) -> Response:
return json_response(
{
"method": request.method,
"path": request.path,
"query": request.query_params,
"is_secure": request.is_secure,
}
)
Request is a frozen dataclass: method, path, query_params, body,
headers, remote_addr, is_secure, plus .json(), .form(), and
.cookies. Response is body/status/content_type/headers.
404s and route listing in debug mode#
When create_application(..., debug=True), an unmatched route's 404 page
lists every registered route pattern — computed once at
application-build time (free when a route does match, and free again
in production where debug=False), never leaking anything beyond your
own app's route table.
Where routing plugs into everything else#
- REST & OpenAPI —
api_route's documentation surface - GraphQL —
graphql_view(schema)is itself just apath()-compatible view - Security —
csrf_protect,login_required, and friends compose with any view, sync or async