Dar4devil's picture
LexGuard backend
c34b339
Raw
History Blame Contribute Delete
1.47 kB
from __future__ import annotations
import asyncio
import json
import logging
from typing import AsyncIterator
from fastapi import APIRouter, HTTPException
from sse_starlette.sse import EventSourceResponse
from app.services import jobs
from app.services.jobs import SENTINEL, StreamEvent
logger = logging.getLogger(__name__)
router = APIRouter(tags=["stream"])
HEARTBEAT_SECONDS = 15.0
@router.get("/stream/{job_id}")
async def stream(job_id: str) -> EventSourceResponse:
job = jobs.store().get(job_id)
if job is None:
raise HTTPException(status_code=404, detail="job not found")
async def event_source() -> AsyncIterator[dict[str, str]]:
while True:
try:
item = await asyncio.wait_for(job.queue.get(), timeout=HEARTBEAT_SECONDS)
except asyncio.TimeoutError:
yield {"event": "heartbeat", "data": "{}"}
if job.finished.is_set():
return
continue
if item is SENTINEL:
return
assert isinstance(item, StreamEvent)
payload = (
item.data.model_dump()
if hasattr(item.data, "model_dump")
else item.data
)
yield {"event": item.type, "data": json.dumps(payload, default=str)}
if item.type in ("report_complete", "error"):
return
return EventSourceResponse(event_source())