YashashviAlva's picture
Initial commit for HF Spaces deploy
7b4f5dd
"""
CodeSentry Backend β€” FastAPI application entry point.
"""
from __future__ import annotations
import logging
import os
from contextlib import asynccontextmanager
from pathlib import Path
from typing import AsyncGenerator
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
load_dotenv()
# Path to the pre-built frontend (populated by Docker build for HF Spaces)
STATIC_DIR = Path(__file__).parent / "static"
from api.routes import router
from privacy.privacy_guard import ZDRMiddleware
# ──────────────────────────────────────────
# Logging
# ──────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("codesentry")
# ──────────────────────────────────────────
# Lifespan (startup / shutdown)
# ──────────────────────────────────────────
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger.info("=" * 60)
logger.info(" CodeSentry Backend starting up")
logger.info(" vLLM endpoint: %s", os.getenv("VLLM_BASE_URL", "http://localhost:8080"))
logger.info(" Model: %s", os.getenv("MODEL_NAME", "Qwen/Qwen2.5-Coder-32B-Instruct"))
logger.info(" Zero Data Retention: ENABLED")
logger.info("=" * 60)
# Pre-warm orchestrator (initialises agents without LLM calls)
from api.routes import get_orchestrator
get_orchestrator()
logger.info("Orchestrator initialised.")
yield
logger.info("CodeSentry Backend shutting down.")
# ──────────────────────────────────────────
# App factory
# ──────────────────────────────────────────
def create_app() -> FastAPI:
app = FastAPI(
title="CodeSentry Backend",
description=(
"AI/ML Code Security Analysis Engine β€” "
"OWASP + OWASP LLM Top-10 scanning powered by Qwen2.5-Coder-32B on AMD MI300X. "
"Zero Data Retention: all inference runs on localhost."
),
version="1.0.0",
lifespan=lifespan,
docs_url="/docs",
redoc_url="/redoc",
)
# ── CORS ────────────────────────────────
allowed_origins = os.getenv("CORS_ORIGINS", "*").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ── ZDR Middleware ───────────────────────
app.add_middleware(ZDRMiddleware)
# ── Routes ──────────────────────────────
app.include_router(router, prefix="/api")
# ── Static Frontend (HF Spaces / Docker deployment) ──────
if STATIC_DIR.is_dir():
# Serve the pre-built React SPA
app.mount("/assets", StaticFiles(directory=str(STATIC_DIR / "assets")), name="assets")
@app.get("/", include_in_schema=False)
async def serve_spa_root():
return FileResponse(str(STATIC_DIR / "index.html"))
# SPA catch-all: any route not matched by /api returns index.html
@app.get("/{full_path:path}", include_in_schema=False)
async def serve_spa_fallback(full_path: str):
# If a real static file exists, serve it (favicon, etc.)
file_path = STATIC_DIR / full_path
if file_path.is_file():
return FileResponse(str(file_path))
return FileResponse(str(STATIC_DIR / "index.html"))
else:
# Dev mode β€” no static build present
@app.get("/", include_in_schema=False)
async def root() -> JSONResponse:
return JSONResponse({
"service": "CodeSentry Backend",
"version": "1.0.0",
"status": "running",
"docs": "/docs",
"health": "/api/health",
})
# ── Global exception handler ─────────────
@app.exception_handler(Exception)
async def global_exception_handler(request, exc: Exception) -> JSONResponse:
logger.error("Unhandled exception: %s", exc, exc_info=True)
return JSONResponse(
status_code=500,
content={"detail": "Internal server error", "error": str(exc)},
)
return app
app = create_app()
# ──────────────────────────────────────────
# Dev runner
# ──────────────────────────────────────────
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host=os.getenv("HOST", "0.0.0.0"),
port=int(os.getenv("PORT", "8000")),
reload=os.getenv("RELOAD", "true").lower() == "true",
log_level="info",
)