"""ASGI lifespan adapter for the application runtime owner.""" from typing import Any from loguru import logger from starlette.types import ASGIApp, Receive, Scope, Send from .application import ApplicationRuntime, startup_failure_message class RuntimeASGIApp: """Delegate HTTP to FastAPI and lifespan to `ApplicationRuntime`.""" def __init__(self, app: ASGIApp, runtime: ApplicationRuntime) -> None: self.app = app self.runtime = runtime def __getattr__(self, name: str) -> Any: return getattr(self.app, name) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] != "lifespan": await self.app(scope, receive, send) return await self._lifespan(receive, send) async def _lifespan(self, receive: Receive, send: Send) -> None: started = False while True: message = await receive() if message["type"] == "lifespan.startup": try: await self.runtime.start() except Exception as exc: await send( { "type": "lifespan.startup.failed", "message": startup_failure_message( self.runtime.settings, exc, ), } ) return started = True await send({"type": "lifespan.startup.complete"}) continue if message["type"] == "lifespan.shutdown": if started: try: closed = await self.runtime.close() except Exception as exc: logger.error( "Shutdown failed: exc_type={}", type(exc).__name__, ) await send({"type": "lifespan.shutdown.failed", "message": ""}) return if not closed: await send({"type": "lifespan.shutdown.failed", "message": ""}) return await send({"type": "lifespan.shutdown.complete"}) return