The @client decorator + ServerFunction base + composition machinery is mostly framework-agnostic. The only Django couplings were typing (HttpRequest in __init__ and submit_handler signatures) and runtime view-path detection (HttpResponseBase isinstance/issubclass checks). Replaced both with backend-extension hooks: - HttpRequest type hints → Any. Type Protocol can be tightened later. - HttpResponseBase view-path detection → set_framework_response_base(cls) hook in mizan_core.client.function. Backends register their framework's response base at import time. is_framework_response(obj_or_cls) handles both instance and subclass checks via the registered base. mizan-django registers HttpResponseBase via mizan/client/__init__.py before any @client-decorated code is loaded. FastAPI would similarly register starlette.responses.Response. Direct consumers updated: - mizan/setup/discovery.py: ServerFunction import path - mizan/forms/__init__.py: ServerFunction + create_form_functions imports mizan/client/__init__.py keeps its public re-export surface stable so 'from mizan.client import client, ServerFunction, …' continues to work for downstream Django consumers. Verified: - mizan-core: 15/15 - mizan-django: 348 pass, 21 skip, 0 fail - mizan-ts edge-compat: 34/34 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
2.9 KiB
Python
94 lines
2.9 KiB
Python
"""
|
|
mizan Auto-Discovery
|
|
|
|
Scans Django apps for server functions following the 'clients' layer convention:
|
|
- <app>/clients.py
|
|
- <app>/clients/**/*.py
|
|
|
|
Usage in urls.py:
|
|
from mizan.setup.discovery import mizan_clients
|
|
|
|
mizan_clients('apps') # Scans apps/*/clients.py
|
|
mizan_clients('mizan', 'allauth') # Scans mizan/allauth/**/*.py
|
|
|
|
This replaces manual "import to register" patterns with explicit auto-discovery.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from mizan._vendor.app_visitor import DjangoAppVisitor, get_members
|
|
|
|
from mizan_core.registry import register, get_function
|
|
from mizan_core.client.function import ServerFunction
|
|
|
|
|
|
class _RegisterServerFunctions:
|
|
"""Visitor handler that registers ServerFunction subclasses."""
|
|
|
|
def on_module(
|
|
self, app_name: str, path_parts: list[str], members: list[tuple[str, Any]]
|
|
) -> None:
|
|
"""Process discovered module members."""
|
|
for name, member in members:
|
|
# Register ServerFunction subclasses
|
|
if (
|
|
isinstance(member, type)
|
|
and issubclass(member, ServerFunction)
|
|
and member is not ServerFunction
|
|
and hasattr(member, "__name__")
|
|
):
|
|
# Use the function name as registration name
|
|
fn_name = getattr(member, "name", None) or member.__name__
|
|
|
|
# Skip already registered (idempotent)
|
|
if get_function(fn_name) is member:
|
|
continue
|
|
|
|
try:
|
|
register(member, fn_name)
|
|
except ValueError:
|
|
# Already registered with different class - skip
|
|
pass
|
|
|
|
|
|
def mizan_clients(apps_root: str, layer: str = "clients") -> None:
|
|
"""
|
|
Discover and register server functions from Django apps.
|
|
|
|
Scans for the specified layer (default: 'clients') in each app:
|
|
- <app>/<layer>.py
|
|
- <app>/<layer>/**/*.py
|
|
|
|
Args:
|
|
apps_root: Root package containing Django apps (e.g., 'apps')
|
|
layer: Module name pattern to scan (default: 'clients')
|
|
|
|
Example:
|
|
# In urls.py
|
|
mizan_clients('apps') # Scans apps/*/clients.py
|
|
mizan_clients('apps', 'functions') # Scans apps/*/functions.py
|
|
"""
|
|
visitor = DjangoAppVisitor(layer=layer, apps_root=apps_root)
|
|
visitor.visit(_RegisterServerFunctions())
|
|
|
|
from .registry import validate_registry
|
|
validate_registry()
|
|
|
|
|
|
def mizan_module(module_path: str) -> None:
|
|
"""
|
|
Register server functions from a specific module.
|
|
|
|
Use this for library modules that don't follow the app convention.
|
|
|
|
Args:
|
|
module_path: Full module path (e.g., 'mizan.integrations.allauth')
|
|
|
|
Example:
|
|
mizan_module('mizan.integrations.allauth')
|
|
mizan_module('mizan.jwt.functions')
|
|
"""
|
|
members = get_members(module_path)
|
|
handler = _RegisterServerFunctions()
|
|
handler.on_module("", [], members)
|