edusync / app /main.py
sakshusat's picture
perf(onnx): optimized threading for multi-worker environments
3490e39
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.exceptions import RequestValidationError
from contextlib import asynccontextmanager
from app.core.config import settings
from app.core.database import db
from app.api import endpoints
import uvicorn
import os
import sys
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("face-service-main")
@asynccontextmanager
async def lifespan(app: FastAPI):
# --- BONUS FIX #1: Empty API Key Bypass ---
is_prod = os.environ.get("NODE_ENV") == "production"
api_key = settings.FACE_API_KEY
if not api_key:
if is_prod:
logger.error("❌ FATAL: FACE_API_KEY is not set. Refusing to start in production mode.")
sys.exit(1)
else:
logger.warning("⚠️ WARNING: FACE_API_KEY is missing. Service is UNSECURED. Recommended only for development.")
# Startup
db.connect_db()
# Initial config fetch
settings.update_from_server()
# Start background sync
settings.start_config_sync()
yield
# Shutdown
db.close_db()
app = FastAPI(
title="Face Recognition Attendance Service",
description="High-performance, CPU-optimized face recognition API for EduSync.",
version="1.2.0",
lifespan=lifespan
)
# CORS Middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- FIX #10 (COMPLETE): Python Global Error Handlers ---
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""Handles 422 Unprocessable Entity (Input Validation Error)"""
return JSONResponse(
status_code=400,
content={
"success": False,
"error": "Input validation failed",
"code": "VALIDATION_ERROR",
"details": exc.errors()
}
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""Handles specific HTTP Exceptions"""
return JSONResponse(
status_code=exc.status_code,
content={
"success": False,
"error": exc.detail,
"code": "HTTP_ERROR"
}
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""Catches ALL unhandled exceptions and standardizes the response"""
is_dev = os.environ.get("NODE_ENV") != "production"
# Log the full error internally
logger.error(f"UNHANDLED EXCEPTION: {str(exc)}", exc_info=True)
error_message = str(exc) if is_dev else "An internal server error occurred. Please contact administrator."
return JSONResponse(
status_code=500,
content={
"success": False,
"error": error_message,
"code": "INTERNAL_ERROR"
}
)
# SECURITY MIDDLEWARE: API Key Validation
@app.middleware("http")
async def validate_api_key_middleware(request: Request, call_next):
# Skip validation for health check and docs
if request.url.path in ["/health", "/", "/docs", "/openapi.json"]:
return await call_next(request)
api_key = request.headers.get("X-API-Key")
if not api_key:
return JSONResponse(
status_code=403,
content={
"success": False,
"error": "Forbidden",
"code": "AUTH_REQUIRED",
"message": "X-API-Key header is missing"
}
)
if api_key != settings.FACE_API_KEY:
return JSONResponse(
status_code=403,
content={
"success": False,
"error": "Forbidden",
"code": "INVALID_KEY",
"message": "Invalid API Key provided"
}
)
return await call_next(request)
# Routes
app.include_router(endpoints.router, prefix="/api/v1", tags=["Face Recognition"])
@app.get("/health")
async def health_check():
return {"status": "ok", "success": True, "message": "Service is running optimally."}
@app.get("/")
async def root():
return {"success": True, "message": "EduSync Face Recognition Service Running", "status": "active"}
if __name__ == "__main__":
# Ensure port is an integer
port = int(os.environ.get("PORT", 8000))
is_dev = os.environ.get("NODE_ENV") != "production"
# In production, we should ideally use GUNICORN.
# This direct run is for testing/development.
logger.info(f"🚀 Starting Uvicorn (reload={is_dev}) on port {port}")
uvicorn.run("app.main:app", host="0.0.0.0", port=port, reload=is_dev)