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.45 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.45) or a tight range (>=0.0.45,<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.

Metadata

Release files for quadkit-web 0.0.45

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.45
File Size Uploaded Provenance
quadkit_web-0.0.45.tar.gz 1.2 MB Without provenance Details

Built distribution (wheel)

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

Total release size: 2.4 MB

Release files / quadkit_web-0.0.45.tar.gz

Download URL quadkit_web-0.0.45.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
4d8f1c7f54929e7425589cd078300850ee17399e0adb448ea9480a8c842c0ebd
BLAKE2b-256 checksum
How to use checksums
9c7f2802b88cc1600c4c3e74dfe9105d8c2788914c2a1fe83ea5f925a02485ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

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

Download URL quadkit_web-0.0.45-py3-none-any.whl
Size 1.2 MB
Tags Python 3
SHA-256 checksum
How to use checksums
4d2e8739888be264a5ec3b45b6e8bb2acb9d4c965c732f08beb2ab473166641a
BLAKE2b-256 checksum
How to use checksums
6cb0d9ce6cc5e0251ba43cdf5c982ad08ed204deaae2305c07ef8f3a01addbe1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for quadkit-web 0.0.45

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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