Roll the working tree back to the last approved shape, before the post-LICENSE span that false-greened the AFI parity matrix with symbol-presence probes and smuggled an unauthorized SQLAlchemy dependency into FastAPI's Shapes binding.
Forward commit, not a history rewrite — the six commits since 4effcc7 stay in the log as the record of what happened.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mizan-django
Django backend adapter for the Mizan protocol. One decorator on a server function. Typed React client generated. Invalidation automatic.
Install
uv add "mizan[channels]"
# or with allauth integration:
uv add "mizan[channels,allauth]"
Setup
# settings.py
INSTALLED_APPS = ["mizan", "myapp", ...]
MIZAN_CACHE_SECRET = "..." # 32-byte HMAC signing key
MIZAN_CACHE_REDIS_URL = "redis://localhost:6379/0"
MIZAN_MWT_SECRET = "..." # MWT signing key (separate from cache + JWT)
# urls.py
from django.urls import include, path
urlpatterns = [
path("api/mizan/", include("mizan.urls")),
]
# asgi.py — for WebSocket / Channels support
from django.core.asgi import get_asgi_application
from mizan import wrap_asgi
application = wrap_asgi(get_asgi_application())
Define server functions
# myapp/clients.py
from mizan.client import client
from mizan.setup 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")
Auto-discover clients.py modules from each Django app:
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "myapp"
def ready(self) -> None:
from mizan.setup import mizan_clients
mizan_clients("myapp") # imports myapp/clients.py — triggers @client side effects
@client parameters
@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(websocket=True) # WebSocket transport (requires channels)
@client(auth=True) # requires authentication
@client(auth="staff") # requires is_staff
@client(auth="superuser") # requires is_superuser
@client(auth=lambda req: ...) # custom predicate
@client(route="/profile/<id>/") # view-path function (returns HttpResponse)
@client(rev=2) # cache revision (busts on bump)
Forms
Django Forms become server functions + typed React hooks with Zod validation:
from django import forms
from mizan.forms import mizanFormMixin, mizanFormMeta
class ContactForm(mizanFormMixin, forms.Form):
mizan = mizanFormMeta(name="contact", title="Contact Us", submit_label="Send")
name = forms.CharField()
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
def on_submit_success(self, request):
send_email(self.cleaned_data)
return {"sent": True}
Auto-registers contact.schema, contact.validate, contact.submit. Frontend
gets useContactForm().
Channels
WebSocket-native RPC via a flag flip:
from pydantic import BaseModel
from mizan.channels import ReactChannel
class ChatChannel(ReactChannel):
class Params(BaseModel):
room: str
class DjangoMessage(BaseModel):
text: str
user: str
def authorize(self, params):
return self.user.is_authenticated
def group(self, params):
return f"chat_{params.room}"
Frontend gets useChatChannel({ room }).
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). From your frontend
project, point a mizan.toml at the Django backend and run the CLI:
# frontend/mizan.toml
output = "src/api"
targets = ["react"]
[source.django]
manage_path = "../backend/manage.py"
command = ["uv", "run", "python"] # optional — defaults to ["python"]
[source.django.env]
PYTHONPATH = "../backend"
DJANGO_SETTINGS_MODULE = "myproject.settings"
mizan-generate --config mizan.toml
The codegen drives Django's management command (export_mizan_ir) 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/.
// app.tsx
import { MizanContext } from "./api"
export default function App({ children }) {
return <MizanContext baseUrl="/api/mizan">{children}</MizanContext>
}
// 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
uv sync --extra dev --extra channels
uv run pytest
Architecture
mizan-django is one of two reference backend adapters (the other is
backends/mizan-fastapi). Both implement the same Mizan protocol on top of
the shared cores/mizan-python core (@client, registry, MWT, HMAC cache
keys). See docs/AFI_ARCHITECTURE.md.