Protocols
Section titled “Protocols”CallHandlerProtocol
Section titled “CallHandlerProtocol”Wraps the next step in the pipeline.
Interceptors use this to continue processing to the next interceptor or to the actual handler.
async def handle() -> Any
Continue processing the request.
Returns the result of the next handler in the pipeline.
ConfigAccessorProtocol
Section titled “ConfigAccessorProtocol”Provides access to web configuration.
Consumers that need configuration details should depend on this protocol for minimal coupling to the provider.
property web_config() -> Any
Return the web-layer configuration.
| Type | Description |
|---|---|
| Any | WebConfig instance with web-layer settings. |
property provider_config() -> Any
Return the provider-specific configuration.
| Type | Description |
|---|---|
| Any | WebProviderConfig instance with provider settings. |
property debug_routes_auth() -> Any
Return the debug routes authentication handler, if set.
| Type | Description |
|---|---|
| Any | Callable or None. |
property fail_on_route_conflict() -> bool
Whether to raise on duplicate route registration.
| Type | Description |
|---|---|
| bool | True if duplicate routes should raise RuntimeError. |
ControllerSourceProtocol
Section titled “ControllerSourceProtocol”Provides the list of registered controller classes.
Consumers that only need access to controllers should depend on this protocol instead of the full WebProvider.
property controllers() -> list[type]
Return the list of registered controller classes.
| Type | Description |
|---|---|
| list[type] | A list of controller class types. |
ExecutionContextProtocol
Section titled “ExecutionContextProtocol”Provides metadata about the current request being processed.
Used by interceptors and guards to access request information and make decisions about how to process the request/response.
property request() -> Any
The current HTTP request.
property handler() -> Callable[Ellipsis, Any]
The handler function that will be called.
property controller_class() -> type | None
The controller class, if this is a controller method.
property method_name() -> str | None
The name of the method being called.
property route_metadata() -> dict[str, Any]
Metadata about the route.
PipeProtocol
Section titled “PipeProtocol”Transforms or validates a single handler parameter.
Pipes operate on individual parameters before they reach the handler. They can transform (e.g., parse string to int) or validate (e.g., check that a value is within range).
Example
class ParseIntPipe: async def transform(self, value: Any, metadata: ParamMetadata) -> int: if value is None: return metadata.default or 0 try: return int(value) except ValueError: raise BadRequestError(f"Invalid integer for {metadata.name}")async def transform( value: Any, metadata: ParamMetadata ) -> Any
Transform or validate the value.
| Parameter | Type | Description |
|---|---|---|
value | Any | The value to transform/validate. |
metadata | ParamMetadata | Metadata about the parameter. |
| Type | Description |
|---|---|
| Any | The transformed value. |
| Exception | Description |
|---|---|
| Exception | On validation failure (typically HTTP 400 Bad Request). |
ProviderResourcesProtocol
Section titled “ProviderResourcesProtocol”Provides access to internal provider resources.
Used by advanced components like routing and middleware managers that need direct access to router and OpenAPI generator.
property router() -> Any
Return the internal Router instance.
| Type | Description |
|---|---|
| Any | Router instance or None if not initialized. |
property openapi_generator() -> Any
Return the OpenAPI generator instance.
| Type | Description |
|---|---|
| Any | OpenAPIGenerator instance or None if not configured. |
WebAppAccessorProtocol
Section titled “WebAppAccessorProtocol”Provides access to the underlying Starlette application.
Consumers that only need the Starlette app should depend on this protocol instead of the full WebProvider, decoupling from the provider’s full interface.
property starlette() -> Any
Return the Starlette application instance.
| Type | Description |
|---|---|
| Any | The Starlette ASGI application, or None if not yet initialized. |
WebInterceptorProtocol
Section titled “WebInterceptorProtocol”Intercepts request/response flow with full AOP control.
Interceptors wrap the entire request→handler→response lifecycle, enabling cross-cutting concerns like logging, caching, response transformation, and timing without modifying handler code.
Example
class TimingInterceptor(Interceptor): async def intercept( self, context: ExecutionContextProtocol, next: CallHandlerProtocol ) -> Any: start = time.perf_counter() result = await next.handle() elapsed = time.perf_counter() - start return resultasync def intercept( context: ExecutionContextProtocol, next_handler: CallHandlerProtocol ) -> Any
Intercept the request/response flow.
| Parameter | Type | Description |
|---|---|---|
context | ExecutionContextProtocol | Provides metadata about the current request. |
next_handler | CallHandlerProtocol | Wraps the next step in the pipeline. |
| Type | Description |
|---|---|
| Any | The result of the handler (possibly transformed). |
Notes:
- MUST call await next_handler.handle() to continue the pipeline.
- CAN transform the result before returning.
- CAN add/modify response headers.
- CAN short-circuit by returning early without calling next.
WebProviderProtocol
Section titled “WebProviderProtocol”Combined protocol for full WebProvider access.
This is the complete interface expected by components that need the full capabilities of WebProvider. Consumers that need fewer properties should depend on the more specific protocols above to reduce coupling.
Classes
Section titled “Classes”ApiVersionMetadata
Section titled “ApiVersionMetadata”Metadata attached to a versioned controller or handler by ``@api_version``.
BackgroundTasks
Section titled “BackgroundTasks”Accumulates background tasks to run after response is sent.
Context is captured at add time using propagate_context so that _execute_all restores the request-scope context variables (request_id, trace_id, …) even when called after the original request scope has been torn down.
def __init__() -> None
def add( func: Callable[Ellipsis, Any], *args: Any, **kwargs: Any ) -> None
Add a task to be executed after the response.
The current contextvars context is snapshotted immediately so that _execute_all can restore it at execution time.
CQRSController
Section titled “CQRSController”Controller with built-in CQRS command and query bus dispatch.
Subclass this instead of Controller when your endpoints need to
dispatch commands or queries through the CQRS buses.
The buses are injected via the constructor and are expected to be resolved from the DI container.
Example
from quadkit.serialization.domain import json_serializefrom quadkit.web import JSONResponse
class OrderController(CQRSController): @post("/orders") async def create_order(self, request: Request) -> JSONResponse: data = await request.json() result = await self.dispatch_command(CreateOrderCommand(**data)) return JSONResponse({"id": result.id}, status_code=201)
@get("/orders/{order_id}") async def get_order(self, request: Request) -> JSONResponse: order_id = request.path_params["order_id"] result = await self.dispatch_query(GetOrderQuery(order_id=order_id)) return JSONResponse(json_serialize(result))from quadkit.serialization.domain import json_serializefrom quadkit.web import JSONResponse
class OrderController(CQRSController): @post("/orders") async def create_order(self, request: Request) -> JSONResponse: data = await request.json() result = await self.dispatch_command(CreateOrderCommand(**data)) return JSONResponse({"id": result.id}, status_code=201)
@get("/orders/{order_id}") async def get_order(self, request: Request) -> JSONResponse: order_id = request.path_params["order_id"] result = await self.dispatch_query(GetOrderQuery(order_id=order_id)) return JSONResponse(json_serialize(result))def __init__( command_bus: CommandBusProtocol | None = None, query_bus: QueryBusProtocol | None = None ) -> None
Initialize CQRSController with optional command and query buses.
| Parameter | Type | Description |
|---|---|---|
command_bus | CommandBusProtocol | None | CommandBusProtocol implementation for dispatching commands. If None, calling `dispatch_command` will raise RuntimeError. |
query_bus | QueryBusProtocol | None | QueryBusProtocol implementation for executing queries. If None, calling `dispatch_query` will raise RuntimeError. |
async def dispatch_command(command: Any) -> Any
Dispatch a command through the command bus.
| Parameter | Type | Description |
|---|---|---|
command | Any | The command object to dispatch. |
| Type | Description |
|---|---|
| Any | The result returned by the command handler. |
| Exception | Description |
|---|---|
| RuntimeError | If no CommandBusProtocol was provided to this controller. |
async def dispatch_query(query: Any) -> Any
Dispatch a query through the query bus.
| Parameter | Type | Description |
|---|---|---|
query | Any | The query object to execute. |
| Type | Description |
|---|---|
| Any | The query result. |
| Exception | Description |
|---|---|
| RuntimeError | If no QueryBusProtocol was provided to this controller. |
Controller
Section titled “Controller”Base controller class with dependency injection support.
Controllers group related route handlers and can specify dependencies to be injected by the DI container.
Example
class UserController(Controller): @get(“/users”) async def list_users(self): return []
def __init__() -> None
def collect_routes(cls) -> list[dict[str, Any]]
Collect routes from controller methods.
It scans the class and its base classes for methods with _route_config attribute and returns a list of route definitions.
| Type | Description |
|---|---|
| List of route dictionaries with keys | method, path, handler_name, etc. |
ControllerRegistry
Section titled “ControllerRegistry”Registry for managing controller registrations.
Canonical source for controllers: ControllerRegistry. Use ControllerRegistry for controller-level operations (class registration). Use RouteRegistry for route-level operations (path, method, handler).
def __init__() -> None
def register( key: str | type, value: type | None = None, *, allow_overwrite: bool | None = None ) -> type | Any
Register a controller class.
| Parameter | Type | Description |
|---|---|---|
key | str | type | Component key or class |
value | type | None | The controller class to register |
allow_overwrite | bool | None | Whether to allow overwriting an existing registration |
| Type | Description |
|---|---|
| type | Any | The registered controller class |
def get(name: str) -> type | None
Get a controller class by name.
def list_controllers() -> list[str]
List all registered controller names.
def get_all_controllers() -> list[type]
Get all registered controller classes.
def clear() -> None
Clear all registrations.
Use in tests only to prevent test pollution between test cases.
DefaultMiddlewareStack
Section titled “DefaultMiddlewareStack”Builds the default Quadkit middleware stack as an explicit, composable list.
Encapsulates the minimum set of middlewares that every Quadkit web application applies so users can see and extend it without magic.
WebProvider delegates to this class internally, ensuring that all code paths produce the same default stack.
| Parameter | Type | Description |
|---|---|---|
container | The resolved DI container. Required for DIScopeMiddleware which provides request-scoped dependency resolution. | |
extra_middlewares | Additional user-supplied Quadkit middlewares to include in the stack. They are adapted to Starlette `Middleware wrappers via MiddlewareAdapterRegistry and follow DIScopeMiddleware` in user-supplied order — the DI-scope layer always stays outermost so request-scoped resolution is available to every later layer. |
Example
from quadkit.web.middleware import DefaultMiddlewareStackfrom quadkit.logging import get_logger
logger = get_logger(__name__)
# Inspect the default stackstack = DefaultMiddlewareStack(container=container)for mw in stack.build(): logger.debug("middleware", name=mw.cls.__name__)
# Extend with custom middlewarestack = DefaultMiddlewareStack( container=container, extra_middlewares=[MyTimingMiddleware()],)from quadkit.web.middleware import DefaultMiddlewareStackfrom quadkit.logging import get_logger
logger = get_logger(__name__)
# Inspect the default stackstack = DefaultMiddlewareStack(container=container)for mw in stack.build(): logger.debug("middleware", name=mw.cls.__name__)
# Extend with custom middlewarestack = DefaultMiddlewareStack( container=container, extra_middlewares=[MyTimingMiddleware()],)def __init__( container: ContainerResolverProtocol | None = None, extra_middlewares: list[Any] | None = None ) -> None
Initialise the stack builder.
| Parameter | Type | Description |
|---|---|---|
container | ContainerResolverProtocol | None | Resolved DI container for `DIScopeMiddleware`. |
extra_middlewares | list[Any] | None | Optional extra Quadkit middlewares to include. |
def build() -> list[StarletteMiddleware]
Build and return the ordered list of default Starlette middlewares.
The always-present entry is
DIScopeMiddleware.
Any extra_middlewares supplied at construction time are prepended
to that entry after adaptation.
| Type | Description |
|---|---|
| list[StarletteMiddleware] | An ordered list of starlette.middleware.Middleware instances ready to pass to starlette.applications.Starlette. |
def __init__(error: E) -> None
def is_ok() -> bool
def is_err() -> bool
def unwrap() -> T
def unwrap_err() -> E
def unwrap_or(default: T) -> T
def unwrap_or_else(op: Callable[[E], T]) -> T
def map_sync(op: Callable[[T], U]) -> Result[U, E]
def map_err(op: Callable[[E], F]) -> Result[T, F]
def expect(message: str) -> T
def match( ok: Callable[[T], U], err: Callable[[E], U] ) -> U
async def map(op: Callable[[T], Awaitable[U]]) -> Result[U, E]
async def async_map(op: Callable[[T], Awaitable[U]]) -> Result[U, E]
Alias for map — exists for backward compatibility.
def flatten() -> Result[Any, E]
def filter( predicate: Callable[[T], bool], error: E ) -> Result[T, E]
def ok_or(default: U) -> U
FastJSONResponse
Section titled “FastJSONResponse”JSON response using quadkit common JSON utilities.
Uses orjson when available (5-10x faster than stdlib json) with graceful fallback to stdlib json. Provides consistent JSON serialization across all Quadkit packages.
Performance benefits:
- 5-10x faster than stdlib json when orjson is available
- Native datetime/UUID/dataclass support
- Efficient bytes output
- Consistent behavior across packages
For Pydantic models, prefer model.model_dump_json() which uses Rust-based serialization (similar performance to orjson).
def render(content: Any) -> bytes
FileResponse
Section titled “FileResponse”File response for serving static files
GenericController
Section titled “GenericController”Generic controller with common CRUD patterns for any service.
Returns Result[T, DomainError] from all action methods. The
ResponseSerializer in the request pipeline automatically maps:
Ok(value)→ 200 JSON response (201 forcreate_item).Err(error)→ HTTP error viaResultResponseMapper.
Use @error_status to control the HTTP status code a domain error
maps to; the decorator registers the mapping, using the decorated
controller as the decoration target
from quadkit.web import error_status
@error_status(ItemNotFound, 404)class ItemsController(Controller): ...from quadkit.web import error_status
@error_status(ItemNotFound, 404)class ItemsController(Controller): ...def __init__( service: CRUDServiceProtocol[T], resource_name: str | None = None ) -> None
property resource_name() -> str
Get the resource name for permissions and error messages.
property logger() -> Any
Get the logger for this controller.
async def list_items( limit: int = 20, offset: int = 0, **filters: Any ) -> Result[dict[str, Any], DomainError]
List items with pagination.
Returns a Result whose Ok payload is a dict with keys
items, limit, offset, and total.
async def get_item(item_id: str) -> Result[T, DomainError]
Get single item by ID.
async def create_item(data: dict[str, Any]) -> Any
Create new item (returns 201 on success).
async def update_item( item_id: str, data: dict[str, Any] ) -> Result[T, DomainError]
Update existing item.
async def delete_item(item_id: str) -> Any
Delete item (returns 204 on success).
GracefulShutdownManager
Section titled “GracefulShutdownManager”Manages graceful shutdown with connection draining.
def __init__(timeout: float = 30.0)
property is_shutting_down() -> bool
Check if shutdown is in progress.
property active_connections() -> int
Get the number of active connections.
async def wait_for_drain() -> None
Wait for active connections to complete or timeout.
def begin_shutdown() -> None
Begin the shutdown process.
def complete_shutdown() -> None
Complete the shutdown process.
async def track_connection() -> Any
Context manager to track active connections.
async def serve_503_during_drain(request: Any) -> Any
Return a 503 response during drain period.
GracefulShutdownProvider
Section titled “GracefulShutdownProvider”DI provider that coordinates HTTP request draining on shutdown.
def __init__( timeout: float = 30.0, shutdown_manager: GracefulShutdownManager | None = None ) -> None
property manager() -> GracefulShutdownManager
Return the shared graceful shutdown manager.
property middleware() -> tuple[type[ShutdownMiddleware], dict[str, Any]]
Return the ASGI middleware tuple backed by the shared manager.
async def register(container: ContainerRegistrarProtocol) -> None
Register the shared graceful shutdown manager singleton.
async def boot(container: ContainerResolverProtocol) -> None
No-op: shutdown wiring is shared through DI and middleware config.
async def on_before_shutdown(signal: str | None = None) -> None
Begin HTTP drain and wait for in-flight requests to complete.
async def shutdown() -> None
No-op: request drain already completed in on_before_shutdown().
HTMLContent
Section titled “HTMLContent”Marker type explicitly indicating HTML content.
Handlers that return HTMLContent are explicitly declaring that the
returned string should be treated as HTML. This avoids heuristic checks
and makes the behavior explicit for callers.
HTMLResponse
Section titled “HTMLResponse”HTML response
HTMXResponse
Section titled “HTMXResponse”Convenience HTML response with HTMX-specific headers support.
Example
return HTMXResponse(“
HX-Trigger header to the JSON-encoded value.
def __init__( content: Any, status_code: int = 200, headers: dict[str, str] | None = None, hx_trigger: Any = None, hx_refresh: bool = False ) -> None
HealthController
Section titled “HealthController”HTTP probe controller exposing ``/health``, ``/ready``, ``/live``, ``/startup``, and ``/metrics``.
def __init__( registry: HealthCheckRegistryProtocol, metrics_exporter: MetricsExpositionProtocol | None = None ) -> None
async def health_check() -> JSONResponse
Basic liveness summary probe.
async def readiness() -> JSONResponse
Readiness probe.
async def liveness() -> JSONResponse
Liveness probe.
async def startup() -> JSONResponse
Startup probe.
async def metrics() -> Response
Export Prometheus-formatted metrics when a collector/exporter is wired.
InputSanitizationMiddleware
Section titled “InputSanitizationMiddleware”ASGI middleware that sanitizes query string parameters.
Strips null bytes and rejects obvious script-injection patterns from query parameters before they reach request handlers. Path parameters are not modified because they have been parsed and validated by the router before they hit middleware.
This is a defense-in-depth measure, not a substitute for validation at the service layer.
| Parameter | Type | Description |
|---|---|---|
app | The ASGI application to wrap. | |
sanitize_query_params | Whether to sanitize query string values (default: `True`). |
def __init__( app: ASGIApp, sanitize_query_params: bool = True ) -> None
JSONResponse
Section titled “JSONResponse”JSON response with high performance.
Uses quadkit common JSON utilities for consistent serialization across all Quadkit packages. Automatically uses Pydantic’s Rust serializer for Pydantic models when available.
def __init__(value: T) -> None
def is_ok() -> bool
def is_err() -> bool
def unwrap() -> T
def unwrap_err() -> E
def unwrap_or(default: T) -> T
def unwrap_or_else(op: Callable[[E], T]) -> T
def map_sync(op: Callable[[T], U]) -> Result[U, E]
def map_err(op: Callable[[E], F]) -> Result[T, F]
def expect(message: str) -> T
def match( ok: Callable[[T], U], err: Callable[[E], U] ) -> U
async def map(op: Callable[[T], Awaitable[U]]) -> Result[U, E]
async def async_map(op: Callable[[T], Awaitable[U]]) -> Result[U, E]
Alias for map — exists for backward compatibility.
def flatten() -> Result[Any, E]
def filter( predicate: Callable[[T], bool], error: E ) -> Result[T, E]
def ok_or(default: U) -> T
ParamMetadata
Section titled “ParamMetadata”Metadata about a parameter being piped.
Attributes: name: The parameter name. param_type: The type of parameter (path, query, body, header, cookie, file). expected_type: The expected Python type for the parameter. default: Default value if the parameter is not provided. alias: Optional alias for the parameter.
ProblemDetail
Section titled “ProblemDetail”RFC 7807 Problem Details for HTTP APIs.
Attributes: type: URI identifying the problem type title: Short summary of the problem status: HTTP status code detail: Human-readable explanation instance: URI for this specific occurrence errors: Additional validation errors (extension)
def to_dict() -> dict[str, Any]
Convert to dictionary for JSON serialization.
def json_schema(cls) -> dict[str, Any]
Default wire shape from to_dict; extension members remain allowed.
def from_exception( cls, exc: Exception, status: int = 500, debug: bool = False, **kwargs: Any ) -> ProblemDetail
Create a ProblemDetail from an exception.
When debug is False (default), server-error responses (5xx) use a generic detail string to prevent internal information from leaking to API consumers. Set debug=True only in development environments.
def validation_error( cls, errors: list[dict[str, Any]], detail: str = 'Validation failed' ) -> ProblemDetail
Create a validation error ProblemDetail.
def not_found( cls, resource: str, identifier: str | None = None ) -> ProblemDetail
Create a not found ProblemDetail.
def bad_request( cls, detail: str, errors: list[dict[str, Any]] | None = None ) -> ProblemDetail
Create a bad request ProblemDetail.
def internal_error( cls, detail: str = 'An internal error occurred' ) -> ProblemDetail
Create an internal error ProblemDetail.
RateLimitModule
Section titled “RateLimitModule”Module-level rate limiting configuration.
Registers a RateLimitProvider in
the application’s DI container so that route-level @throttle and
@rate_limit decorators can resolve a limiter from the container.
Usage
app.add_module(RateLimitModule.configure( backend="redis", default_limit="100/minute",))
# Development / testing — no external service required:app.add_module(RateLimitModule.configure(backend="memory"))app.add_module(RateLimitModule.configure( backend="redis", default_limit="100/minute",))
# Development / testing — no external service required:app.add_module(RateLimitModule.configure(backend="memory"))def configure( cls, *, backend: Literal['redis', 'memory'] = 'memory', default_limit: str | None = None, redis_client: RedisClientProtocol | None = None, enabled: bool = True ) -> Any
Create a DynamicModule that wires up rate limiting.
| Parameter | Type | Description |
|---|---|---|
backend | Literal['redis', 'memory'] | Storage backend. `"redis" uses atomic Lua scripts (recommended for production); "memory`" uses a process-local sliding window (suitable for development and single-process deployments). |
default_limit | str | None | Optional default rate limit validated at module configuration time. Scope note: global (all-routes) enforcement lives in the ASGI `RateLimitMiddleware, configured through the [web.rate_limit] section of application.yaml` — this kwarg does not wire that middleware; per-route limits are enforced by throttle with the module-provided limiter. |
redis_client | RedisClientProtocol | None | Pre-built Redis client satisfying RedisClientProtocol. Only used when `backend="redis"`. When *None*, the provider resolves one from the container at boot time. |
enabled | bool | When `False` the rate limiter is not started. |
| Type | Description |
|---|---|
| Any | A DynamicModule descriptor. |
RateLimitProvider
Section titled “RateLimitProvider”Standalone provider for rate-limiting services.
Registers a RateLimiter in the DI container and lazily resolves a Redis client from the container at boot time when none is supplied at construction.
A client reaches the container as a
RedisClientProtocol registration or
as a named "redis_client" binding; see
resolve_redis_client.
def __init__( redis_client: RedisClientProtocol | None = None, enabled: bool = True, backend: Literal['redis', 'memory'] = 'redis' ) -> None
Initialize rate limit provider.
| Parameter | Type | Description |
|---|---|---|
redis_client | RedisClientProtocol | None | Optional pre-built client satisfying RedisClientProtocol. |
enabled | bool | When `False` the provider skips all registration/boot. |
backend | Literal['redis', 'memory'] | `"redis" uses the Redis-backed limiter (previous and default behaviour); "memory"` binds the process-local sliding-window MemoryLimiter and never touches Redis. |
async def register(container: ContainerRegistrarProtocol) -> None
Register rate limiter in the DI container.
| Parameter | Type | Description |
|---|---|---|
container | ContainerRegistrarProtocol | DI container registrar. |
async def boot(container: ContainerResolverProtocol) -> None
Start the rate limit provider, resolving Redis from the container if needed.
async def shutdown() -> None
Shutdown the rate limit provider.
async def health_check(timeout: float = 5.0) -> HealthCheckResult
Check rate-limit provider health.
| Type | Description |
|---|---|
| HealthCheckResult | HealthCheckResult reflecting whether the limiter is initialised. |
RedirectResponse
Section titled “RedirectResponse”Redirect response
Response
Section titled “Response”Base response class
Result
Section titled “Result”Base Result type. Not abstract — Ok and Err are the only variants.
def is_ok() -> bool
def is_err() -> bool
def unwrap() -> T
def unwrap_err() -> E
def unwrap_or(default: T) -> T
def unwrap_or_else(op: Callable[[E], T]) -> T
def map_sync(op: Callable[[T], U]) -> Result[U, E]
def map_err(op: Callable[[E], F]) -> Result[T, F]
def expect(message: str) -> T
def match( ok: Callable[[T], U], err: Callable[[E], U] ) -> U
async def map(op: Callable[[T], Awaitable[U]]) -> Result[U, E]
def flatten() -> Result[Any, E]
def filter( predicate: Callable[[T], bool], error: E ) -> Result[T, E]
def ok_or(default: U) -> T | U
def from_exception( cls, exc: Exception, ok_type: type[T] = type(None) ) -> Result[T, Exception]
Wrap a caught exception into an Err result.
def to_optional() -> T | None
def inspect(op: Callable[[T], None]) -> Result[T, E]
def inspect_err(op: Callable[[E], None]) -> Result[T, E]
ResultResponseMapper
Section titled “ResultResponseMapper”Maps ``Result`` error values to HTTP responses.
The default mappings follow REST conventions:
| Error type | Status |
|---|---|
NotFoundError | 404 |
ValidationError | 422 |
PermissionDeniedError | 403 |
AuthorizationError | 403 |
AuthenticationError | 401 |
ConflictError | 409 |
RateLimitError | 429 |
DomainError (base) | 400 |
other DomainError | 400 (client fault) |
| non-domain error | 500 (server fault) |
Use register to override the status and optionally attach an app-specific body renderer for a specific error type.
def to_response( result: Any, success_status: int = 200, *, request: Any | None = None ) -> StarletteResponse
Convert a Result to an HTTP response.
| Parameter | Type | Description |
|---|---|---|
result | Any | The `Result[T, E]` object to map. |
success_status | int | HTTP status code for Ok results (default 200). |
request | Any | None | Optional request context passed to a registered error renderer. |
| Type | Description |
|---|---|
| StarletteResponse | A JSONResponse with appropriate status code and structured body. |
def documented_errors( cls, error_types: list[type[Exception]] | tuple[type[Exception], Ellipsis], *, exclude_statuses: set[str] | None = None ) -> dict[str, Any]
Describe explicitly declared default Result errors using native wiring.
Custom renderers require explicit responses; no return-annotation or guard-name guessing, exception construction or policy inference occurs.
def register( cls, error_type: type[Exception], status_code: int, *, body_renderer: ErrorBodyRenderer | None = None ) -> None
Register a custom error status and, optionally, a body renderer.
Custom status registrations take precedence over defaults (inserted at
the front). A body renderer receives (error, status_code, request)
and may return either a JSON-serializable body or a Starlette Response.
When multiple renderers match an error, the nearest type in its MRO
wins; an unmatched error keeps the default RFC 7807 body.
| Parameter | Type | Description |
|---|---|---|
error_type | type[Exception] | The exception class to match. |
status_code | int | HTTP status code to return when the error does not provide its own `status_code` attribute. |
body_renderer | ErrorBodyRenderer | None | Optional app-specific error body renderer. Returning a Response allows an app to preserve response headers or media types alongside its body; returning data lets the mapper apply the resolved status as JSON. |
def error_to_response( cls, error: Any, request: Any | None = None ) -> StarletteResponse
Convert a domain error to a JSON HTTP response.
| Parameter | Type | Description |
|---|---|---|
error | Any | The error value from `Result.unwrap_err()`. |
request | Any | None | Optional request context passed to a registered renderer. |
| Type | Description |
|---|---|
| StarletteResponse | The response returned by a custom renderer, or a JSONResponse with the appropriate status code and a structured RFC 7807 body. |
RoleGuard
Section titled “RoleGuard”GuardProtocol that checks if user has required role(s).
The authorizer must be injected at guard instantiation time (constructor injection).
Usage
def __init__( *required_roles: str, authorizer: AuthorizerProtocol ) -> None
async def can_activate(context: Any) -> bool
Check if user has required role using AuthorizerProtocol.
RouteRegistry
Section titled “RouteRegistry”Registry for managing route registrations and discovery.
Canonical source for routes: RouteRegistry. Use RouteRegistry for route-level operations (path, method, handler). Use ControllerRegistry for controller-level operations (class registration).
def __init__(debug: bool = False)
def register_controller( controller_cls: type[Controller], controller_registry: ControllerRegistry | None = None ) -> None
Register a controller class and synchronize to ControllerRegistry.
| Parameter | Type | Description |
|---|---|---|
controller_cls | type[Controller] | The controller class to register. |
controller_registry | ControllerRegistry | None | If provided, also registers the class by name in the ControllerRegistry for atomic synchronization. This ensures a controller exists in both registries, preventing lookup inconsistencies. |
def get_all_routes() -> dict[str, dict[str, Any]]
Get all registered routes
def get_controllers() -> list[type[Controller]]
Get all registered controllers
def find_route( path: str, method: str ) -> dict[str, Any] | None
Find a route by path and method
def get_routes_by_controller(controller_cls: type[Controller]) -> list[dict[str, Any]]
Get all routes for a specific controller
def clear() -> None
Clear all registrations
Router
Section titled “Router”Router with Controller DI Strategy support.
The Router manages route registration, controller resolution, and request handling. It integrates with the DI container to provide automatic dependency injection for controllers and their methods.
Attributes: routes: List of registered routes with their handlers. _controller_cache: Cache of resolved controller instances. _container: DI container for resolving dependencies. _signature_cache: Cache of handler signatures for performance. _routes_by_name: Dictionary of routes indexed by their unique name. exception_filters: Registry for handling controller exceptions.
def __init__( filter_pipeline: FilterPipeline | None = None, global_pipeline: FilterPipeline | None = None )
Initialize the router.
| Parameter | Type | Description |
|---|---|---|
filter_pipeline | FilterPipeline | None | Pipeline holding this router's own filters. |
global_pipeline | FilterPipeline | None | Application-wide pipeline owned by the DI container; passed down to each request pipeline as the last filter consulted before an exception is re-raised. |
def get_route_by_name(name: str) -> Route | None
Get a route by its unique name in O(1) time.
| Parameter | Type | Description |
|---|---|---|
name | str | The unique name of the route. |
| Type | Description |
|---|---|
| Route | None | The Route object if found, otherwise None. |
def clear_cache() -> None
Clear all cached handler signatures and type-hint lookups.
Call this when modules are hot-reloaded so that stale signatures are not used. The caches will be repopulated lazily on the next request that reaches each handler.
Example
hot_reload_manager.on_reload(router.clear_cache)hot_reload_manager.on_reload(router.clear_cache)def add_route( method: str, path: str, handler: Callable, name: str | None = None, controller_cls: type | None = None, **kwargs: Any ) -> None
Add a route (used internally)
ShutdownMiddleware
Section titled “ShutdownMiddleware”Middleware that tracks connections for graceful shutdown.
def __init__( app: ASGIApp, shutdown_manager: GracefulShutdownManager ) -> None
StreamingResponse
Section titled “StreamingResponse”Streaming response for large files or server-sent events
VersionExtractor
Section titled “VersionExtractor”Extracts version from requests based on strategy.
def __init__(config: VersioningConfig)
def extract(request: Request) -> str
Extract version from request.
WebConfig
Section titled “WebConfig”Hierarchical root configuration for Quadkit Web.
This is the single source of truth for web configuration,
loaded from application.yaml’s web section.
Attributes: name: Configuration name (default: “web”) enabled: Whether the web module is enabled server: Server binding and worker settings app: Application-level settings (OpenAPI, CORS origins, etc.) security: Security headers and policies cors: CORS configuration rate_limit: Rate limiting rules debug_routes: Enable /debug/* endpoints api_docs: API documentation endpoints and their assets enable_identity_resolution: Resolve OAuth external IDs to internal UUIDs enable_auth: Enable built-in authentication middleware auth_exclude_paths: Paths excluded from authentication
property api_docs_enabled() -> bool
Whether the documentation endpoints are served in this environment.
api_docs.enabled is tri-state: None (the default) serves the
documentation everywhere except production, True opts in
unconditionally — including production — and False withholds it
everywhere. Both the endpoints (/docs, /redoc,
/openapi.json) and their assets follow this one answer.
def validate_production_security() -> WebConfig
Block insecure configurations in production.
WebInterceptorBase
Section titled “WebInterceptorBase”Base class for interceptors (optional convenience).
Provides a no-op implementation that subclasses can override.
async def intercept( context: ExecutionContextProtocol, next_handler: CallHandlerProtocol ) -> Any
Default implementation just passes through.
WebModule
Section titled “WebModule”Web module with HTTP provider.
def configure( cls, controllers: list[type] | None = None, discover: list[str] | tuple[str, Ellipsis] | None = None, host: str | None = None, port: int | None = None, websocket_handlers: list[type] | None = None, **kwargs: Any ) -> DynamicModule
Create a WebModule with explicit configuration.
Configuration (CORS, CSRF, server host/port, etc.) is loaded from the
[web] section of application.yaml by the orchestrator and injected
into the provider at registration time. Pass a web_config kwarg
only if you need to bypass YAML entirely.
| Parameter | Type | Description |
|---|---|---|
controllers | list[type] | None | Controller classes to register with the web server. |
discover | list[str] | tuple[str, Ellipsis] | None | Package paths to scan for controllers and decorated WebSocket handlers, merging them with explicit classes. |
host | str | None | Override the server host (builds a `WebConfig` internally). |
port | int | None | Override the server port (builds a `WebConfig` internally). **kwargs: Additional keyword arguments forwarded to WebProvider. |
def stub( cls, config: Any = None ) -> DynamicModule
Return a no-op WebModule suitable for unit testing.
Registers WebProvider with no controllers and test-safe configuration. No real HTTP server is started.
| Parameter | Type | Description |
|---|---|---|
config | Any | Optional test configuration override. |
| Type | Description |
|---|---|
| DynamicModule | A DynamicModule with in-memory web configuration. |
WebProvider
Section titled “WebProvider”Web provider with native Starlette integration and pass-through architecture.
Recommended usage — let the orchestrator inject configuration via
application.yaml (uses config_key = "web" and config_model = WebConfig)
app.add_provider(WebProvider())app.add_provider(WebProvider())Config-first factory — construct from an explicit WebConfig
from quadkit.web import WebProvider, WebConfig
app.add_provider(WebProvider.from_config(WebConfig(debug=True, ...)))from quadkit.web import WebProvider, WebConfig
app.add_provider(WebProvider.from_config(WebConfig(debug=True, ...)))Advanced — pass individual components explicitly (e.g. for tests)
app.add_provider(WebProvider( controllers=[UsersController], middleware=[AuthMiddleware], filters=[AuditExceptionFilter()], web_config=WebConfig(debug=True),))app.add_provider(WebProvider( controllers=[UsersController], middleware=[AuthMiddleware], filters=[AuditExceptionFilter()], web_config=WebConfig(debug=True),))filters are application-wide exception filters. They are registered as
the container’s FilterPipeline singleton and consulted after the
route-level filters and the router’s built-in defaults, so a filter listed
here is the last stop before an error propagates.
Note: The multi-parameter constructor is intended for advanced and test
scenarios. For typical applications, prefer the no-arg or from_config()
form so that configuration is driven by application.yaml.
def __init__( middleware: list[Any] | None = None, filters: list[Any] | None = None, exception_handlers: dict[Any, Any] | None = None, controllers: list[type[Controller]] | None = None, web_config: WebConfig | None = None, provider_config: WebProviderConfig | None = None, debug_routes_auth: Callable[Ellipsis, Any] | None = None, websocket_handlers: list[type] | None = None ) -> None
property contributor_registry() -> Any
Expose contributor registry for route setup access.
property route_registry() -> Any
This application’s route registry (per-instance, plans/09172026 H-04).
property controller_registry() -> Any
This application’s controller registry (per-instance, H-04).
def from_config( cls, config: WebConfig, **context: Any ) -> WebProvider
Create a WebProvider from config.
Context kwargs may include middleware, controllers, exception_handlers.
def auto_discover( cls, *packages: str, web_config: WebConfig | None = None, **kwargs: Any ) -> WebProvider
Create a WebProvider with routes auto-discovered from packages.
Scans each package recursively for
Controller subclasses and
@websocket_handler classes, registering both automatically.
| Parameter | Type | Description |
|---|---|---|
web_config | WebConfig | None | Optional web configuration. Falls back to defaults. **kwargs: Extra kwargs forwarded to WebProvider.__init__. |
| Type | Description |
|---|---|
| WebProvider | A configured WebProvider instance. |
Example
app.add_provider(WebProvider.auto_discover("my_app.api.controllers"))app.add_provider(WebProvider.auto_discover("my_app.api.controllers"))async def register(container: ContainerRegistrarProtocol) -> None
Register web services in DI container.
Delegates each cohesive registration group to quadkit.web.di.provider_sections, invoked at the same ordered positions as the pre-extraction implementation.
async def boot(container: ContainerResolverProtocol) -> None
Initialize the web layer in five ordered phases.
Phase 1 — OpenAPI generator Instantiates OpenAPIGenerator with title and version from web_config.
Phase 2 — Starlette application
Builds the native ASGI Starlette instance. The middleware stack
is composed once here; it is never rebuilt at request time.
Container and config are attached to app.state.
Phase 3 — Middleware pipeline Iterates registered AbstractMiddleware subclasses and wraps the Starlette app. Order follows the provider registration order (outermost-first).
Phase 4 — Integration setup Wires optional first-class integrations: authentication, rate limiting, GraphQL gateway. Each integration is only activated when its config key is present in the resolved container.
Phase 5 — Route registration Discovers annotated controller methods and mounts them on the Starlette router. Route-level dependencies (guards, interceptors, serializers) are resolved here.
async def shutdown() -> None
Cleanup resources created by this provider.
async def health_check(timeout: float = 5.0) -> HealthCheckResult
Check web provider health.
def run_server( host: str = '127.0.0.1', port: int = 8000, **kwargs: Any ) -> None
Run the web application synchronously (blocking).
Delegates to quadkit.web.server.runner.run_server, which is sync-only; call quadkit.web.server.runner.run_server_async from async code instead.
WebRequestReceivedHook
Section titled “WebRequestReceivedHook”Payload fired when the ASGI server receives an incoming HTTP request.
Attributes:
path: URL path of the incoming request (e.g. "/api/users").
method: HTTP verb in upper-case (e.g. "GET").
WebResponsePreparedHook
Section titled “WebResponsePreparedHook”Payload fired once a response has been assembled, before it is sent.
Attributes: path: URL path the response belongs to. status_code: HTTP status code of the prepared response.
WebServerStartedHook
Section titled “WebServerStartedHook”Payload fired after the ASGI lifespan ``startup`` phase completes.
WebServerStoppedHook
Section titled “WebServerStoppedHook”Payload fired after an orderly ASGI lifespan ``shutdown`` completes.
WebSocket
Section titled “WebSocket”Quadkit WebSocket wrapper around Starlette WebSocket
def __init__(starlette_ws: StarletteWebSocket)
async def accept(subprotocol: str | None = None) -> None
async def close(code: int = 1000) -> None
async def receive_text() -> str
async def receive_json() -> Any
async def receive_event() -> MutableMapping[str, Any]
Receive a raw ASGI WebSocket event for protocol bridges.
Starlette types the ASGI message as a mutable mapping, not a plain
dict; declaring the wider type is what keeps make type honest
without an unchecked cast.
property headers() -> Any
Handshake headers exposed read-only to gateway handlers.
property query_params() -> Any
Handshake query parameters exposed to gateway handlers.
async def send_text(data: str) -> None
async def send_json(data: Any) -> None
async def send_bytes(data: bytes) -> None
property state() -> Any
WebSocket state for middleware.
property path_params() -> dict[str, Any]
Path parameters captured by the Starlette WebSocket route.
Functions
Section titled “Functions”api_version
Section titled “api_version”
def api_version( ver: int | str, *, deprecated: bool = False, sunset: str | None = None, prefix: str | None = None ) -> Callable[[Any], Any]
This extends the simpler version decorator with:
- URL prefix — the version number is automatically prepended to the
controller’s
prefixattribute (e.g.prefix = "/users"becomes"/v1/users"). Override with the prefix argument. - Deprecation support — setting
deprecated=Truecauses the routing layer to injectDeprecation: trueand optionallySunset: <date>response headers for all routes on the controller. - Metadata storage — an ApiVersionMetadata instance is
stored on the class/function as
__api_version_meta__and the plain version string as__api_version__(compatible with VersioningMiddleware).
| Parameter | Type | Description |
|---|---|---|
ver | int | str | Version number or string (e.g. `1, "2", "2.1"`). |
deprecated | bool | When `True`, mark this version as deprecated. |
sunset | str | None | Optional ISO-8601 date string indicating when support ends. |
prefix | str | None | Explicit URL prefix to use instead of the auto-derived one. |
| Type | Description |
|---|---|
| Callable[[Any], Any] | Class/function decorator. |
Example
@api_version(1)class UserControllerV1(Controller): prefix = "/users" # mounted at /v1/users
@api_version(2)class UserControllerV2(Controller): prefix = "/users" # mounted at /v2/users
@api_version(1, deprecated=True, sunset="2025-12-31")class LegacyController(Controller): prefix = "/legacy" # adds Deprecation + Sunset headers@api_version(1)class UserControllerV1(Controller): prefix = "/users" # mounted at /v1/users
@api_version(2)class UserControllerV2(Controller): prefix = "/users" # mounted at /v2/users
@api_version(1, deprecated=True, sunset="2025-12-31")class LegacyController(Controller): prefix = "/legacy" # adds Deprecation + Sunset headersbackground
Section titled “background”
def background(func: Callable[Ellipsis, Awaitable[Any]]) -> Callable[Ellipsis, Awaitable[Any]]
def body( default: Any = ..., validation: Callable[Ellipsis, Any] | None = None, pipes: list[Any] | None = None ) -> Any
controller
Section titled “controller”
def controller(name: str | None = None) -> Any
Registers the class by name in the active application’s
ControllerRegistry (plans/09172026 H-04: registries are per-application
instances — there is no module-level global to write to). When no
registration context is active the class is returned unchanged and a
warning is logged; declare controllers via WebProvider(controllers=...)
or @module(controllers=...) to have them mounted.
| Parameter | Type | Description |
|---|---|---|
name | str | None | Optional controller name, defaults to class name |
Example
@controller() class UserController(Controller): @get(“/users”) async def get_users(self): return {“users”: []}
cookie
Section titled “cookie”
def cookie( default: Any = ..., alias: str | None = None, validation: Callable[Ellipsis, Any] | None = None, pipes: list[Any] | None = None ) -> Any
delete
Section titled “delete”
def delete( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
discover_controllers
Section titled “discover_controllers”
def discover_controllers(packages: tuple[str, Ellipsis] | list[str]) -> list[type]
| Parameter | Type | Description |
|---|---|---|
packages | tuple[str, Ellipsis] | list[str] | Dotted Python package paths to scan. |
| Type | Description |
|---|---|
| list[type] | List of Controller subclasses (not instantiated — the DI container constructs them during boot). |
discover_websocket_handlers
Section titled “discover_websocket_handlers”
def discover_websocket_handlers(packages: tuple[str, Ellipsis] | list[str]) -> list[type]
WebSocket handlers are deliberately discovered separately from HTTP
controllers: they have a class-level path marker and are mounted as
Starlette WebSocketRoute instances rather than HTTP routes.
| Parameter | Type | Description |
|---|---|---|
packages | tuple[str, Ellipsis] | list[str] | Dotted Python package paths to scan. |
| Type | Description |
|---|---|
| list[type] | Deduplicated handler classes in discovery order. |
error_status
Section titled “error_status”
def error_status( error_type: type[Exception], status_code: int ) -> Any
| Parameter | Type | Description |
|---|---|---|
error_type | type[Exception] | Exception class to match. |
status_code | int | HTTP status code to use. |
Example
@error_status(PaymentDeclined, 402)class BillingController(Controller): ...@error_status(PaymentDeclined, 402)class BillingController(Controller): ...
def file( default: Any = ..., validation: Callable[Ellipsis, Any] | None = None, pipes: list[Any] | None = None ) -> Any
file_response
Section titled “file_response”
def file_response( path: str | Path, status_code: int = 200, headers: dict[str, str] | None = None, media_type: str | None = None, filename: str | None = None, content_disposition_type: str = 'attachment' ) -> FileResponse
def form( default: Any = ..., alias: str | None = None, validation: Callable[Ellipsis, Any] | None = None, pipes: list[Any] | None = None ) -> Any
def get( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
Example
@get("/users")async def list_users(request):return {"users": []}get_current_user_optional
Section titled “get_current_user_optional”
async def get_current_user_optional(request: Request) -> Any | None
get_current_user_required
Section titled “get_current_user_required”
async def get_current_user_required(request: Request) -> Any
get_request_id
Section titled “get_request_id”
async def get_request_id(request: Request) -> str
get_version
Section titled “get_version”
def get_version(request: Request) -> str
| Parameter | Type | Description |
|---|---|---|
request | Request | The HTTP request |
| Type | Description |
|---|---|
| str | API version string |
def guard(*guard_classes: type[Any] | Any) -> Any
Attaches one or more guards to a route handler or controller class. Guards are executed in order; the first failure returns a 403 response.
| Parameter | Type | Description |
|---|
| Type | Description |
|---|---|
| Any | Decorator that attaches guards to the target. |
Example
@guard(AuthGuard)async def profile(self): ...
@guard(AuthGuard, PermissionGuard("users:write"))async def create_user(self): ...@guard(AuthGuard)async def profile(self): ...
@guard(AuthGuard, PermissionGuard("users:write"))async def create_user(self): ...
def head( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
header
Section titled “header”
def header( default: Any = ..., alias: str | None = None, validation: Callable[Ellipsis, Any] | None = None, pipes: list[Any] | None = None ) -> Any
html_response
Section titled “html_response”
def html_response( content: str, status_code: int = 200, headers: dict[str, str] | None = None ) -> HTMLResponse
injectable
Section titled “injectable”
def injectable(cls: type[T]) -> type[T]
Each DI resolution creates a new instance of the class (transient scope). This is the quickstart alias for the transient-scope decorator.
Returns the class unchanged, so the decorator is transparent.
| Parameter | Type | Description |
|---|---|---|
cls | type[T] | The class to register as a transient (per-resolve) service. |
| Type | Description |
|---|---|
| type[T] | The original *cls* with the injectable marker applied. |
Example
from quadkit.web import app, get, injectablefrom quadkit.logging import get_logger
logger = get_logger(__name__)
@injectableclass RequestLogger: def log(self, msg: str) -> None: logger.info("request_log", message=msg)from quadkit.web import app, get, injectablefrom quadkit.logging import get_logger
logger = get_logger(__name__)
@injectableclass RequestLogger: def log(self, msg: str) -> None: logger.info("request_log", message=msg)json_response
Section titled “json_response”
def json_response( content: Any, status_code: int = 200, headers: dict[str, str] | None = None ) -> JSONResponse
options
Section titled “options”
def options( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
def patch( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
def path( alias: str | None = None, validation: Callable[Ellipsis, Any] | None = None, pipes: list[Any] | None = None ) -> Any
def post( path: str, **kwargs: Any ) -> Callable[[F], F]
Parameters annotated with a Pydantic BaseModel or DomainModel
subclass are automatically deserialised from the JSON request body by
ParameterBinder — no
body() decorator is required.
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
Example
@post("/users")async def create_user(request):data = await request.json()return {"id": 1, "data": data}
def put( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
def query( default: Any = ..., alias: str | None = None, validation: Callable[Ellipsis, Any] | None = None, pipes: list[Any] | None = None ) -> Any
redirect_response
Section titled “redirect_response”
def redirect_response( url: str, status_code: int = 302, headers: dict[str, str] | None = None ) -> RedirectResponse
register_controller
Section titled “register_controller”
def register_controller(controller_cls: type[Controller]) -> None
Registers the controller in both the active RouteRegistry and
ControllerRegistry, ensuring synchronization and preventing lookup
inconsistencies. With no active registration_context this is a
no-op (with a warning) — import-time side effects on shared module
state are gone; declare controllers via WebProvider(controllers=...)
or @module(controllers=...) instead.
render_template
Section titled “render_template”
def render_template( name: str, context: dict[str, Any] | None = None, templates: Jinja2Templates | None = None, **kwargs: Any ) -> str
def roles( *role_names: str, authorizer: Any | None = None ) -> Any
The authorizer dependency must be injected.
| Parameter | Type | Description |
|---|---|---|
authorizer | Any | None | Required AuthorizerProtocol instance, typically resolved from the container during application startup. |
| Type | Description |
|---|---|
| Any | Decorator that attaches a RoleGuard to the target. |
Note
Guarded handlers must declare request: Request as a parameter —
the guard locates the principal on request.state.user at runtime
(stamp it from your authentication middleware; see
AuthenticatorProtocol).
Example
authorizer = await container.resolve(AuthorizerProtocol)
@roles("admin", authorizer=authorizer)async def admin_only(self): ...
@roles("admin", "moderator", authorizer=authorizer)async def manage_content(self): ...authorizer = await container.resolve(AuthorizerProtocol)
@roles("admin", authorizer=authorizer)async def admin_only(self): ...
@roles("admin", "moderator", authorizer=authorizer)async def manage_content(self): ...singleton
Section titled “singleton”
def singleton(cls: type[T]) -> type[T]
Combines the quadkit.di.singleton DI marker (which sets
__quadkit_injectable__) with a direct entry in the quickstart
service registry so the class is discovered regardless of whether it
is defined at module scope.
Returns the class unchanged, so the decorator is transparent.
| Parameter | Type | Description |
|---|---|---|
cls | type[T] | The class to register as a container-managed singleton. |
| Type | Description |
|---|---|
| type[T] | The original *cls* with the injectable marker applied. |
Example
from quadkit.web import app, get, singleton
@singletonclass UserRepo: async def find(self, user_id: str) -> dict: return {"id": user_id}from quadkit.web import app, get, singleton
@singletonclass UserRepo: async def find(self, user_id: str) -> dict: return {"id": user_id}streaming_response
Section titled “streaming_response”
def streaming_response( content: AsyncGenerator[bytes, None] | Iterator[bytes], status_code: int = 200, headers: dict[str, str] | None = None, media_type: str | None = None ) -> StreamingResponse
throttle
Section titled “throttle”
def throttle( rate: str, *, by: Literal['user', 'ip', 'endpoint'] = 'user' ) -> Callable[[Any], Any]
This is ergonomic sugar over the lower-level rate_limit decorator.
| Parameter | Type | Description |
|---|---|---|
rate | str | Rate string in `" format, e.g. "30/minute", "5/hour", "100/second"`. |
by | Literal['user', 'ip', 'endpoint'] | Scope for the rate limit key: * `"user" — per authenticated user (falls back to IP when request.state.user is not set). * "ip" — per client IP address. * "endpoint"` — per route path + HTTP method. |
| Type | Description |
|---|---|
| Callable[[Any], Any] | Decorator that enforces the rate limit on the decorated handler. |
| Exception | Description |
|---|---|
| ValueError | If *rate* is not a valid rate string. |
Example
@get("/search")@throttle("30/minute")async def search(self, request: Request) -> list[dict]: ...
@post("/upload")@throttle("5/hour", by="user")async def upload(self, request: Request) -> dict: ...@get("/search")@throttle("30/minute")async def search(self, request: Request) -> list[dict]: ...
@post("/upload")@throttle("5/hour", by="user")async def upload(self, request: Request) -> dict: ...
def trace( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the route. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured route handler function. |
use_interceptors
Section titled “use_interceptors”
def use_interceptors(*interceptors: Any) -> Any
Interceptors wrap handler execution: code before
await next_handler.handle() runs on the way in; code after runs on
the way out and may transform the result. Applied to a controller
class, the interceptors cover every route the class declares;
handler-level interceptors take precedence.
| Parameter | Type | Description |
|---|
Example
@use_interceptors(EnvelopeInterceptor())class ReportsController(Controller): ...
@use_interceptors(AuditLogInterceptor())async def sensitive_action(self): ...@use_interceptors(EnvelopeInterceptor())class ReportsController(Controller): ...
@use_interceptors(AuditLogInterceptor())async def sensitive_action(self): ...version
Section titled “version”
def version(api_version: str) -> Callable[[Any], Any]
Usage
@version("1")class UsersController(Controller): ...
@version("2")class UsersV2Controller(Controller): ...@version("1")class UsersController(Controller): ...
@version("2")class UsersV2Controller(Controller): ...websocket
Section titled “websocket”
def websocket( path: str, **kwargs: Any ) -> Callable[[F], F]
| Parameter | Type | Description |
|---|---|---|
path | str | URL path pattern for the WebSocket endpoint. **kwargs: Additional route configuration. |
| Type | Description |
|---|---|
| Callable[[F], F] | Configured WebSocket handler function. |
websocket_handler
Section titled “websocket_handler”
def websocket_handler( path: str, *, ping_interval: int | None = None, ping_timeout: int | None = None, max_connections_per_user: int | None = None ) -> Any
This decorator registers the handler class with the router and configures the WebSocket path.
| Parameter | Type | Description |
|---|---|---|
path | str | URL path for the WebSocket endpoint (can include path parameters) |
ping_interval | int | None | Override default ping interval |
ping_timeout | int | None | Override default ping timeout |
max_connections_per_user | int | None | Override max connections per user |
Example
@websocket_handler("/ws/chat/{room}")class ChatHandler(AbstractWebSocketHandler): async def on_connect(self, websocket): await websocket.accept() await self.broadcast({"type": "join", "user": "..."})
async def on_message(self, websocket, message): await self.broadcast(message)
async def on_disconnect(self, websocket): await self.broadcast({"type": "leave", "user": "..."})Exceptions
Section titled “Exceptions”ConflictError
Section titled “ConflictError”409 Conflict.
def __init__( detail: str = 'Conflict', cause: Exception | None = None ) -> None
HTTPError
Section titled “HTTPError”Base HTTP error, compatible with QuadkitError.
def __init__( status_code: int, detail: str = '', headers: dict[str, str] | None = None, code: str | None = None, cause: Exception | None = None ) -> None
InternalServerError
Section titled “InternalServerError”500 Internal Server Error.
def __init__( detail: str = 'Internal Server Error', code: str = 'INTERNAL_SERVER_ERROR', cause: Exception | None = None ) -> None
NotFoundError
Section titled “NotFoundError”404 Not Found.
def __init__( detail: str = 'Not Found', cause: Exception | None = None ) -> None
RateLimitError
Section titled “RateLimitError”429 Too Many Requests.
def __init__( detail: str = 'Too Many Requests', retry_after: int | None = None, cause: Exception | None = None ) -> None
TooManyConnectionsError
Section titled “TooManyConnectionsError”503 Service Unavailable — connection limit reached.
Raised when a streaming endpoint (e.g. SSE) has reached its
max_connections cap and cannot accept further connections.
def __init__( detail: str = 'Too many active connections', cause: Exception | None = None ) -> None