Files
Rezept0r/app/api/extract.py
T
2026-03-29 20:52:20 +02:00

86 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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",
},
)