MegrurNiftiyev's picture
Upload folder using huggingface_hub
215f97f verified
Raw History Blame Contribute Delete
4.84 kB
"""
FastAPI application entrypoint.
Registers all routers and manages the DB connection lifecycle.
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.core.config import settings
from app.core.logging import setup_logging, get_logger
from app.core.firebase import init_firebase
from app.api.routes import classify, model_status, train
from fastapi.responses import RedirectResponse
logger = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan — startup and shutdown hooks."""
# Startup
setup_logging()
logger.info("Starting ML service…")
# Initialize Firebase Admin SDK
init_firebase()
# Warm-load model (fetches active model from Firebase Storage/Firestore or uses DummyModel fallback)
try:
from app.ml.serving.registry import load_active_model
model = await load_active_model()
logger.info("Active model initialized successfully (cached)")
except Exception as e:
logger.warning("Active model initialization warning: %s", str(e))
logger.info("==================================================================")
logger.info("🚀 Swagger UI (Interactive API Docs): http://localhost:8000/api-docs")
logger.info("==================================================================")
yield
# Shutdown
logger.info("ML service shut down")
app = FastAPI(
title="MyGuard ML Service",
description=(
"Internal RETVec+CNN classification service. "
"Called server-to-server by the Node.js backend — not exposed to end users."
),
version="0.1.0",
lifespan=lifespan,
docs_url="/api-docs",
redoc_url="/redoc",
)
# CORS Middleware (Restricts origins to Render backend + Swagger UI / Localhost testing)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS_LIST,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Register routers
app.include_router(classify.router)
app.include_router(model_status.router)
app.include_router(train.router)
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc: RequestValidationError):
"""Format Pydantic validation errors into clean {code, message} JSON."""
msg_parts = []
for err in exc.errors():
loc = ".".join(str(l) for l in err.get("loc", []) if str(l) != "body")
msg = err.get("msg", "Invalid field")
msg_parts.append(f"Field '{loc}' {msg.lower()}" if loc else msg)
message = "; ".join(msg_parts) if msg_parts else "Unprocessable Entity validation error"
return JSONResponse(
status_code=422,
content={
"code": "UNPROCESSABLE_ENTITY",
"message": message,
},
)
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc: StarletteHTTPException):
"""Format HTTP exceptions into clean {code, message} JSON."""
detail = exc.detail
if isinstance(detail, dict):
message = detail.get("error") or detail.get("message") or detail.get("detail") or str(detail)
else:
message = str(detail)
code_map = {
400: "BAD_REQUEST",
401: "UNAUTHORIZED",
403: "FORBIDDEN",
404: "NOT_FOUND",
422: "UNPROCESSABLE_ENTITY",
500: "INTERNAL_SERVER_ERROR",
503: "SERVICE_UNAVAILABLE",
}
code = code_map.get(exc.status_code, "ERROR")
return JSONResponse(
status_code=exc.status_code,
content={
"code": code,
"message": message,
},
)
@app.exception_handler(Exception)
async def global_exception_handler(request, exc: Exception):
"""Catch unhandled internal server exceptions to prevent raw 500 server crashes."""
logger.error("Unhandled server error on %s: %s", request.url.path, str(exc), exc_info=True)
return JSONResponse(
status_code=500,
content={
"code": "INTERNAL_SERVER_ERROR",
"message": "An internal server error occurred while processing the request.",
},
)
@app.get("/", include_in_schema=False)
async def root():
"""Redirect root path to interactive Swagger UI documentation."""
return RedirectResponse(url="/api-docs")
@app.get("/health", tags=["Health"])
async def health_check():
"""Simple liveness probe."""
return {"status": "ok"}