import asyncio import logging from contextlib import asynccontextmanager from fastapi import FastAPI from backend.bootstrap.container import build_container from backend.bootstrap.logging import configure_logging from backend.bootstrap.settings import Settings from backend.database.migrations import MIGRATIONS, MigrationRunner from backend.http.errors import install_error_handlers from backend.http.request_context import install_request_context from backend.http.router import api_router from backend.jobs.screener import run_screener_scheduler logger = logging.getLogger(__name__) def create_application(settings: Settings | None = None) -> FastAPI: runtime = settings or Settings.from_environment() container = build_container(runtime) @asynccontextmanager async def lifespan(_application: FastAPI): runtime.data_directory.mkdir(parents=True, exist_ok=True) configure_logging(runtime.log_level, runtime.log_file) MigrationRunner(container.database).upgrade(MIGRATIONS) database_status = container.database_status.get() logger.info( "Application started", extra={ "event": "application.started", "context": { "environment": runtime.environment, "schema_version": database_status.schema_version, }, }, ) stop = asyncio.Event() task = None if runtime.environment != "test": task = asyncio.create_task(run_screener_scheduler(container.screener, stop)) try: yield finally: if task: stop.set() await task application = FastAPI( title="小白复盘", version="0.1.0", docs_url="/api/docs" if runtime.debug else None, redoc_url=None, lifespan=lifespan, ) application.state.settings = runtime application.state.container = container install_request_context(application) install_error_handlers(application) application.include_router(api_router, prefix="/api") return application