boq-api / main.py
gabcares's picture
Upload 80 files
72fdabd verified
Raw
History Blame Contribute Delete
2.9 kB
import logging
import asyncio
from routers import health, predict, home
from contextlib import asynccontextmanager
from fastapi import FastAPI
# Import deployment functions
from models.artifacts_loader import get_artifacts
from core.config import settings
from core.logging import setup_logging
from core.exceptions import (
ModelPredictionError,
ArtifactLoadError,
generic_exception_handler,
model_prediction_exception_handler,
)
# Setup logging before app startup
setup_logging()
logger = logging.getLogger(__name__)
# -------------------------------------------------------------------------
# 1. Global State Management (The safe way to load ML models in APIs)
# -------------------------------------------------------------------------
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Lifespan context manager — non-blocking startup.
Model loading is fired as a background task so the server yields
immediately and can answer /health/liveness probes right away.
Traffic is withheld naturally by /health/readiness (returns 503)
until `artifacts.is_loaded` becomes True.
"""
logger.info(f"Starting {settings.PROJECT_NAME} v{settings.VERSION}")
async def _load():
try:
await asyncio.to_thread(get_artifacts().load_artifacts)
except ArtifactLoadError as e:
logger.error(f"Failed to load artifacts: {e.detail}")
except Exception as e:
logger.critical(f"Unhandled error loading artifacts: {e}")
# Fire-and-forget — the server becomes ready for probes instantly.
asyncio.create_task(_load())
yield # Server accepts connections NOW (before model loading completes)
# Cleanup on shutdown
logger.info("Shutting down API...")
get_artifacts().clear()
# -------------------------------------------------------------------------
# 2. App Initialization
# -------------------------------------------------------------------------
app = FastAPI(
title=settings.PROJECT_NAME,
description=settings.DESCRIPTION,
version=settings.VERSION,
lifespan=lifespan,
)
# Exception Handlers
app.add_exception_handler(Exception, generic_exception_handler)
app.add_exception_handler(ModelPredictionError, model_prediction_exception_handler)
# -------------------------------------------------------------------------
# 3. API Endpoints / Routers
# -------------------------------------------------------------------------
API_V1_PREFIX = "/api/v1"
app.include_router(home.router) # Serves HTML landing page — no versioning needed
app.include_router(health.router, prefix=API_V1_PREFIX) # GET /api/v1/health
app.include_router(predict.router, prefix=API_V1_PREFIX) # POST /api/v1/predict