Python SDK for shellbound Django applications. Provides ShellApp, ShardContext, ShellboundMiddleware. Emits Chronicle events to stdout in dev mode. Includes fix for IndexError in apps.py when DJANGO_SETTINGS_MODULE has no dots (e.g. instance_settings). Shard name now falls back safely without eager default argument parsing. Implements SHELLBOUND-APP-0001 §4 (dev mode). Wired into entropyopposition as of 2026-03-18.
66 lines
1.9 KiB
Python
66 lines
1.9 KiB
Python
"""Django middleware — emits APP_DJANGO_REQUEST for every request. Non-fatal."""
|
|
|
|
import logging
|
|
import time
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_emitter = None
|
|
|
|
|
|
def set_emitter(emitter):
|
|
global _emitter
|
|
_emitter = emitter
|
|
|
|
|
|
class ShellboundMiddleware:
|
|
"""WSGI middleware emitting Chronicle events per Django request."""
|
|
|
|
def __init__(self, get_response):
|
|
self.get_response = get_response
|
|
|
|
def __call__(self, request):
|
|
start = time.monotonic()
|
|
response = self.get_response(request)
|
|
duration_ms = (time.monotonic() - start) * 1000
|
|
|
|
try:
|
|
if _emitter:
|
|
_emitter.request(
|
|
method=request.method,
|
|
path=request.path,
|
|
status=response.status_code,
|
|
duration_ms=round(duration_ms, 2),
|
|
actor_did=_extract_did(request),
|
|
view_name=_extract_view(request),
|
|
)
|
|
except Exception as e:
|
|
logger.debug("Chronicle middleware non-fatal: %s", e)
|
|
|
|
return response
|
|
|
|
|
|
def _extract_did(request) -> str:
|
|
"""Extract actor DID from request auth context."""
|
|
if hasattr(request, "auth") and request.auth:
|
|
claims = getattr(request.auth, "payload", {})
|
|
if isinstance(claims, dict):
|
|
did = claims.get("substrate_did", "")
|
|
if did:
|
|
return did
|
|
|
|
if hasattr(request, "user") and request.user and getattr(request.user, "is_authenticated", False):
|
|
user = request.user
|
|
if hasattr(user, "external_id") and user.external_id:
|
|
org = getattr(user, "organization", None)
|
|
if org:
|
|
return f"did:web:guildhouse.dev:{org.slug}:user:{user.external_id}"
|
|
|
|
return ""
|
|
|
|
|
|
def _extract_view(request) -> str:
|
|
resolver = getattr(request, "resolver_match", None)
|
|
if resolver:
|
|
return resolver.view_name or getattr(resolver.func, "__name__", "")
|
|
return ""
|