"""FastAPI application factory (架构文档 §3.1 L3 API 层). The API layer only orchestrates: auth, validation, idempotency, quota, unified response, OpenAPI. Business rules live in ``packages/``. """ from __future__ import annotations from collections.abc import AsyncIterator from contextlib import asynccontextmanager from fastapi import FastAPI from apps.api.deps import build_llm_gateway from apps.api.routers import ( agent, auth, auth_sms, events, health, kb, quota, reports, reservations, tasks, uploads, ) from packages.shared.config import Settings, get_settings from packages.shared.db import dispose_engine from packages.shared.handlers import install_exception_handlers from packages.shared.logging import configure_logging, get_logger from packages.shared.middleware import RequestContextMiddleware, install_version_alias from packages.shared.observability import setup_tracing from packages.shared.redis_client import close_redis logger = get_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: settings: Settings = get_settings() app.state.llm_gateway = build_llm_gateway(settings) logger.info( "api_started", env=settings.app_env, api_prefix=settings.api_prefix, llm_primary=settings.llm_primary_provider, llm_model=settings.deepseek_model, ) try: yield finally: await app.state.llm_gateway.aclose() await close_redis() await dispose_engine() logger.info("api_stopped") def create_app(settings: Settings | None = None) -> FastAPI: cfg = settings or get_settings() configure_logging( level=cfg.log_level, json_output=cfg.log_json, extra_redact_keys=cfg.extra_redact_keys ) app = FastAPI( title="美搭 backend", version="0.1.0", description=( "AI 穿搭美学助手服务端。统一响应见架构文档 §9.2;" "所有业务接口在 " + cfg.api_prefix + " 之下(/v1 为等价别名)。" ), openapi_url=f"{cfg.api_prefix}/openapi.json", docs_url=f"{cfg.api_prefix}/docs" if cfg.is_local else None, redoc_url=None, lifespan=lifespan, ) install_version_alias(app, cfg.api_prefix) app.add_middleware(RequestContextMiddleware) install_exception_handlers(app) setup_tracing(app, cfg) app.include_router(health.router) routers = ( auth.router, auth_sms.router, agent.router, quota.router, tasks.router, events.router, uploads.router, kb.router, reports.router, reservations.router, ) for router in routers: app.include_router(router, prefix=cfg.api_prefix) return app app = create_app()