# mizan-fastapi

FastAPI backend adapter for the Mizan protocol. One decorator on a server
function. Typed React client generated. Invalidation automatic.

## Scope

mizan-fastapi targets the **AFI-common subset** — RPC dispatch, context
bundling, JSON-body invalidation, and auth gating. Forms, Channels, Shapes,
and SSR are out of scope for the FastAPI adapter — FastAPI projects use
native equivalents (Pydantic, native WebSockets, ORM-of-choice, FastAPI's
own 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,
)


app = FastAPI()
app.include_router(mizan_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)
```

`websocket=True`, Forms, and Channels parameters are accepted by the
decorator (they're a `mizan-core` primitive) but ignored by mizan-fastapi —
those features only have effect when paired with mizan-django.

## 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 <module>` under the hood,
parses the emitted KDL IR, then emits Stage 1 (typed `callXxx`/`fetchXxx`
over the runtime kernel) + Stage 2 (`<MizanContext>` provider, per-context
providers, `use{Hook}()` hooks) into `src/api/`.

```tsx
// app.tsx
import { MizanContext } from "./api"

export default function App({ children }) {
    return <MizanContext baseUrl="/api/mizan">{children}</MizanContext>
}
```

```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 <module>
```

Imports the named module (which must register every `@client` function as
import-time side effects), then prints the Mizan KDL IR to stdout.
Mirrors mizan-django's `manage.py export_mizan_ir` so the codegen consumes
either backend the same subprocess way.

## 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, 14/14 Playwright tests).
