import traceback from fastapi import APIRouter, BackgroundTasks, HTTPException from fastapi.responses import FileResponse from app.models.schemas import RenderRequest, RenderAccepted, JobStatus from app.core.jobs import new_job_id, write_status, read_status from app.core.renderer import run_render_job from app.core.logger import job_log router = APIRouter() def _run_job_safe(job_id: str, payload: dict): try: run_render_job(job_id, payload) except Exception as e: job_log(job_id, f"FAILED: {e}\n{traceback.format_exc()}", stage="error") write_status(job_id, status="failed", error=str(e)) @router.post("/render", response_model=RenderAccepted) def render(req: RenderRequest, background_tasks: BackgroundTasks): job_id = new_job_id() write_status(job_id, status="queued", progress="job created") # Async by design (Rule #5): return immediately, render in background. # This is what makes the API safe to run on an HF Space free tier without # hitting request timeouts on multi-minute renders. background_tasks.add_task(_run_job_safe, job_id, req.model_dump()) return RenderAccepted(job_id=job_id) @router.get("/status/{job_id}", response_model=JobStatus) def status(job_id: str): s = read_status(job_id) if s is None: raise HTTPException(status_code=404, detail="job_id not found") return s @router.get("/outputs/{filename}") def get_output(filename: str): import os path = os.path.join("outputs", filename) if not os.path.exists(path): raise HTTPException(status_code=404, detail="file not found") return FileResponse(path) @router.get("/templates") def list_templates(): import os import json names = [] for f in os.listdir("templates"): if f.endswith(".json"): with open(os.path.join("templates", f)) as fh: names.append(json.load(fh)) return names