# mizan-fastapi FastAPI backend adapter for the Mizan protocol. One decorator on a server function. Typed React client generated. Invalidation automatic. ## Scope mizan-fastapi's surface is RPC dispatch, context bundling, JSON-body invalidation, auth gating, and channels over a multiplexed WebSocket. Forms, Shapes, and SSR sit outside that surface — a FastAPI project reaches for its own native equivalents (Pydantic, ORM-of-choice, FastAPI's SSR ecosystem). ## Install ```bash uv add mizan-fastapi ``` ## Setup ```python # main.py from fastapi import FastAPI from fastapi.exceptions import RequestValidationError from mizan_fastapi import ( MizanError, mizan_exception_handler, mizan_validation_handler, router as mizan_router, ws_router, ) app = FastAPI() app.include_router(mizan_router, prefix="/api/mizan") app.include_router(ws_router, prefix="/api/mizan") app.add_exception_handler(MizanError, mizan_exception_handler) app.add_exception_handler(RequestValidationError, mizan_validation_handler) ``` The exception handlers render every error path through the Mizan envelope (`{"error": {"code", "message", "details"}}`) so the kernel's `MizanError` parses status + code on the frontend regardless of which failure happened. ## Define server functions ```python from mizan_core.client.function import client from mizan_core.registry import register from pydantic import BaseModel class EchoOutput(BaseModel): message: str @client def echo(request, text: str) -> EchoOutput: return EchoOutput(message=text) register(echo, "echo") ``` mizan-fastapi has no auto-discovery (FastAPI doesn't have an app registry to walk). Register every `@client`-decorated function explicitly. A typical project keeps registrations in `main.py` (alongside the FastAPI app) or in a dedicated `clients.py` imported during startup. ## `@client` parameters ```python @client # plain RPC function @client(context="global") # singleton context — fetched once, SSR-hydrated @client(context="user") # named context — fetched per provider mount @client(affects="user") # mutation — invalidates the user context @client(affects=user_profile) # mutation — invalidates a specific function @client(auth=True) # requires authentication @client(auth="staff") # requires is_staff @client(auth="superuser") # requires is_superuser @client(auth=lambda req: ...) # custom predicate @client(rev=2) # cache revision (busts on bump) ``` Forms parameters are accepted by the decorator (they're a `mizan-core` primitive) and carry no meaning to this adapter. ## Channels A channel is a named fan-out over the WebSocket `ws_router` serves. Subclass `Channel`, declare whichever payload models the channel carries, and register it. The three model names are read from the client's side: `Params` keys the fan-out, `ClientMessage` travels up, `ServerMessage` travels down. ```python from mizan_fastapi import Channel, register_channel from pydantic import BaseModel class Chat(Channel): class Params(BaseModel): room: str class ClientMessage(BaseModel): text: str class ServerMessage(BaseModel): user: str text: str def authorize(self, params: Params) -> bool: return True def receive(self, params: Params, msg: ClientMessage) -> ServerMessage: return self.ServerMessage(user="anon", text=msg.text) register_channel(Chat, "chat") ``` Server code pushes to a group from anywhere: ```python await Chat.push(Chat.ServerMessage(user="system", text="hello"), room="general") ``` Registered channels contribute to the exported IR, so codegen emits the `Params` / `ClientMessage` / `ServerMessage` types and the matching frontend hook. Group membership lives in the process that holds the socket, so a push reaches only the subscribers attached to that process. Fan-out that spans processes is a shared broker in front of `broadcast`. ## Auth integration The executor expects `request.state.user` to be populated by your FastAPI middleware or dependency tree before dispatch: ```python from fastapi import Request @app.middleware("http") async def attach_user(request: Request, call_next): request.state.user = await resolve_user_from_token(request) return await call_next(request) ``` Where `resolve_user_from_token` returns either a user object with `is_authenticated`, `is_staff`, `is_superuser` attributes, or `None` for an anonymous request. The executor branches on those for `auth=True`, `auth="staff"`, `auth="superuser"` requirements. ## Generate the frontend The codegen is the `mizan-generate` Rust binary (source at `protocol/mizan-codegen/`; `protocol/mizan-generate/` is a thin npm launcher that dispatches to the platform binary). Point a `mizan.toml` at your FastAPI app and run the CLI: ```toml # frontend/mizan.toml output = "src/api" targets = ["react"] [source.fastapi] module = "main" # module to import for @client side effects cwd = "../backend" # python cwd for module resolution command = ["uv", "run", "python"] # optional — defaults to ["python"] ``` ```bash mizan-generate --config mizan.toml ``` The codegen drives `python -m mizan_fastapi.ir ` under the hood, parses the emitted KDL IR, then emits Stage 1 (typed `callXxx`/`fetchXxx` over the runtime kernel) + Stage 2 (`` provider, per-context providers, `use{Hook}()` hooks) into `src/api/`. ```tsx // app.tsx import { MizanContext } from "./api" export default function App({ children }) { return {children} } ``` ```tsx // any component import { useEcho, useCurrentUser } from "./api" const echo = useEcho() echo.mutate({ text: "hi" }).then(r => console.log(r.message)) const user = useCurrentUser() // global context — auto-fetched, auto-refreshed on mutation ``` ## Running tests ```bash uv sync --extra dev uv run pytest ``` ## Schema export CLI For codegen consumption (or any tooling that wants the Mizan schema): ```bash python -m mizan_fastapi.ir ``` Imports the named module (which must register every `@client` function as import-time side effects), then prints the Mizan KDL IR to stdout. ## Architecture mizan-fastapi is one of two reference backend adapters (the other is `backends/mizan-django`). Both implement the same Mizan protocol on top of the shared `cores/mizan-python` core (`@client`, registry, MWT, HMAC cache keys). The AFI conformance suite at `tests/afi/` gates that the two adapters emit equivalent schemas for the same registered functions. See `docs/AFI_ARCHITECTURE.md`. A live e2e harness exercises this adapter end-to-end at `examples/fastapi-react-site/` (real Chromium → React with generated hooks → FastAPI server, driven by Playwright).