from __future__ import annotations import logging from contextlib import asynccontextmanager import httpx from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request as StarletteRequest from starlette.responses import Response as StarletteResponse from app.core.audit import SharedBase from app.core.config import settings from app.core.db import create_postgres_engine, create_session_factory from app.core.executor import get_executor, shutdown_executor from app.core.otel import configure_otel, instrument_fastapi, instrument_sqlalchemy, shutdown_otel from app.domain.aw.models import AWBase from app.domain.wwi.models import WWIBase from app.routers import aw, platform, wwi LOGGER = logging.getLogger(__name__) class SecurityHeadersMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: StarletteRequest, call_next) -> StarletteResponse: response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "DENY" response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" return response @asynccontextmanager async def lifespan(app: FastAPI): # --- startup --- providers = configure_otel(settings) LOGGER.info("OTel configured for %s", settings.otel_service_name) pg_engine = create_postgres_engine() instrument_sqlalchemy({"pg": pg_engine}) SharedBase.metadata.create_all(pg_engine) AWBase.metadata.create_all(pg_engine) WWIBase.metadata.create_all(pg_engine) pg_factory = create_session_factory(pg_engine) analytics_client = httpx.AsyncClient( base_url=settings.analytics_service_url, timeout=httpx.Timeout(60.0), ) executor = get_executor() app.state.pg_engine = pg_engine app.state.pg_factory = pg_factory app.state.analytics_client = analytics_client LOGGER.info("Ready: analytics_service=%s thread_pool_workers=%d", settings.analytics_service_url, executor._max_workers) # noqa: SLF001 instrument_fastapi(app) yield # --- shutdown --- LOGGER.info("Shutting down") await analytics_client.aclose() shutdown_executor() pg_engine.dispose() shutdown_otel(providers) def create_app() -> FastAPI: app = FastAPI( title="otel-bi-backend", version="1.0.0", lifespan=lifespan, docs_url="/docs" if settings.app_env != "prod" else None, redoc_url=None, ) app.add_middleware(SecurityHeadersMiddleware) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["GET", "POST", "DELETE"], allow_headers=["Authorization", "Content-Type"], ) app.include_router(platform.router) app.include_router(aw.router) app.include_router(wwi.router) return app app = create_app()