import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse # uvicorn's dictConfig only configures uvicorn.* loggers; the root logger # ends up with no handler, so app.* records are silently discarded. # Give the app namespace its own StreamHandler to guarantee output. _app_logger = logging.getLogger("app") _app_logger.setLevel(logging.INFO) if not _app_logger.handlers: _h = logging.StreamHandler() _h.setFormatter(logging.Formatter("%(levelname)-8s [%(name)s] %(message)s")) _app_logger.addHandler(_h) _app_logger.propagate = False log = logging.getLogger(__name__) from app.database import engine, Base from app.routers import libraries, media, tags, search from app.services import watcher as watcher_service import app.models # noqa: F401 — registers models with Base.metadata @asynccontextmanager async def lifespan(app: FastAPI): log.info("Creating database tables...") async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) log.info("Starting library watchers...") await watcher_service.start_all() log.info("Startup complete.") yield await watcher_service.stop_all() app = FastAPI(title="MediaLore", lifespan=lifespan) @app.exception_handler(Exception) async def unhandled_exception_handler(request: Request, exc: Exception): log.exception("Unhandled error on %s %s", request.method, request.url) return JSONResponse(status_code=500, content={"detail": str(exc)}) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) app.include_router(libraries.router, prefix="/api") app.include_router(media.router, prefix="/api") app.include_router(tags.router, prefix="/api") app.include_router(search.router, prefix="/api")