from __future__ import annotations from typing import Optional, Any from sn_basis.functions import ( ask_yes_no, info, warning, error, set_layer_visible, ) from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion class Pruefmanager: """ Zentrale Verarbeitung von pruef_ergebnis-Objekten. Erwartete öffentliche API (verwendet von Core-Komponenten wie DataGrabber): - report_error(thema, meldung, *, aktion: Optional[PruefAktion]=None, kontext=None) -> None - request_decision(pruef_res) -> str - report_summary(summary: dict) -> None - verarbeite(ergebnis: pruef_ergebnis) -> pruef_ergebnis """ def __init__(self, ui_modus: str = "qgis", parent: Optional[Any] = None): self.ui_modus = ui_modus self.parent = parent # --------------------------------------------------------------------- # Basis-API: Meldungen / Zusammenfassungen # --------------------------------------------------------------------- def report_error(self, thema: str, meldung: str, *, aktion: Optional[PruefAktion] = None, kontext: Optional[Any] = None) -> None: """ Einheitliche Meldung für Fehler/Warnungen aus dem Core. Keine Rückgabe; dient als zentraler Hook für Logging/UI. """ critical_actions = { "netzwerkfehler", "pruefe_exception", "save_exception", "layer_create_failed", "read_error", "open_error", } warn_actions = { "datei_nicht_gefunden", "pfad_nicht_gefunden", "url_nicht_erreichbar", "falsche_endung", "kein_header", "kein_arbeitsblatt", } if aktion in critical_actions: error(thema, meldung) return if aktion in warn_actions: warning(thema, meldung) return # Default: informative Warnung warning(thema, meldung) def report_summary(self, summary: dict) -> None: geladen = summary.get("geladen", []) fehler = summary.get("fehler", {}) ausserhalb = summary.get("ausserhalb", []) relevant = summary.get("relevant", []) message = ( f"Geladene Dienste: {len(geladen)}\n" f"Relevante Dienste: {len(relevant)}\n" f"Dienste ausserhalb: {len(ausserhalb)}\n" f"Fehler: {len(fehler)}" ) info("DataGrabber Zusammenfassung", message) # --------------------------------------------------------------------- # Entscheidungs-API # --------------------------------------------------------------------- def request_decision(self, pruef_res: Any) -> str: """ Synchronously request a decision from the user (or return a default in headless mode). Returns one of: - "abort" - "continue" - "temporaer_erzeugen" - "ignore" """ aktion = getattr(pruef_res, "aktion", None) meldung = getattr(pruef_res, "meldung", str(pruef_res)) interactive_actions = { "leereingabe_erlaubt", "standarddatei_vorschlagen", "temporaer_erlaubt", "layer_unsichtbar", } if aktion in interactive_actions: if self.ui_modus == "qgis": title_map = { "leereingabe_erlaubt": "Ohne Eingabe fortfahren", "standarddatei_vorschlagen": "Standarddatei verwenden", "temporaer_erlaubt": "Temporäre Datei erzeugen", "layer_unsichtbar": "Layer einblenden", } title = title_map.get(aktion, "Entscheidung erforderlich") try: yes = ask_yes_no(title, meldung, default=False, parent=self.parent) except Exception: return "abort" if yes: if aktion == "temporaer_erlaubt": return "temporaer_erzeugen" return "continue" return "abort" if self.ui_modus == "headless": return "abort" informational_actions = { "leer", "datei_nicht_gefunden", "pfad_nicht_gefunden", "url_nicht_erreichbar", "netzwerkfehler", "falscher_geotyp", "layer_leer", "falscher_layertyp", "falsches_crs", "felder_fehlen", "datenquelle_unerwartet", "layer_nicht_editierbar", "kein_header", "kein_arbeitsblatt", "read_error", "open_error", "pflichtfelder_fehlen", } if aktion in informational_actions: return "abort" return "abort" # --------------------------------------------------------------------- # Höhere Abstraktion: verarbeite # --------------------------------------------------------------------- def verarbeite(self, ergebnis: pruef_ergebnis) -> pruef_ergebnis: """ Verarbeitet ein pruef_ergebnis-Objekt und führt ggf. Nutzerinteraktion durch. Liefert ein ggf. modifiziertes pruef_ergebnis zurück. """ if ergebnis.ok: return ergebnis aktion = ergebnis.aktion kontext = ergebnis.kontext meldung = ergebnis.meldung # Zentrale Meldung self.report_error(aktion or "pruefung", meldung or "", aktion=aktion, kontext=kontext) # Interaktive Entscheidungen if aktion in ("leereingabe_erlaubt", "standarddatei_vorschlagen", "temporaer_erlaubt", "layer_unsichtbar"): decision = self.request_decision(ergebnis) if decision == "temporaer_erzeugen": return pruef_ergebnis(ok=True, meldung="Temporäre Datei soll erzeugt werden.", aktion="temporaer_erzeugen", kontext=None) if decision == "continue": return pruef_ergebnis(ok=True, meldung="Fortgefahren.", aktion="ok", kontext=kontext) return ergebnis # abort / unverändert # Spezielle Excel/Importer-Fälle: klare Meldungen, keine interaktive Entscheidung if aktion == "kein_header": warning("Excel-Import", meldung or "") return ergebnis if aktion == "kein_arbeitsblatt": warning("Excel-Import", meldung or "") return ergebnis if aktion in ("read_error", "open_error"): error("Excel-Import", meldung or "") return ergebnis if aktion == "datei_nicht_gefunden": warning("Datei nicht gefunden", meldung or "") return ergebnis # Spezieller Fall: layer_unsichtbar (falls nicht interaktiv behandelt) if aktion == "layer_unsichtbar": if kontext is not None: try: set_layer_visible(kontext, True) return pruef_ergebnis(ok=True, meldung="Layer wurde eingeblendet.", aktion="ok", kontext=kontext) except Exception: return ergebnis return ergebnis # Standard: keine Änderung return ergebnis def ask_overwrite_append_cancel(self, layer_name: str, default: str = "overwrite") -> str: """ Zeigt dem Nutzer eine Auswahl für einen bereits existierenden Layer an. Rückgabe ------- str Einer der Werte: "overwrite", "append", "cancel". Verhalten -------- - Verwendet bevorzugt die UI-Wrapper-Funktion `qt_wrapper` / `qgisui_wrapper`, falls vorhanden (z. B. ein QMessageBox-Dialog mit drei Buttons). - Im Mock- oder Headless-Modus (kein Qt/QGIS verfügbar) wird der übergebene `default`-Wert zurückgegeben. - Alle Nutzerinteraktionen laufen über diese zentrale Methode, damit das Plugin an einer Stelle gesteuert und ggf. getested werden kann. Parameter --------- layer_name: Anzeigename des Layers, der bereits existiert (wird im Dialog angezeigt). default: Rückgabewert im Headless/Mock-Modus oder wenn der Dialog nicht verfügbar ist. Gültige Werte: "overwrite", "append", "cancel". Standard: "overwrite". """ # Validierung des Defaults if default not in ("overwrite", "append", "cancel"): default = "overwrite" # Versuche, eine UI-Wrapper-Funktion zu verwenden, falls vorhanden try: # qgisui_wrapper kann eine spezialisierte Dialogfunktion bereitstellen from sn_basis.functions import qgisui_wrapper as qgisui ask_fn = getattr(qgisui, "ask_overwrite_append_cancel", None) if callable(ask_fn): # Die Wrapper-Funktion soll genau die drei Strings zurückgeben choice = ask_fn(layer_name) if choice in ("overwrite", "append", "cancel"): return choice except Exception: # Falls Import/Wrapper fehlschlägt, weiter zum Qt-Fallback pass # Fallback: direkte Qt-Dialoge über qt_wrapper (wenn verfügbar) try: from sn_basis.functions import qt_wrapper as qt QMessageBox = getattr(qt, "QMessageBox", None) if QMessageBox is not None: # Erzeuge und konfiguriere Dialog msg = QMessageBox() msg.setWindowTitle("Layer bereits vorhanden") msg.setText(f"Der Layer '{layer_name}' existiert bereits. Was möchten Sie tun?") overwrite_btn = msg.addButton("Überschreiben", QMessageBox.AcceptRole) append_btn = msg.addButton("Anhängen", QMessageBox.AcceptRole) cancel_btn = msg.addButton("Abbrechen", QMessageBox.RejectRole) msg.setDefaultButton(overwrite_btn) # Blockierend anzeigen msg.exec_() clicked = msg.clickedButton() if clicked == overwrite_btn: return "overwrite" if clicked == append_btn: return "append" return "cancel" except Exception: # Qt nicht verfügbar oder Fehler beim Dialogaufbau pass # Headless / Mock: gib Default zurück return default