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:
@@ -61,6 +61,20 @@ def _aus_verzeichnis(verzeichnis: Path) -> list[BasePlugin]:
|
||||
if not init.is_file():
|
||||
continue
|
||||
modulname = f"{_MODUL_PRAEFIX}.{eintrag.name}"
|
||||
|
||||
# Bereits geladenes Modul wiederverwenden (idempotentes Laden):
|
||||
# Mehrere create_app()-Aufrufe im selben Prozess teilen sich sonst
|
||||
# doppelte Klassen/Tabellen auf derselben SQLAlchemy-MetaData.
|
||||
zwischengespeichert = sys.modules.get(modulname)
|
||||
if (
|
||||
zwischengespeichert is not None
|
||||
and hasattr(zwischengespeichert, "plugin")
|
||||
and getattr(zwischengespeichert.__spec__, "origin", None)
|
||||
and Path(zwischengespeichert.__spec__.origin).resolve() == init.resolve()
|
||||
):
|
||||
plugins.append(_als_plugin(zwischengespeichert.plugin))
|
||||
continue
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
modulname, init, submodule_search_locations=[str(eintrag)]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user