From bcd35114151541c6588d7a90bbdc0f432900c661 Mon Sep 17 00:00:00 2001 From: Adarsh Gautam <232441988+adarsh-gautam-sys@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:23:35 +0530 Subject: [PATCH] fix: resolve #99 - consolidate duplicate FastAPI instances and metrics endpoint --- app/main.py | 114 +++++++++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/app/main.py b/app/main.py index eeed26e..c0dcce7 100644 --- a/app/main.py +++ b/app/main.py @@ -1,64 +1,35 @@ -from fastapi import FastAPI, Depends +import logging +from fastapi import FastAPI, Depends, HTTPException from fastapi.middleware.cors import CORSMiddleware +from prometheus_client import generate_latest +from starlette.responses import Response from app.core.config import settings from app.core.dependencies import get_current_user - from app.api.v1.auth import router as auth_router from app.api.v1.tenants import router as tenants_router from app.api.v1.document import router as document_router from app.api.v1.rag import router as rag_router -from fastapi import FastAPI -from prometheus_client import generate_latest -from starlette.responses import Response -from core.middleware import MetricsMiddleware -from core.middleware_logging import LoggingMiddleware -from core.rate_limit import init_redis -from fastapi import Depends, HTTPException -from core.dependencies import get_current_user - -@app.get("/metrics") -def metrics(user=Depends(get_current_user)): - if user.get("sub") != "admin": - raise HTTPException(status_code=403, detail="Not allowed") - - return Response(generate_latest(), media_type="text/plain") - - - -@app.on_event("startup") -async def startup(): - await init_redis() - - -app.add_middleware(LoggingMiddleware) - - -app = FastAPI() - -app.add_middleware(MetricsMiddleware) - -@app.get("/metrics") -def metrics(): - return Response(generate_latest(), media_type="text/plain") - +from app.middleware.middleware import MetricsMiddleware +from app.middleware.middleware_logging import LoggingMiddleware +from app.reliability.rate_limit import init_redis from app.db.init_db import init_db +from app.observability.tracing import setup_tracing -# =============================== -# Create FastAPI App -# =============================== -app = FastAPI(title=settings.APP_NAME) +logger = logging.getLogger(__name__) # =============================== -# Initialize DB +# Create FastAPI App # =============================== -@app.on_event("startup") -def startup_event(): - init_db() +app = FastAPI( + title=settings.APP_NAME, + docs_url="/docs", + redoc_url="/redoc", +) # =============================== -# CORS (FINAL & CORRECT) +# CORS Middleware # =============================== app.add_middleware( CORSMiddleware, @@ -72,16 +43,49 @@ def startup_event(): allow_headers=["*"], ) +# =============================== +# Custom Middlewares +# =============================== +app.add_middleware(MetricsMiddleware) +app.add_middleware(LoggingMiddleware) + # =============================== # Routers # =============================== -app.include_router(auth_router) -app.include_router(tenants_router) -app.include_router(document_router) -app.include_router(rag_router) +app.include_router(auth_router, prefix="/api/v1/auth", tags=["auth"]) +app.include_router(tenants_router, prefix="/api/v1/tenants", tags=["tenants"]) +app.include_router(document_router, prefix="/api/v1/documents", tags=["documents"]) +app.include_router(rag_router, prefix="/api/v1/rag", tags=["rag"]) + +# =============================== +# Startup Event (Reliability & Observability) +# =============================== +@app.on_event("startup") +async def startup_event() -> None: + """ + Application startup handler. + Initializes the database and Redis connection pool. + Raises RuntimeError to abort startup if the database is unavailable, + ensuring the application never starts in a broken state. + """ + try: + init_db() + logger.info("Database initialized successfully.") + except Exception as exc: + logger.critical("Database initialization failed: %s", exc, exc_info=True) + raise RuntimeError( + f"Startup aborted — database unavailable: {exc}" + ) from exc + + try: + await init_redis() + logger.info("Redis connection pool ready.") + except Exception as exc: + logger.warning("Redis initialization failed: %s", exc, exc_info=True) + # Redis failure is non-fatal (degraded mode); log and continue. # =============================== -# Health Routes +# Health & Metrics Routes # =============================== @app.get("/") def root(): @@ -98,6 +102,14 @@ def protected(user=Depends(get_current_user)): "user": user, } -from core.tracing import setup_tracing +@app.get("/metrics", include_in_schema=False) +def metrics(user=Depends(get_current_user)): + """Prometheus metrics endpoint — admin only.""" + if user.get("sub") != "admin": + raise HTTPException(status_code=403, detail="Admin access required") + return Response(generate_latest(), media_type="text/plain") +# =============================== +# OpenTelemetry Tracing +# =============================== setup_tracing() \ No newline at end of file