"""FastAPI application.""" from __future__ import annotations import asyncio import contextlib from collections.abc import AsyncGenerator import sentry_sdk import structlog from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse from marketing_intelligence.api import error_handlers from marketing_intelligence.api.routers import campaigns, infra from marketing_intelligence.core.config import settings logger = structlog.get_logger("api") # Strong references prevent tasks from being GC'd before they complete. _background_tasks: set[asyncio.Task[str | None]] = set() @contextlib.asynccontextmanager async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]: from marketing_intelligence.agent_workflows.discovery_relevant import ( resume_discovery_relevant, ) from marketing_intelligence.storage.manifest import find_running_runs running = await find_running_runs() for campaign_id, run_id in running: logger.info("api.resume_run", campaign_id=campaign_id, run_id=run_id) task = asyncio.create_task(resume_discovery_relevant(campaign_id, run_id)) _background_tasks.add(task) task.add_done_callback(_background_tasks.discard) yield app = FastAPI( title="Marketing Intelligence", description="Marketing Intelligence AI Agent Service", lifespan=lifespan, default_response_class=JSONResponse, ) app.include_router(infra.router) app.include_router(campaigns.router) app.add_exception_handler(Exception, error_handlers.default_error_handler) app.add_exception_handler( HTTPException, error_handlers.http_error_handler, # type: ignore[arg-type] ) if settings.sentry_dsn: sentry_sdk.init(dsn=settings.sentry_dsn)