"""FastAPI composition root: wiring, lifespan, middleware, routers.""" from collections.abc import AsyncIterator from contextlib import asynccontextmanager from fastapi import FastAPI, WebSocket from app.api.errors import register_exception_handlers from app.api.health import router as health_router from app.api.middleware import CorrelationIdMiddleware from app.api.rest import subsonic_router from app.api.v1 import api_v1_router from app.core.config import app_version, get_settings from app.core.logging import configure_logging, get_logger from app.infrastructure.cache import close_redis from app.infrastructure.db import dispose_engine log = get_logger(__name__) @asynccontextmanager async def lifespan(_app: FastAPI) -> AsyncIterator[None]: settings = get_settings() log.info("startup", environment=settings.environment) yield log.info("shutdown") await dispose_engine() await close_redis() def create_app() -> FastAPI: settings = get_settings() configure_logging(level=settings.log_level, json=settings.log_json) app = FastAPI( title="mcma-backend", version=app_version(), summary="Self-hosted, offline-first music service.", lifespan=lifespan, ) app.add_middleware(CorrelationIdMiddleware) register_exception_handlers(app) app.include_router(health_router) app.include_router(api_v1_router) app.include_router(subsonic_router, prefix="/rest") @app.websocket("/ws") async def ws_stub(websocket: WebSocket) -> None: await websocket.accept() await websocket.close(1001) return app app = create_app()