import asyncio import logging from collections.abc import AsyncIterator from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from app.config import BASE_DIR, get_settings from app.db import db from app.logging_config import setup_logging from app.routers import admin, auth, discover, downloads, library, pages, proxy, search, torznab from app.scrapers.http import close_client from app.services.discover import discover as discover_service from app.services.downloads import download_manager logger = logging.getLogger(__name__) def _log_task_error(task: asyncio.Task) -> None: if task.cancelled(): return exc = task.exception() if exc is not None: logger.warning("Réchauffe découverte échouée : %s", exc) @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: settings = get_settings() setup_logging(settings.debug) await db.connect() await download_manager.start() warmup = asyncio.create_task(discover_service.latest()) warmup.add_done_callback(_log_task_error) logger.info("%s prêt", settings.app_name) yield warmup.cancel() await download_manager.stop() await close_client() await db.close() def create_app() -> FastAPI: settings = get_settings() app = FastAPI(title=settings.app_name, lifespan=lifespan) app.include_router(torznab.router) app.include_router(pages.router) app.include_router(pages.protected) app.include_router(auth.router) app.include_router(search.router) app.include_router(discover.router) app.include_router(downloads.router) app.include_router(library.router) app.include_router(admin.router) app.include_router(proxy.router) app.mount("/static", StaticFiles(directory=BASE_DIR / "app" / "static"), name="static") @app.get("/health") async def health() -> dict[str, str]: return {"status": "ok"} return app app = create_app()