"""FastAPI app serving the trained model to the web front end. cd ml uvicorn api.main:app --reload --port 8000 """ import os from dotenv import load_dotenv from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from pydantic import BaseModel, Field from api.service import ( EmptyAfterCleaning, analyze, describe_model, load_metrics, plot_path, ) load_dotenv() def allowed_origins(): """Browser origins permitted to call this API.""" raw = os.getenv("ASA_CORS_ORIGINS", "http://localhost:3000") return [origin.strip() for origin in raw.split(",") if origin.strip()] app = FastAPI( title="Arabic Sentiment Analysis API", description="Serves the trained TF-IDF + classifier pipeline.", version="1.0.0", ) app.add_middleware( CORSMiddleware, allow_origins=allowed_origins(), allow_credentials=False, allow_methods=["GET", "POST"], allow_headers=["*"], ) class PredictRequest(BaseModel): """One piece of raw Arabic text to classify.""" text: str = Field(min_length=1, max_length=5000) convert_emojis: bool = True @app.get("/api/health") def health(): try: model = describe_model() except FileNotFoundError: raise HTTPException( status_code=503, detail="No saved model found. Run `python run_pipeline.py` first.", ) return {"status": "ok", "model": model["name"]} @app.get("/api/model") def model_info(): """What the served pipeline is, and which score types it can produce.""" try: return describe_model() except FileNotFoundError: raise HTTPException( status_code=503, detail="No saved model found. Run `python run_pipeline.py` first.", ) @app.get("/api/metrics") def metrics(): """Test-set scores for all four candidate models, plus the chart manifest.""" return load_metrics() @app.get("/api/plots/{name}") def plot(name: str): """Serve one of the pipeline's chart PNGs by file name.""" path = plot_path(name) if path is None: raise HTTPException(status_code=404, detail=f"No such chart: {name}") return FileResponse(path, media_type="image/png") @app.post("/api/predict") def predict(request: PredictRequest): """Clean and classify one piece of text.""" try: return analyze(request.text, convert_emojis=request.convert_emojis) except EmptyAfterCleaning as error: raise HTTPException(status_code=422, detail=str(error)) except FileNotFoundError: raise HTTPException( status_code=503, detail="No saved model found. Run `python run_pipeline.py` first.", )