""" backend/asgi.py ================ ASGI application entrypoint for Fashionistar. HTTP requests stay on Django's standard ASGI app. WebSocket traffic is routed through the modular app registry with JWT query-string authentication. Settings module priority: 1. DJANGO_SETTINGS_MODULE environment variable (set by Dockerfile in prod). 2. Falls back to development settings for local ``uvicorn backend.asgi:application``. """ import os from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application # setdefault only fires when DJANGO_SETTINGS_MODULE is NOT already in the # environment. The production Dockerfile sets it to backend.config.production, # so this default only applies to local development runs. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.config.development") django_asgi_app = get_asgi_application() from backend.websocket_auth import JWTQueryAuthMiddleware # noqa: E402 from backend.websocket_routes import websocket_urlpatterns # noqa: E402 class LifespanASGIWrapper: """ Wrap an ASGI app to handle the 'lifespan' protocol. Django's default ASGI handler does not implement lifespan, causing Uvicorn to emit: "WARNING: ASGI 'lifespan' format appears unsupported." This wrapper silently accepts lifespan startup/shutdown messages. """ def __init__(self, app): self.app = app async def __call__(self, scope, receive, send): if scope["type"] == "lifespan": while True: message = await receive() if message["type"] == "lifespan.startup": await send({"type": "lifespan.startup.complete"}) elif message["type"] == "lifespan.shutdown": await send({"type": "lifespan.shutdown.complete"}) return else: await self.app(scope, receive, send) application = LifespanASGIWrapper( ProtocolTypeRouter( { "http": django_asgi_app, "websocket": JWTQueryAuthMiddleware(URLRouter(websocket_urlpatterns)), } ) )