Skip to main content

quadkit-web

Quadkit

PyPI Python License

The async web layer for Quadkit: controllers with signature-based binding, a Result-to-HTTP bridge, middleware pipelines, and generated OpenAPI docs — built on Starlette, server-backend agnostic.

For application developers building HTTP services: define controllers, attach the web module, and serve on Granian, Uvicorn, or Hypercorn.

The quadkit family

Package Role
quadkit-contracts zero-dependency protocols, types, exception hierarchy
quadkit the framework core — DI container, modules, config, logging, Result
quadkit-web ASGI layer — controllers, routing, middleware, OpenAPI docs
quadkit-cli project scaffolding and code generators
quadkit-testing in-process test beds, fakes, fixtures

Installation

The distribution ships no server by itself — install one backend (or use quadkit[web], which resolves to quadkit-web[granian]):

uv add "quadkit-web[granian]"   # default backend, ASGI
uv add "quadkit-web[uvicorn]"   # alternative backend
uv add "quadkit-web[hypercorn]" # alternative backend

Requires Python >= 3.11.

Minimal working example

from quadkit import Application
from quadkit.web import Controller, WebModule, get
from quadkit.web.server import run_server


class HelloController(Controller):
    @get("/hello")
    async def hello(self, name: str = "world") -> dict:
        return {"message": f"hello, {name}"}


def create_app() -> Application:
    app = Application()
    app.add_modules([WebModule.configure(controllers=[HelloController])])
    return app


if __name__ == "__main__":
    run_server(create_app(), port=8000)

/hello is yours; /docs, /redoc, /openapi.json, and /health come with it. Full walkthrough: your first app, then the web API guide.

Binding, not parsing

Handler signatures are the validation: query(), path(), header(), cookie(), form(), and body() bind and convert request data to typed values — a DomainModel-annotated parameter validates the whole JSON body, and a malformed request is a 422 with per-field errors before your code runs:

from dataclasses import dataclass

from quadkit.domain import DomainModel
from quadkit.web import Controller, body, get, post, query


@dataclass
class CreateOrder(DomainModel):
    sku: str
    qty: int = 1


class OrdersController(Controller):
    @get("/orders")
    async def list_orders(self, page: int = query(1)) -> dict:
        return {"page": page}  # query param, converted to int

    @post("/orders")
    async def create_order(self, order: CreateOrder = body()) -> dict:
        return {"sku": order.sku, "qty": order.qty}  # validated body

Controllers can also be discovered by package instead of listed — WebModule.configure(discover=["my_app.controllers"]). One security note: cookie-based CSRF is on by default, so JSON clients send the X-CSRF-Token header (or you disable CSRF for token-authenticated APIs: web.security.enable_csrf: false). More composition — middleware, guards, per-route rate limits — in the web API guide and the feature tour.

Optional extras

Extra Contents
[granian] / [uvicorn] / [hypercorn] ASGI server backends
[security] itsdangerous — signed tokens
[templates] Jinja2 template rendering
[websocket] websockets — WebSocket support
[client] HTTP client
[test] / [docs] / [dev] / [all] tooling bundles

Public API entry points

from quadkit.web import (
    Controller,
    WebModule,
    get,
    post,
    put,
    patch,
    delete,
    body,
    query,
    path,
    header,
    cookie,
    form,
    HTTPError,
    error_status,
    JSONResponse,
    HTMLResponse,
    StreamingResponse,
    FileResponse,
    RedirectResponse,
    BackgroundTasks,
)
from quadkit.web.config import WebConfig, ServerConfig, RateLimitConfig
from quadkit.web.middleware import MiddlewareRegistry
from quadkit.web.server import run_server, run_server_async

WebModule.configure(controllers=..., middleware=...) is the assembly point; WebModule.stub() gives a no-op web module for unit tests.

Configuration

Everything is typed, validated at boot, and env-overridable (QK_WEB__...):

YAML path Default What it controls
web.server.host / port 0.0.0.0 / 8000 bind address
web.server.backend granian granian, uvicorn, or hypercorn
web.server.workers 1 worker processes (production lever)
web.server.reload false hot reload (development aid)
web.security.enable_csrf true cookie-based CSRF protection
web.security.cors.allowed_origins [] (deny-by-default) CORS allow-list
web.rate_limit.enabled false opt-in rate limiting
web.api_docs.enabled unset → on outside production /docs, /redoc + /openapi.json
web.max_body_size 10 MiB request body cap

Env overrides mirror the YAML path, e.g. QK_WEB__SERVER__PORT=8080, QK_WEB__SECURITY__CORS__ALLOWED_ORIGINS='["https://app.example.com"]'. See configuration.

Error handling

Every failure renders as an RFC 7807 problem body: domain exceptions map through a status table, HTTPError gives direct control, request-body validation answers 422 with per-field errors. The error-handling guide shows all three paths with executable examples.

Testing

quadkit-testing's WebTestBed boots your app in-process and asserts on responses — no listening socket, no mocking:

async def test_hello() -> None:
    from quadkit.testing import WebTestBed

    async with WebTestBed(create_app()) as bed:
        response = bed.get("/hello", params={"name": "quadkit"})
        response.assert_status(200)
        assert response.json == {"message": "hello, quadkit"}

Security

Conservative defaults: CSRF on, CORS deny-by-default, unexpected exceptions contained to a minimal 500 body. Details and hardening: secure configuration; report vulnerabilities privately per SECURITY.md.

Stability

Version 0.0.42 in the 0.x series, released in lockstep with the other four distributions; APIs may change between minor versions until 1.0 — pin an exact version (quadkit-web==0.0.42) or a tight range (>=0.0.42,<0.1.0). Full policy: stability and compatibility.

Apache-2.0 — see LICENSE. "Quadkit" and the Quadkit logo are trademarks of the project — see TRADEMARK.md.

Release files for quadkit-web 0.0.42

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for quadkit-web 0.0.42
File Size Uploaded
quadkit_web-0.0.42.tar.gz 1.3 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for quadkit-web 0.0.42
File Interpreter ABI Platform
quadkit_web-0.0.42-py3-none-any.whl Python 3 none any Details

Total release size: 2.4 MB

Release files / quadkit_web-0.0.42.tar.gz

Download URL quadkit_web-0.0.42.tar.gz
Size 1.3 MB
Tags Source
SHA-256 checksum
How to use checksums
52e0676db2f56d12189e939312d62805d137557610a4e3497320700fab51e53a
BLAKE2b-256 checksum
How to use checksums
3c972f41d7a0423c707e665aac9a8030bfba3ac06e236a4e72a1acb3b3d81034
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.14

Release files / quadkit_web-0.0.42-py3-none-any.whl

Download URL quadkit_web-0.0.42-py3-none-any.whl
Size 1.2 MB
Tags Python 3
SHA-256 checksum
How to use checksums
83d4aa6a7a0290fb977ab92ab3866570235530a59331bec271efedbdb5414d16
BLAKE2b-256 checksum
How to use checksums
e89d8154179b13ee40974860f56973a65aed2150933d1c12558dd8bd922e4dc2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.14

Release history Release notifications | RSS feed

This release

0.0.42 This release

2 release files

0.0.41

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page