Plugin neuheiten: BGG-Sync mit APScheduler, Filtern und deutscher UI
- Datenmodell + eigene Migration 0001_neuheiten_tabelle (Tabelle neuheiten: Titel, Verlag, Autor, Erscheinungsjahr, BGG-ID, Status 'neuheit', Quelle, Zeitstempel; bgg_id eindeutig als Merge-Kriterium) - BoardGameGeek XML API2-Client (search + thing, Batches à 20 IDs): Rate-Limit >= 1 s zwischen Requests, Retry mit exponentiellem Backoff bei 5xx/429/Netzwerkfehlern, HTTP 202 gemäß Retry-After, robustes XML-Parsing; Transport/Uhr/Sleep injizierbar (keine echten Calls in Tests) - Filter: Erweiterungen (boardgameexpansion) auf Request- und Elementebene ausgeschlossen; Prototypen per Titel-Heuristik (BGG hat keinen Marker) - Sync-Service mit Update-statt-Duplikat-Logik über die eindeutige BGG-ID (Status bleibt erhalten); Fehler je Suchbegriff brechen den Lauf nicht ab - APScheduler-Hintergrundjob (Standard 24 h) mit Überlappungsschutz, abschaltbar/intervallkonfigurierbar per Env; manueller 'Jetzt synchronisieren'-Endpunkt nur für Admin/Redakteur, optional mit Sofort-Suchbegriff - UI /neuheiten: sortier-/filterbare Tabelle mit Volltextsuche (HTMX-Teilladung, noscript-fähig), deutsche Oberfläche, BGG-Links, Ergebnis-Banner - Plugin-Loader: idempotentes Laden (Modul-Caching), damit mehrere create_app()-Aufrufe dieselben Plugin-Klassen/Tabellen nutzen - Tests: Parsing, Erweiterungs-/Prototyp-Filter, Rate-Limit/Backoff/202, Update-statt-Duplikat, Rollen am Sync-Endpunkt, Scheduler-Lifecycle — ausschließlich mit gemockten BGG-Antworten (uv run pytest: 96 grün) - README/AGENTS: Plugin-Doku, Env-Variablen, Fortschrittstabelle aktualisiert
This commit is contained in:
42
tests/_neuheiten.py
Normal file
42
tests/_neuheiten.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Zugriff auf die neuheiten-Plugin-Module über den vom Plugin-Loader
|
||||
verwendeten Modulnamen (`spiele_redaktion_plugins.neuheiten`).
|
||||
|
||||
Der Kern teilt sich eine SQLAlchemy-MetaData; deshalb darf es pro Prozess
|
||||
nur eine Modul-Identität geben. Tests importieren das Plugin daher über
|
||||
diesen Helper statt direkt über `plugins.neuheiten`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from redaktionskern.plugin_loader import discover_plugins
|
||||
|
||||
_PROJEKT_WURZEL = Path(__file__).resolve().parents[1]
|
||||
|
||||
if "spiele_redaktion_plugins.neuheiten" not in sys.modules:
|
||||
discover_plugins(
|
||||
_PROJEKT_WURZEL / "plugins",
|
||||
entry_point_group="spiele_redaktion.tests.keine_entry_points",
|
||||
)
|
||||
|
||||
_neuheiten = sys.modules["spiele_redaktion_plugins.neuheiten"]
|
||||
|
||||
bgg = _neuheiten.bgg
|
||||
sync_modul = _neuheiten.sync
|
||||
models = _neuheiten.models
|
||||
|
||||
BggClient = bgg.BggClient
|
||||
BggFehler = bgg.BggFehler
|
||||
SuchTreffer = bgg.SuchTreffer
|
||||
BggSpiel = bgg.BggSpiel
|
||||
parse_suche = bgg.parse_suche
|
||||
parse_things = bgg.parse_things
|
||||
|
||||
SyncService = sync_modul.SyncService
|
||||
SyncErgebnis = sync_modul.SyncErgebnis
|
||||
ist_moeglicher_prototyp = sync_modul.ist_moeglicher_prototyp
|
||||
speichere_oder_aktualisiere = sync_modul.speichere_oder_aktualisiere
|
||||
|
||||
Neuheit = models.Neuheit
|
||||
STATUS_NEUHEIT = models.STATUS_NEUHEIT
|
||||
Reference in New Issue
Block a user