from __future__ import annotations import asyncio from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from app.logging_config import setup_logging from app.db import init_db from app.routers import biomass, ingest from app.services.action_watch import run_action_watch_loop from app.services.measure_watch import run_measure_watch_loop from app.settings import get_settings setup_logging() @asynccontextmanager async def lifespan(app: FastAPI): setup_logging() s = get_settings() init_db(s) s.media_root.mkdir(parents=True, exist_ok=True) s.stream_tmp_dir.mkdir(parents=True, exist_ok=True) tasks: list[asyncio.Task[None]] = [] if s.action_watch_dir is not None: tasks.append(asyncio.create_task(run_action_watch_loop(s))) if s.measure_watch_dir is not None: tasks.append(asyncio.create_task(run_measure_watch_loop(s))) yield for t in tasks: t.cancel() for t in tasks: try: await t except asyncio.CancelledError: pass app = FastAPI(title="Fish API", lifespan=lifespan) app.include_router(ingest.router) app.include_router(biomass.router) _settings = get_settings() _settings.media_root.mkdir(parents=True, exist_ok=True) app.mount( "/media", StaticFiles(directory=str(_settings.media_root)), name="media", ) @app.get("/") async def root(): return { "service": "fish-api", "docs": "/docs", "ingest": "/api/v1/ingest/", "biomass_camera": "/api/v1/biomass/real/camera/", "biomass_health": "/api/v1/biomass/health/result/", "note": "若配置了 ACTION_WATCH_DIR / MEASURE_WATCH_DIR,启动后会后台监控对应目录。", }