izana-django
Django backend adapter for the Izana protocol. One decorator on a server function. Typed React client generated. Invalidation automatic.
Install
uv add "django-izana[channels]"
The distribution is django-izana; the importable package is izana.
Setup
# settings.py
INSTALLED_APPS = ["izana", "myapp", ...]
IZANA_CACHE_SECRET = "..." # 32-byte HMAC signing key
IZANA_CACHE_REDIS_URL = "redis://localhost:6379/0"
IZANA_MWT_SECRET = "..." # MWT signing key (separate from cache + JWT)
# urls.py
from django.urls import include, path
urlpatterns = [
path("api/izana/", include("izana.urls")),
]
# asgi.py — for WebSocket / Channels support
from django.core.asgi import get_asgi_application
from izana import wrap_asgi
application = wrap_asgi(get_asgi_application())
Define server functions
# myapp/clients.py
from izana.client import client
from izana.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 izana.setup import izana_clients
izana_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", "cart"]) # mutation — invalidates several contexts
@client(merge="cart") # mutation — its result splices into the cart context's matching slot
@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 izana.forms import izanaFormMixin, izanaFormMeta
class ContactForm(izanaFormMixin, forms.Form):
izana = izanaFormMeta(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. The
codegen doesn't emit a hook for them; the frontend drives the three through
useIzanaFormCore({ name: "contact" }) from @eralyr/izana-react, which
binds to that package's IzanaProvider rather than the generated
IzanaContext kernel.
Auth-provider forms (django-allauth login, signup, MFA, WebAuthn) live in the
dedicated izana-allauth repository, built on this mixin.
Channels
WebSocket-native RPC via a flag flip. The message slots are named from the
client's point of view: ClientMessage travels client → server,
ServerMessage travels server → client. Declare only the directions the
channel uses.
from pydantic import BaseModel
from izana.channels import Channel
class ChatChannel(Channel):
class Params(BaseModel):
room: str
class ClientMessage(BaseModel):
text: str
class ServerMessage(BaseModel):
text: str
user: str
def authorize(self, params: Params) -> bool:
return self.user.is_authenticated
def group(self, params: Params) -> str:
return f"chat_{params.room}"
def receive(self, params: Params, msg: ClientMessage) -> ServerMessage:
return self.ServerMessage(text=msg.text, user=self.user.email)
Frontend gets useChatChannel({ room }). authorize, group, and
receive may be plain def and use the ORM freely — the consumer runs them
off the event loop, the same way it dispatches RPC — or async def, which it
awaits. Annotate the hooks with your own nested Params/ClientMessage as
above: the base declares them Any (it can't name classes you nest later),
so the annotation is what gives your editor the field names. A channel with
no Params writes params: None = None.
Server code outside a subscription broadcasts with push(). It builds a bare
channel instance (no user), so group() must be derivable from params
alone:
await ChatChannel.push(room="general", message=ChatChannel.ServerMessage(...))
Generate the frontend
The codegen is the izana-generate Rust binary (source at
protocol/izana-codegen/; protocol/izana-generate/ is a thin npm
launcher that dispatches to the platform binary). From your frontend
project, point a izana.toml at the Django backend and run the CLI:
# frontend/izana.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"
izana-generate --config izana.toml
The codegen drives Django's management command (export_izana_ir) under
the hood, parses the emitted KDL IR, then emits Stage 1 (typed
callXxx/fetchXxx over the runtime kernel) + Stage 2 (<IzanaContext>
provider, per-context providers, use{Hook}() hooks) into src/api/.
// app.tsx
import { IzanaContext } from "./api"
export default function App({ children }) {
return <IzanaContext baseUrl="/api/izana">{children}</IzanaContext>
}
// 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
izana-django is one of two reference backend adapters (the other is
backends/izana-fastapi). Both implement the same Izana protocol on top of
the shared cores/izana-python core (@client, registry, MWT, HMAC cache
keys). See docs/AFI_ARCHITECTURE.md.
Metadata
Release files for django-izana 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_izana-1.1.0.tar.gz | 156.7 kB | Without provenance | Details for django_izana-1.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_izana-1.1.0-py3-none-any.whl | Python 3 | none | any | Details for django_izana-1.1.0-py3-none-any.whl |
Total release size: 202.8 kB
Release files / django_izana-1.1.0.tar.gz
| Download URL | django_izana-1.1.0.tar.gz |
|---|---|
| Size | 156.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e816a3f28ad72671a47799411668c0eb46cb464a9dd122b55e993d0e4f277f79
|
|
BLAKE2b-256 checksum How to use checksums |
dab402daba430377d6e77b3ed02f96b3edfb6222f5319bc79b33875d7d95dc79
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / django_izana-1.1.0-py3-none-any.whl
| Download URL | django_izana-1.1.0-py3-none-any.whl |
|---|---|
| Size | 46.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1d9f77c3d2464a52699e9bf0ada51e86454caf9ed1464f5e80c102a7f142a705
|
|
BLAKE2b-256 checksum How to use checksums |
23f3e92049cae81eccbb1536ed85d6e7efdb51a967568850510fa523f9c9fd7d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for django-izana 1.1.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.