"""Centralized accessors for singleton objects stored on ``app.state``. **Getters** (used by routers): raise 503 when a required dependency is missing, except ``get_store`` which returns ``None``. Initialization is handled directly in ``app.py`` via :class:`AsyncExitStack`. """ from __future__ import annotations from collections.abc import AsyncGenerator from contextlib import AsyncExitStack, asynccontextmanager from fastapi import FastAPI, HTTPException, Request from deerflow.runtime import RunManager, StreamBridge @asynccontextmanager async def langgraph_runtime(app: FastAPI) -> AsyncGenerator[None, None]: """Bootstrap and tear down all LangGraph runtime singletons. Usage in ``app.py``:: async with langgraph_runtime(app): yield """ from deerflow.agents.checkpointer.async_provider import make_checkpointer from deerflow.runtime import make_store, make_stream_bridge async with AsyncExitStack() as stack: app.state.stream_bridge = await stack.enter_async_context(make_stream_bridge()) app.state.checkpointer = await stack.enter_async_context(make_checkpointer()) app.state.store = await stack.enter_async_context(make_store()) app.state.run_manager = RunManager() yield # --------------------------------------------------------------------------- # Getters – called by routers per-request # --------------------------------------------------------------------------- def get_stream_bridge(request: Request) -> StreamBridge: """Return the global :class:`StreamBridge`, or 503.""" bridge = getattr(request.app.state, "stream_bridge", None) if bridge is None: raise HTTPException(status_code=503, detail="Stream bridge not available") return bridge def get_run_manager(request: Request) -> RunManager: """Return the global :class:`RunManager`, or 503.""" mgr = getattr(request.app.state, "run_manager", None) if mgr is None: raise HTTPException(status_code=503, detail="Run manager not available") return mgr def get_checkpointer(request: Request): """Return the global checkpointer, or 503.""" cp = getattr(request.app.state, "checkpointer", None) if cp is None: raise HTTPException(status_code=503, detail="Checkpointer not available") return cp def get_store(request: Request): """Return the global store (may be ``None`` if not configured).""" return getattr(request.app.state, "store", None)