mirror of
https://github.com/Andre-Bzk/Rezept0r.git
synced 2026-11-15 09:33:05 +01:00
86 lines
2.9 KiB
Python
86 lines
2.9 KiB
Python
"""GET /api/extract/stream – SSE endpoint."""
|
||
from __future__ import annotations
|
||
import json
|
||
import logging
|
||
from typing import AsyncGenerator
|
||
|
||
from fastapi import APIRouter
|
||
from fastapi.responses import StreamingResponse
|
||
|
||
from app.extractors.base import is_video_url
|
||
from app.extractors.website import extract_website
|
||
from app.extractors.video import extract_video
|
||
from app.models import Recipe, StatusEvent, ErrorEvent, ExtractResponse
|
||
from app.services.nutrition import enrich_nutrition
|
||
from app.services.tandoor_service import recipe_to_tandoor
|
||
from app.services.history_service import save_recipe as save_to_history
|
||
|
||
log = logging.getLogger(__name__)
|
||
router = APIRouter()
|
||
|
||
|
||
def _sse(event: str, data: str) -> str:
|
||
return f"event: {event}\ndata: {data}\n\n"
|
||
|
||
|
||
async def _stream(url: str) -> AsyncGenerator[str, None]:
|
||
try:
|
||
recipe: Recipe | None = None
|
||
pipeline = extract_video(url) if is_video_url(url) else extract_website(url)
|
||
|
||
async for item in pipeline:
|
||
if isinstance(item, StatusEvent):
|
||
yield _sse("status", item.model_dump_json())
|
||
elif isinstance(item, Recipe):
|
||
recipe = item
|
||
|
||
if recipe is None:
|
||
raise RuntimeError("Kein Rezept extrahiert.")
|
||
|
||
# Nutrition enrichment (may yield status events in future; here inline)
|
||
yield _sse("status", StatusEvent(
|
||
stage="nutrition", message="Berechne Nährwerte...", progress=90
|
||
).model_dump_json())
|
||
|
||
recipe = await enrich_nutrition(recipe)
|
||
|
||
tandoor_json = recipe_to_tandoor(recipe)
|
||
base = ExtractResponse(recipe=recipe, tandoor_json=tandoor_json)
|
||
history_id = None
|
||
try:
|
||
history_id = save_to_history(base.model_dump())
|
||
except Exception as db_err:
|
||
log.warning("History save failed: %s", db_err)
|
||
response = ExtractResponse(recipe=recipe, tandoor_json=tandoor_json, history_id=history_id)
|
||
yield _sse("result", response.model_dump_json())
|
||
|
||
except Exception as e:
|
||
log.exception("Extraction error for %s", url)
|
||
yield _sse("error", ErrorEvent(message=str(e)).model_dump_json())
|
||
|
||
|
||
@router.get("/extract/stream")
|
||
async def extract_stream(url: str):
|
||
if not url or not url.startswith(("http://", "https://")):
|
||
async def bad():
|
||
yield _sse("error", ErrorEvent(message="Ungültige URL.").model_dump_json())
|
||
return StreamingResponse(
|
||
bad(),
|
||
media_type="text/event-stream",
|
||
headers={
|
||
"Cache-Control": "no-cache",
|
||
"X-Accel-Buffering": "no",
|
||
"Connection": "keep-alive",
|
||
},
|
||
)
|
||
|
||
return StreamingResponse(
|
||
_stream(url),
|
||
media_type="text/event-stream",
|
||
headers={
|
||
"Cache-Control": "no-cache",
|
||
"X-Accel-Buffering": "no",
|
||
"Connection": "keep-alive",
|
||
},
|
||
)
|