Team Ai
Apppublic

kernelmind/Resume-Screener-API

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
main.py170 linesDownload Raw Back to src
1"""2Resume Screener API - Main Application Entry Point.3 4This is the FastAPI application factory with:5- Lifespan event for model preloading (not reloaded per request)6- SlowAPI rate limiting (prevents API abuse)7- Custom exception handlers (clean error responses)8- CORS middleware (if needed for web frontends)9 10Run with: uvicorn src.main:app --reload11"""12 13from contextlib import asynccontextmanager14from typing import AsyncGenerator15 16from fastapi import FastAPI, Request17from fastapi.middleware.cors import CORSMiddleware18from fastapi.responses import JSONResponse, FileResponse19from fastapi.staticfiles import StaticFiles20import os21from slowapi import Limiter, _rate_limit_exceeded_handler22from slowapi.errors import RateLimitExceeded23from slowapi.util import get_remote_address24 25from src.api.routes import screen_router, rank_router26from src.auth.routes import router as auth_router27from src.core.config import get_settings28from src.core.exceptions import (29    ResumeScreenerError,30    InvalidFileError,31    GeminiAPIError,32    resume_screener_error_handler,33    invalid_file_error_handler,34    gemini_api_error_handler,35    unhandled_exception_handler,36)37from src.core.logging import setup_logging, logger38from src.services.embedding_service import initialize_embedding_model39from src.database import init_db40 41 42# Initialize rate limiter43limiter = Limiter(key_func=get_remote_address)44 45 46@asynccontextmanager47async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:48    """49    Application lifespan handler.50    51    Runs once at startup (before any requests) and once at shutdown.52    We use this to:53    1. Configure logging54    2. Load the embedding model INTO MEMORY (avoid per-request loading)55    56    Why lifespan instead of @app.on_event("startup")?57    - on_event is deprecated in FastAPI 0.100+58    - lifespan gives us proper async context management59    - Cleaner resource cleanup on shutdown60    """61    # === STARTUP ===62    settings = get_settings()63    64    # Configure logging first65    setup_logging(level=settings.log_level)66    logger.info("Resume Screener API starting up...")67    68    # Load embedding model into memory69    # This takes 2-5 seconds but only happens ONCE at startup70    logger.info("Loading embedding model (this may take a few seconds)...")71    try:72        initialize_embedding_model()73        logger.info("Embedding model loaded successfully")74    except Exception as e:75        logger.error(f"Failed to load embedding model: {e}")76    77    # Initialize database78    logger.info("Initializing user database...")79    init_db()80    logger.info("Database ready")81    82    logger.info("Resume Screener API ready for requests")83    84    yield  # Application runs here85    86    # === SHUTDOWN ===87    logger.info("Resume Screener API shutting down...")88 89 90# Create FastAPI app91app = FastAPI(92    title="Resume Screener API",93    description="""94    Production-grade Resume Screening & Ranking API.95    96    Upload a PDF resume and job description to get:97    - Similarity score (0-1)98    - Extracted skills and experience99    - Gap analysis showing what the candidate is missing100    """,101    version="1.0.0",102    lifespan=lifespan,103    docs_url="/docs",104    redoc_url="/redoc",105)106 107# === MIDDLEWARE ===108 109# Rate limiting110app.state.limiter = limiter111app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)112 113# CORS - fully permissive for development114app.add_middleware(115    CORSMiddleware,116    allow_origins=["*"],117    allow_credentials=False,  # Disabled to allow wildcard origins118    allow_methods=["*"],119    allow_headers=["*"],120)121 122 123# === EXCEPTION HANDLERS ===124# Order matters - more specific handlers first125 126app.add_exception_handler(InvalidFileError, invalid_file_error_handler)127app.add_exception_handler(GeminiAPIError, gemini_api_error_handler)128app.add_exception_handler(ResumeScreenerError, resume_screener_error_handler)129app.add_exception_handler(Exception, unhandled_exception_handler)130 131 132# === ROUTES ===133 134app.include_router(auth_router)  # Auth routes (public)135app.include_router(screen_router)136app.include_router(rank_router)137 138 139# Root endpoint for quick verification140@app.get("/api/v1/health", tags=["health"])141async def health() -> dict:142    """Health check endpoint."""143    return {144        "status": "healthy",145        "service": "Resume Screener API",146    }147 148 149# Mount the frontend static files150# In Docker, the frontend will be built to /app/frontend-next/out151static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "frontend-next", "out")152 153if os.path.exists(static_dir):154    app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")155 156    @app.exception_handler(404)157    async def not_found_handler(request: Request, exc: Exception):158        """Handle 404s by serving index.html (required for SPAs)."""159        return FileResponse(os.path.join(static_dir, "index.html"))160else:161    @app.get("/", tags=["root"])162    async def root() -> dict:163        """Root endpoint with API info (when frontend is not built)."""164        return {165            "service": "Resume Screener API",166            "version": "1.0.0",167            "docs": "/docs",168            "health": "/api/v1/health",169        }170