Backend adapter READMEs — DX surface + codegen invocation
mizan-django/README.md:
- Updated install path (was pointing at the old `subdirectory=django` git
layout from before the backends/ restructure).
- Dropped the dead "monorepo root README" link (the root README was
removed earlier in the substrate-restoration work).
- Fixed the apps.py example — convention is `clients.py` per MIZAN.md,
not `mizan_clients.py`.
- Added the `mizan_clients()` auto-discovery pattern (it was missing).
- Added a Generate-the-frontend section: config shape + CLI invocation
+ the resulting <MizanContext>/use{Hook}() React surface.
- Tightened decorator-parameter overview to a single block covering the
full @client surface.
mizan-fastapi/README.md (new):
- Mirrors mizan-django's structure for consistency.
- Opens with the AFI-common scope: forms/channels/shapes/SSR are out of
scope on the FastAPI side; FastAPI projects use native equivalents.
- Setup shows app.add_exception_handler wiring for MizanError +
RequestValidationError so every error surface goes through the same
envelope the kernel parses.
- Calls out explicit register() (no AppConfig.ready() analog on FastAPI;
registrations live in main.py or an imported clients.py).
- Auth-integration section explains the request.state.user middleware
contract the executor expects.
- Codegen section shows the source.fastapi config shape that points at
the new `python -m mizan_fastapi.cli <module>` schema export.
- Closes with pointers to AFI conformance + the e2e harness so a reader
can verify the adapter's claims.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
194
backends/mizan-fastapi/README.md
Normal file
194
backends/mizan-fastapi/README.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# 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 lives in `backends/mizan-django/generate/` (the codegen package
|
||||
is framework-agnostic; the directory name is historical). Point a config at
|
||||
your FastAPI app and run the CLI:
|
||||
|
||||
```js
|
||||
// frontend/fastapi.config.mjs
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const root = path.resolve(__dirname, "..")
|
||||
|
||||
export default {
|
||||
source: {
|
||||
fastapi: {
|
||||
module: "main", // module to import for @client side effects
|
||||
cwd: path.join(root, "backend"), // python cwd for module resolution
|
||||
command: ["uv", "run", "python"], // optional — defaults to ["python"]
|
||||
},
|
||||
},
|
||||
output: "src/api",
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
node path/to/mizan-django/generate/generator/cli.mjs --config fastapi.config.mjs
|
||||
```
|
||||
|
||||
The codegen drives `python -m mizan_fastapi.cli <module>` under the hood,
|
||||
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.cli <module>
|
||||
```
|
||||
|
||||
Imports the named module (which must register every `@client` function as
|
||||
import-time side effects), then prints the OpenAPI schema as JSON to stdout.
|
||||
Mirrors mizan-django's `manage.py export_mizan_schema` 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).
|
||||
Reference in New Issue
Block a user