Skip to main content

fastapi-canon

The canonical FastAPI API for people and coding agents: declare features, typed operations and public errors once, then generate the SDK your consumers use. Canon gives recurring application problems a shared solution: feature wiring, Dishka lifecycle, error mapping, streaming contracts and client boilerplate.

Keep business logic in ordinary Python. Canon carries its public contracts from the server into OpenAPI/OpenRPC and generated Python and TypeScript clients. The generated API follows your domain: client.projects.get(), client.projects.events() and client.projects.cancel().

Contents

Core features

Declare Canon provides
Feature + Composition Ordered router/provider installation, one Dishka container and managed startup/shutdown.
Error + ErrorRegistry + raises= Shared public problem definitions for runtime responses, contracts and generated SDKs.
CanonRouter + CanonResponse Familiar FastAPI routes with composable error and success contracts, including JSON, empty and binary responses.
CanonStreamRouter Typed SSE events, declared terminal problems and explicit completion, consumed through async iteration.
RpcChannel + CanonWebSocketRouter Typed WebSocket RPC methods, notifications, message streams and declared socket/binary sessions.
fastapi-canon generate One domain SDK per target, shared models/errors, internal protocol bindings and drift checks.
fastapi-canon check errors Source analysis for missing error mappings/declarations, with explicit incomplete-analysis diagnostics.
Optional WebRTC and telemetry Typed media-session contracts and explicit OpenTelemetry instrumentation at the composition boundary.

The public API guide maps these responsibilities to imports.

Install

The current published release is 0.14.0. See the 0.14.0 upgrade guide for database startup checks, engine instrumentation contexts and explicit Alembic downgrades. The bundled backend skill is installed with fastapi-canon skill install. For earlier versions, also follow the preceding migration sections.

uv add fastapi-canon
uv add "fastapi-canon[sse,websockets]"
uv add --dev "fastapi-canon[codegen]"
uv run fastapi-canon --help

The base includes FastAPI, Pydantic and Dishka. All integrations live in the same fastapi-canon distribution; extras select their dependencies. Add webrtc for media sessions, aiortc for the reference media engine, or otel for telemetry. The optional otel-httpx and otel-sqlalchemy extras enable explicit resource adapters. See installation and migration. Python 3.12–3.14 is supported; see compatibility.

Core API: routes, errors and composition

Save this as src/app.py. A domain exception stays ordinary Python; its public mapping drives both the HTTP response and the exported operation contract.

from fastapi import FastAPI
from pydantic import BaseModel

from fastapi_canon import CanonRouter, Composition, Error, ErrorRegistry, Feature


class ProjectMissing(Exception):
    pass


class Project(BaseModel):
    id: int


project_missing = Error(
    ProjectMissing,
    status=404,
    code="project_missing",
    title="Project not found",
    detail="Choose another project.",
)
errors = ErrorRegistry(
    errors=[project_missing], type_base="https://api.example.com/problems"
)
http = CanonRouter(error_registry=errors)


def require_project(project_id: int) -> None:
    if project_id == 0:
        raise ProjectMissing()


@http.get(
    "/projects/{project_id}",
    operation_id="projects.get",
    response_model=Project,
    raises=[project_missing],
)
async def get_project(project_id: int) -> dict[str, int]:
    require_project(project_id)
    return {"id": project_id}


feature = Feature(name="projects", routers=[http])
composition = Composition(features=(feature,))
app = composition.apply(FastAPI())
composition.validate(app)

GET /projects/1 returns {"id": 1}. GET /projects/0 returns a 404 application/problem+json response with the stable type https://api.example.com/problems/project_missing and code project_missing. The same problem is documented in OpenAPI. An explicit operation_id gives codegen the public SDK name projects.get; response_model=Project supplies its typed result and validates the server's returned data.

A feature can also contribute Dishka providers=, a lifespan= and optional instrumentation=. Composition owns installation and cleanup. Shared RouterPolicy declarations reduce repeated authentication, dependencies and error contracts. See the core API guide, router policies and shared problem contracts.

Streams: typed events from server to SDK

Add a stream to src/app.py, reusing the error registry and lookup above:

from collections.abc import AsyncIterator

from fastapi import Depends
from fastapi_canon.integrations.sse import CanonStreamRouter

streams = CanonStreamRouter(error_registry=errors)


class ProjectEvent(BaseModel):
    sequence: int


@streams.stream(
    "/projects/{project_id}/events",
    operation_id="projects.events",
    dependencies=[Depends(require_project)],
    raises=[project_missing],
    stream_raises=[project_missing],
)
async def events(project_id: int) -> AsyncIterator[ProjectEvent]:
    yield ProjectEvent(sequence=1)
    if project_id == 2:
        raise ProjectMissing()

The event model becomes the generated iterator's item type. raises= describes HTTP admission failures; stream_raises= describes failures after streaming starts. Put admission checks in dependencies: generator bodies run after the response begins.

The generated SDK handles SSE framing and JSON decoding, yields business events, consumes canon.complete, and raises ProblemError on canon.problem. Unexpected EOF raises TransportError; malformed control frames raise ProtocolError. Consumers write an ordinary async for loop and can distinguish completion from a broken connection. No application-specific SSE parser or second error mapping is needed.

See Canon stream contracts, stream clients and terminal problem behavior.

WebSockets: RPC calls as domain methods

Add a WebSocket method using the same public error definition:

from fastapi_canon.integrations.websockets import RpcChannel, CanonWebSocketRouter

projects = RpcChannel("projects", error_registry=errors)


class CancelProject(BaseModel):
    project_id: int


@projects.method(raises=[project_missing])
async def cancel(params: CancelProject) -> None:
    require_project(params.project_id)


websockets = CanonWebSocketRouter("/ws", channels=[projects])

After all route declarations, replace the original composition block with this one to install all three routers and expose the OpenRPC source for generation:

feature = Feature(name="projects", routers=[http, streams, websockets])
composition = Composition(features=(feature,))
app = composition.apply(FastAPI())
composition.validate(app)
contract = websockets.contract(
    title="Projects", base_url="ws://localhost:8000", app=app
)

Install a server and run the application from the project root:

uv add --dev uvicorn
uv run uvicorn src.app:app --reload

The contract describes projects.cancel and its typed parameters and failures. Codegen handles request IDs, serialization, response correlation and typed problem errors; the consumer calls client.projects.cancel(...). HTTP, SSE and RPC share the SDK's public model/error runtime. Protocol bindings stay internal.

WebSockets also support notifications, message streams, typed sockets and binary streams, with explicit session lifecycles. See services and channels, generated clients and socket contracts.

Generate and use the SDK

Save fastapi-canon.toml at the project root. This configuration exports the app above and generates both language targets from its HTTP/SSE and RPC contracts:

[codegen]
version = 1

[codegen.sources.api]
kind = "openapi"
source = "src.app:app"
output = "contracts/openapi.json"

[codegen.sources.control]
kind = "openrpc"
source = "src.app:contract"
output = "contracts/openrpc.json"

[codegen.targets.browser]
language = "typescript"
sources = ["api", "control"]
output = "frontend/generated"

[codegen.targets.python]
language = "python"
sources = ["api", "control"]
output = "api_client"
package = "api_client"
uv run fastapi-canon generate
uv run fastapi-canon generate --check --diff

For the HTTP-only example, omit sources.control and use sources = ["api"] on each target. A stream-only addition uses the same OpenAPI source as HTTP. --check --diff checks generated files and exported contracts without writing; use it in CI to catch drift. Offline generation can consume saved contracts without importing application code.

The generated TypeScript API:

import { createClient, ProblemError } from "./generated";

const client = createClient({ baseUrl: "http://localhost:8000" });
try {
  const project = await client.projects.get({ projectId: 1 });
  for await (const event of client.projects.events({ projectId: project.id })) {
    console.log(event.sequence);
  }
  await client.projects.cancel({ projectId: project.id });
} catch (error) {
  if (error instanceof ProblemError) console.error(error.problem.type);
  else throw error;
} finally {
  await client.close();
}

The same domain API in Python, inside an async function:

from api_client import ApiClient, ProblemError

async with ApiClient("http://localhost:8000") as client:
    try:
        project = await client.projects.get(project_id=1)
        async for event in client.projects.events(project_id=project.id):
            print(event.sequence)
        await client.projects.cancel(project_id=project.id)
    except ProblemError as error:
        print(error.problem.type)

Generated SDKs do not depend on Canon. Python consumers install Pydantic, httpx (or configured httpx2) and websockets for RPC; TypeScript uses fetch/DOM APIs and the ESNext.Disposable type library. Bindings connect lazily, and closing the client releases resources it owns. Shared semantic model identities avoid duplicating DTOs across contracts.

The codegen guide covers naming, model identity, connection/auth options, operation selection and output ownership. It also documents limits: unified HTTP generation supports JSON and declared SSE; unsupported multipart/file operations must be explicitly excluded. WebRTC generation targets TypeScript. The runnable canonical-errors example includes a complete multi-transport configuration.

Check that errors are mapped and declared

An error contract is useful only if it describes the failures the code can raise. Check the core example without importing or starting the application:

uv run fastapi-canon check errors src --strict --require-complete

The checker follows supported endpoint, service, helper and dependency calls. It compares escaping application exceptions with the registry and the combined policy/router/operation raises= declarations:

Finding Meaning Example fix
CAN001 A mapped error is missing from raises=. Restore raises=[project_missing] on get_project.
CAN002 An application exception has no registry mapping. Define its public Error and add it to the router's registry.
CAN003 Analysis could not resolve a declaration or execution path. Inspect the reported path and supported patterns.

For example, remove raises=[project_missing] from the HTTP operation above: the checker follows get_project -> require_project and reports CAN001. --strict fails CI for mapping/declaration gaps; --require-complete also fails for incomplete analysis or no supported operations. --format json produces a machine-readable report for tooling and agents.

This checks supported explicit source paths, not every possible runtime failure. Abstract Dishka ports and dynamic code can leave analysis incomplete. The checker currently covers direct CanonRouter operations, not SSE/RPC/WebRTC handlers. Keep runtime contract validation and behavioral tests. See error checking for boundaries and CI options.

Documentation

The next release includes a build-canon-backend skill for agents working on your application. From your backend project, run:

uv run fastapi-canon skill install

This copies the bundled skill to .agents/skills/build-canon-backend in your project. Use --destination <skill-directory> for another agent's skill location. Existing destinations are never overwritten. The skill loads focused references for folder structure, HTTP features, resources, transports/SDKs and testing.

Goal Start here
Build a feature with routes, providers and lifespans Core API and composition
Choose canonical imports and ownership boundaries Public API
Map domain errors across HTTP, SSE and RPC Shared problems
Check mapping/declaration coverage Error checking
Generate Python and TypeScript domain SDKs Codegen workflow
Build typed SSE streams Stream contracts, SSE guide index
Build WebSocket RPC, streams or sockets WebSocket guide index
Add WebRTC sessions WebRTC contracts
Add tracing and metrics Telemetry
Log HTTP responses and correlate traces Request logging
Upgrade an existing application Package migration, RPC migration
Run a complete application Reference service, HTTP/SSE/RPC example
Contribute or prepare a release Contributing, guardrails, compatibility, releases

Repository

The canonical home for development, issues and future releases is mathisarends/fastapi_canon. The consolidated source returns from mathisarends/canon with its complete Git history. The PyPI project remains fastapi-canon, and Python imports remain fastapi_canon. There is one maintained source repository, not a mirrored pair. See the repository migration for the cutover sequence, publisher setup and historical release locations.

Current releases

Canon 0.14.0 is the current published unified distribution. It adds configurable startup migration checks, engine contexts with declarative SQLAlchemy instrumentation, explicit Alembic downgrades and an installable backend skill for coding agents. See the upgrade guide, migration notes and publication record. The other names below are historical releases from the earlier four-distribution layout; use Canon's feature extras for current development.

Package Version GitHub PyPI
fastapi-canon 0.14.0 Release notes and artifacts Install
fastapi-streams 0.5.0 Release notes and artifacts Install
fastapi-rpckit 0.4.1 Release notes and artifacts Install
fastapi-webrtc 0.3.0 Release notes and artifacts Install

Canon releases use this repository's release list, one Canon version, tag, changelog and artifact set. Historical release URLs and assets stay in the former canon release list and their original repositories.

Repository layout

This repository builds one distribution, fastapi-canon, directly from its root pyproject.toml. The source package is fastapi_canon/; all optional integrations ship in that same wheel. Development uses one root Python environment and one Node toolchain, without a package workspace.

Location Contents
fastapi_canon/ Composition, HTTP contracts, optional integrations and generators
tests/ Separate unit, integration and contract roots; fixtures, TypeScript and browser suites
examples/ Runnable applications and their generated clients, including the reference service
docs/ Library, transport and contributor documentation; predecessor history
scripts/ Development and release entrypoints

See repository architecture for ownership and dependency rules, contributing for root commands, compatibility for tested scope and releases for artifact verification and publication.

The imported repositories remain available in Git history. Current development and future releases use mathisarends/fastapi_canon; historical releases and predecessor repositories are preserved.

MCP features

Install fastapi-canon[mcp] for FastMCP feature mounts with Dishka Inject[T]. See the MCP guide for composition, namespaces, endpoint defaults and supported execution lifetimes.

Metadata

Release files for fastapi-canon 0.15.0

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

Source distribution (sdist)

Source distribution for fastapi-canon 0.15.0
File Size Uploaded Provenance Actions
fastapi_canon-0.15.0.tar.gz 368.5 kB With provenance Details for fastapi_canon-0.15.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-canon 0.15.0
File Interpreter ABI Platform Actions
fastapi_canon-0.15.0-py3-none-any.whl Python 3 none any Details for fastapi_canon-0.15.0-py3-none-any.whl

Total release size: 828.4 kB

Release files / fastapi_canon-0.15.0.tar.gz

Download URL fastapi_canon-0.15.0.tar.gz
Size 368.5 kB
Tags Source
SHA-256 checksum
How to use checksums
4618d3a81e37c67291016b7df8089302fe904fee1e17975bf4defbd9bba5cb2e
BLAKE2b-256 checksum
How to use checksums
8d0794db1db17357888b89db9806e46468183845133b36f5ae85ddbc5c0c6599
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Release files / fastapi_canon-0.15.0-py3-none-any.whl

Download URL fastapi_canon-0.15.0-py3-none-any.whl
Size 459.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ba8070456493fa2e97028f09fed92689c7aaab09176cabcf551d7dc1003f9da7
BLAKE2b-256 checksum
How to use checksums
cd849f1b09ea811dd472024e5c03a746d012364a6efc50e7d3ddd47fd02658c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Security for fastapi-canon 0.15.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for fastapi_canon-0.15.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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