Pull cache/keys.py (HMAC cache key derivation) and mwt.py (Mizan Web Token) out of backends/mizan-django and into a new cores/mizan-python package. mizan-django re-imports them via the new mizan_core module. Naming: directory cores/mizan-python/, distribution mizan-core, importable module mizan_core. mizan-django keeps its existing 'mizan' distribution slot on PyPI; the two coexist as distinct packages. Wiring: - backends/mizan-django/pyproject.toml gains a 'mizan-core' dep with a [tool.uv.sources] path entry (editable install from ../../cores/mizan-python). - Makefile install target prepends 'cd cores/mizan-python && uv pip install -e .' - 3 import sites in mizan-django updated: cache/__init__.py, jwt/functions.py, client/executor.py — all now import from mizan_core. Test split: - 3 unit-test classes (CacheKeyDerivationTests, MWTCreationTests, PermissionKeyTests) move to cores/mizan-python/tests/, rewritten against unittest.TestCase (no Django dep). The cross-language pin test (pinned HMAC hex digests against mizan-ts) moves with CacheKeyDerivationTests. - Integration tests stay in mizan-django (CacheBackendTests, CachePurgeTests, CacheIntegrationTests, RevParameterTests, MWTAuthIntegrationTests) — they need the Django request flow. Verified: - mizan-core: 15/15 pass (incl. cross-language pin) - mizan-django: 348 pass, 21 skip, 0 fail - mizan-ts: edge-compat 34/34 pass — protocol invariant holds, the moved Python derive_cache_key still produces the exact hex digests TS pins against. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mizan (Python)
Django server functions framework. See the monorepo root for full documentation.
Install
uv add "mizan[channels,allauth] @ git+https://git.impactsoundworks.com/isw/mizan.git#subdirectory=django"
Setup
# settings.py
INSTALLED_APPS = ["mizan", ...]
# urls.py
path("api/mizan/", include("mizan.urls"))
# asgi.py (optional, for WebSocket)
from mizan import wrap_asgi
application = wrap_asgi(get_asgi_application())
Define Functions
from mizan.client import client
from mizan.setup.registry import register
from pydantic import BaseModel
class Output(BaseModel):
message: str
@client
def echo(request, text: str) -> Output:
return Output(message=text)
register(echo, "echo")
Register in apps.py:
def ready(self):
import myapp.mizan_clients
Auth
@client(auth=True) # requires authentication
@client(auth='staff') # requires is_staff
@client(auth='superuser') # requires is_superuser
@client(auth=my_callable) # custom check
Contexts
@client(context='global') # fetched once, SSR-hydrated, becomes useCurrentUser()
@client(context='local') # fetched with params, becomes <GreetProvider>
Forms
from mizan.forms import mizanFormMixin, mizanFormMeta
class ContactForm(mizanFormMixin, forms.Form):
mizan = mizanFormMeta(name="contact", title="Contact Us")
name = forms.CharField()
email = forms.EmailField()
def on_submit_success(self, request):
return {"sent": True}
Auto-registers contact.schema, contact.validate, contact.submit. Generates useContactForm() with Zod validation.
Channels
from mizan.channels import ReactChannel
class ChatChannel(ReactChannel):
class Params(BaseModel):
room: str
class DjangoMessage(BaseModel):
text: str
def authorize(self, params):
return self.user.is_authenticated
def group(self, params):
return f"chat_{params.room}"
Generates useChatChannel({ room }).
Running Tests
uv sync --extra dev --extra channels
uv run pytest