ccnasef-cyber
Add protein function prediction API
3d5b11c
Raw
History Blame Contribute Delete
3.15 kB
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
import torch
import uvicorn
from app.predictor import ProteinPredictor
from app.schemas import PredictionResponse, HealthResponse
# ── Global predictor instance ──────────────────────────────────────────────
predictor: ProteinPredictor = None
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Load model once at startup, release on shutdown."""
global predictor
print("πŸ”„ Loading model...")
predictor = ProteinPredictor()
predictor.load()
print("βœ… Model ready!")
yield
print("πŸ›‘ Shutting down...")
# ── App ─────────────────────────────────────────────────────────────────────
app = FastAPI(
title="Protein Function Prediction API",
description="Predicts GO terms for a protein given its FASTA sequence using GraphSAGE + ESM2.",
version="1.0.0",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── Routes ───────────────────────────────────────────────────────────────────
@app.get("/health", response_model=HealthResponse, tags=["Health"])
def health():
"""Check that the API and model are up."""
return HealthResponse(
status="ok",
model_loaded=predictor is not None and predictor.is_ready,
device=str(predictor.device) if predictor else "unknown",
)
@app.post("/predict", response_model=PredictionResponse, tags=["Prediction"])
async def predict(file: UploadFile = File(..., description="FASTA file (.fasta or .fa)")):
"""
Upload a FASTA file and get predicted GO terms.
- Accepts single or multi-sequence FASTA files.
- Returns GO term IDs + their confidence scores.
- Threshold: 0.5 (configurable via config.py).
"""
if not file.filename.endswith((".fasta", ".fa", ".txt")):
raise HTTPException(status_code=400, detail="File must be a .fasta or .fa file.")
content = await file.read()
try:
fasta_text = content.decode("utf-8")
except UnicodeDecodeError:
raise HTTPException(status_code=400, detail="Could not decode file. Make sure it's a valid FASTA text file.")
if not fasta_text.strip():
raise HTTPException(status_code=400, detail="File is empty.")
try:
result = predictor.predict(fasta_text)
except ValueError as e:
raise HTTPException(status_code=422, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
return result
if __name__ == "__main__":
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=False)