122 lines
3.8 KiB
Python
122 lines
3.8 KiB
Python
|
|
"""Open-Graph-Bild-Rendering via Playwright (#141).
|
|||
|
|
|
|||
|
|
Rendert /v2/og-template?drucksache=X als PNG 1200×630.
|
|||
|
|
Cache in data/og-cache/ mit Key SHA256(drucksache + updated_at).
|
|||
|
|
|
|||
|
|
Öffentliche API:
|
|||
|
|
``render_og_card(drucksache, updated_at, base_url)``
|
|||
|
|
→ PNG-Bytes oder None bei Fehler
|
|||
|
|
|
|||
|
|
``cache_key(drucksache, updated_at)``
|
|||
|
|
→ Hex-String (SHA-256 Kurzform, 16 Zeichen)
|
|||
|
|
|
|||
|
|
``get_cached(drucksache, updated_at, cache_dir)``
|
|||
|
|
→ Path der gecacheten Datei oder None
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import hashlib
|
|||
|
|
import logging
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
_DEFAULT_CACHE_DIR = Path(__file__).resolve().parent.parent / "data" / "og-cache"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def cache_key(drucksache: str, updated_at: str) -> str:
|
|||
|
|
"""Berechnet den Cache-Schlüssel als 16-stelligen SHA-256-Präfix.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
drucksache: Drucksachen-ID (z.B. "NRW-18/1234").
|
|||
|
|
updated_at: ISO-Zeitstempel des letzten Updates aus der Datenbank.
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
16 Hex-Zeichen (64-Bit-Präfix des SHA-256).
|
|||
|
|
"""
|
|||
|
|
raw = f"{drucksache}|{updated_at}"
|
|||
|
|
return hashlib.sha256(raw.encode()).hexdigest()[:16]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _cache_path(drucksache: str, updated_at: str, cache_dir: Path) -> Path:
|
|||
|
|
key = cache_key(drucksache, updated_at)
|
|||
|
|
safe_name = drucksache.replace("/", "_").replace(" ", "_")
|
|||
|
|
return cache_dir / f"{safe_name}_{key}.png"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_cached(
|
|||
|
|
drucksache: str,
|
|||
|
|
updated_at: str,
|
|||
|
|
cache_dir: Optional[Path] = None,
|
|||
|
|
) -> Optional[Path]:
|
|||
|
|
"""Gibt den Pfad der gecacheten PNG-Datei zurück, wenn sie existiert.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
drucksache: Drucksachen-ID.
|
|||
|
|
updated_at: ISO-Zeitstempel — ändert sich dieser, ist der Cache ungültig.
|
|||
|
|
cache_dir: Verzeichnis für den Cache. Standard: data/og-cache/.
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Path-Objekt wenn Treffer, sonst None.
|
|||
|
|
"""
|
|||
|
|
cache_dir = cache_dir or _DEFAULT_CACHE_DIR
|
|||
|
|
path = _cache_path(drucksache, updated_at, cache_dir)
|
|||
|
|
return path if path.exists() else None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def render_og_card(
|
|||
|
|
drucksache: str,
|
|||
|
|
updated_at: str,
|
|||
|
|
base_url: str = "http://127.0.0.1:8000",
|
|||
|
|
cache_dir: Optional[Path] = None,
|
|||
|
|
) -> Optional[bytes]:
|
|||
|
|
"""Rendert die OG-Karte als PNG via Playwright und legt sie im Cache ab.
|
|||
|
|
|
|||
|
|
Bei Cache-Hit wird das Rendering übersprungen.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
drucksache: Drucksachen-ID (URL-kodierbar).
|
|||
|
|
updated_at: ISO-Zeitstempel für den Cache-Key.
|
|||
|
|
base_url: Interne Basis-URL der App (Playwright greift darauf zu).
|
|||
|
|
cache_dir: Cache-Verzeichnis. Standard: data/og-cache/.
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
PNG-Bytes bei Erfolg, None bei Fehler.
|
|||
|
|
"""
|
|||
|
|
cache_dir = cache_dir or _DEFAULT_CACHE_DIR
|
|||
|
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
cached = get_cached(drucksache, updated_at, cache_dir)
|
|||
|
|
if cached:
|
|||
|
|
logger.debug("OG-Cache-Hit für %s", drucksache)
|
|||
|
|
return cached.read_bytes()
|
|||
|
|
|
|||
|
|
dest = _cache_path(drucksache, updated_at, cache_dir)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
from playwright.sync_api import sync_playwright
|
|||
|
|
import urllib.parse
|
|||
|
|
|
|||
|
|
encoded = urllib.parse.quote(drucksache, safe="")
|
|||
|
|
url = f"{base_url}/v2/og-template?drucksache={encoded}"
|
|||
|
|
|
|||
|
|
with sync_playwright() as pw:
|
|||
|
|
browser = pw.chromium.launch(args=["--no-sandbox"])
|
|||
|
|
page = browser.new_page(viewport={"width": 1200, "height": 630})
|
|||
|
|
page.goto(url, wait_until="networkidle", timeout=15000)
|
|||
|
|
png_bytes = page.screenshot(
|
|||
|
|
clip={"x": 0, "y": 0, "width": 1200, "height": 630},
|
|||
|
|
type="png",
|
|||
|
|
)
|
|||
|
|
browser.close()
|
|||
|
|
|
|||
|
|
dest.write_bytes(png_bytes)
|
|||
|
|
logger.info("OG-Karte gerendert: %s → %s", drucksache, dest.name)
|
|||
|
|
return png_bytes
|
|||
|
|
|
|||
|
|
except Exception:
|
|||
|
|
logger.exception("Playwright-Render fehlgeschlagen für %s", drucksache)
|
|||
|
|
return None
|