Files
Plugin_SN_Basis/modules/Pruefmanager.py

277 lines
10 KiB
Python

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