# sn_basis/modules/Datenschreiber.py """ Modul Datenschreiber Enthält die Klasse Datenschreiber mit drei Hauptmethoden: - schreibe_Daten: schreibt die abgerufenen Daten in die Ziel-GPKG/Dateien, fragt bei vorhandenen Layern nach Überschreiben/Anhängen/Abbrechen und legt Stile in der Datenbank ab. - lade_Layer: lädt die erzeugten/aktualisierten Layer ins Projekt und wendet die Vorgabestile an; sortiert abschließend die Layer. - schreibe_log: schreibt die verarbeiteten Pruefergebnisse strukturiert in eine Log-Datei im angegebenen Speicherort. Die Implementierung verwendet die Wrapper-APIs: - qgiscore_wrapper als qgiscore - qgisui_wrapper als qgisui (nur wenn nötig) - qt_wrapper als qt Wichtig ------ Alle Nutzerinteraktionen (z. B. Überschreiben / Anhängen / Abbrechen) werden zentral über den Pruefmanager gebündelt. Die Methode `ask_overwrite_append_cancel` des Pruefmanagers wird verwendet, damit UI-Interaktionen an einer Stelle konsolidiert und testbar sind. """ from __future__ import annotations from typing import Any, Dict, List, Optional import os import json import datetime from sn_basis.functions import qgiscore_wrapper as qgiscore from sn_basis.modules.pruef_ergebnis import pruef_ergebnis class Datenschreiber: """ Schreibt abgerufene Fachdaten in die Zieldatenbank/Dateien und lädt die Layer ins Projekt. Konstruktor ---------- pruefmanager: Instanz des Pruefmanagers; wird verwendet, um Pruefergebnisse zu verarbeiten und Nutzerinteraktionen zu zentralisieren. gpkg_path: Pfad zur Ziel-GPKG-Datei (oder Verzeichnis). Wenn None, muss der Aufrufer einen Speicherort übergeben. """ def __init__(self, pruefmanager: Any, gpkg_path: Optional[str] = None) -> None: self.pruefmanager = pruefmanager self.gpkg_path = str(gpkg_path) if gpkg_path else None # ------------------------------------------------------------------ # # Schreibe Daten # ------------------------------------------------------------------ # def schreibe_Daten( self, daten_dict: Dict[str, Any], processed_results: List[Any], speicherort: str, ) -> List[Dict[str, Any]]: """ Schreibt die übergebenen Layer in die Ziel-GPKG. Erwartung: - daten_dict["daten"] enthält Einträge der Form: ident -> {"layer": QgsVectorLayer} - self.gpkg_path ist ein str """ if not speicherort: raise ValueError("Ein gültiger Speicherort (speicherort) muss übergeben werden.") # gpkg_path einmalig setzen / normalisieren if not self.gpkg_path: self.gpkg_path = str(speicherort) results: List[Dict[str, Any]] = [] daten_map: Dict[str, Any] = daten_dict.get("daten", {}) for ident, entry in daten_map.items(): layer = None # ----------------------------- # Layer extrahieren # ----------------------------- if isinstance(entry, dict) and "layer" in entry: layer = entry["layer"] if layer is None or not hasattr(layer, "isValid") or not layer.isValid(): pe_err = pruef_ergebnis( ok=False, meldung=f"Ungültiger Layer für {ident}", aktion="save_exception", kontext={"ident": ident}, ) self.pruefmanager.verarbeite(pe_err) continue # ----------------------------- # Layername bestimmen # ----------------------------- thema = None for pe in processed_results: try: kontext = getattr(pe, "kontext", None) or {} if kontext.get("ident") == ident: thema = kontext.get("thema") break except Exception: continue layer_name = thema or str(ident) # Layer in GPKG schreiben err_msg = self._write_layer_to_gpkg(layer_name=layer_name, layer=layer) if err_msg is not None: pe_err = pruef_ergebnis( ok=False, meldung=f"Fehler beim Schreiben des Layers {layer_name}: {err_msg}", aktion="save_exception", kontext={"ident": ident, "layer_name": layer_name}, ) self.pruefmanager.verarbeite(pe_err) continue # Erfolgsfall: Info für lade_Layer sammeln layer_path = f"{self.gpkg_path}|layername={layer_name}" results.append({ "layer_path": layer_path, "thema": layer_name, "ident": ident, }) return results # ----------------------------- # ------------------------------------------------------------------ # # Lade Layer ins Projekt # ------------------------------------------------------------------ # def lade_Layer(self, layer_infos: List[Dict[str, Any]]) -> None: """ Lädt die in schreibe_Daten erzeugten/aktualisierten Layer ins Projekt und wendet die Vorgabestile an. """ loaded_layers = [] for info in layer_infos: layer_path = info.get("layer_path") thema = info.get("thema") if not layer_path: continue try: layer = qgiscore.QgsVectorLayer(layer_path, thema, "ogr") if not layer or not getattr(layer, "isValid", lambda: False)(): pe_err = pruef_ergebnis( ok=False, meldung=f"Layer {thema} konnte nicht geladen werden", aktion="layer_nicht_gefunden", kontext={"layer_path": layer_path}, ) self.pruefmanager.verarbeite(pe_err) continue except Exception as exc: pe_err = pruef_ergebnis( ok=False, meldung=f"Fehler beim Erzeugen des Layers {thema}: {exc}", aktion="layer_nicht_gefunden", kontext={"layer_path": layer_path, "error": str(exc)}, ) self.pruefmanager.verarbeite(pe_err) continue try: apply_style_fn = getattr(qgiscore, "apply_default_style_from_gpkg", None) if callable(apply_style_fn): apply_style_fn(self.gpkg_path, layer) except Exception: pe_warn = pruef_ergebnis( ok=True, meldung=f"Style konnte für {thema} nicht automatisch angewendet werden", aktion="stil_not_implemented", kontext={"thema": thema}, ) self.pruefmanager.verarbeite(pe_warn) try: # qgisui wrapper wird hier nicht direkt für die Abfrage verwendet; # qgisui.add_layer_to_project sollte aber vorhanden sein. from sn_basis.functions import qgisui_wrapper as qgisui add_fn = getattr(qgisui, "add_layer_to_project", None) if callable(add_fn): add_fn(layer) else: # Fallback: falls wrapper nicht vorhanden, versuche QGIS-API direkt if getattr(qgiscore, "QgsProject", None) is not None and qgiscore.QGIS_AVAILABLE: qgiscore.QgsProject.instance().addMapLayer(layer) loaded_layers.append(layer) except Exception: pe_err = pruef_ergebnis( ok=False, meldung=f"Layer {thema} konnte nicht ins Projekt geladen werden", aktion="layer_nicht_gefunden", kontext={"thema": thema}, ) self.pruefmanager.verarbeite(pe_err) continue # Sortiere Layer im Projekt nach ID (Wrapper-Funktion bevorzugt) sort_fn = getattr(qgiscore, "sort_layers_by_id", None) if callable(sort_fn): try: sort_fn() except Exception: pass # ------------------------------------------------------------------ # # Schreibe Log # ------------------------------------------------------------------ # def schreibe_log(self, processed_results: List[Any], speicherort: str) -> str: """ Schreibt die verarbeiteten Pruefergebnisse strukturiert in eine Log-Datei. """ if not speicherort: raise ValueError("Ein gültiger Speicherort muss übergeben werden.") log_dir = speicherort os.makedirs(log_dir, exist_ok=True) timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") log_path = os.path.join(log_dir, f"datenabruf_log_{timestamp}.json") serializable: List[Dict[str, Any]] = [] for pe in processed_results: try: entry = {} entry["ok"] = getattr(pe, "ok", None) if hasattr(pe, "ok") else None entry["meldung"] = getattr(pe, "meldung", None) if hasattr(pe, "meldung") else None kontext = getattr(pe, "kontext", None) if hasattr(pe, "kontext") else None entry["kontext"] = kontext serializable.append(entry) except Exception: serializable.append({"raw": str(pe)}) with open(log_path, "w", encoding="utf-8") as fh: json.dump(serializable, fh, ensure_ascii=False, indent=2) pe_log = pruef_ergebnis( ok=True, meldung=f"Log geschrieben: {os.path.basename(log_path)}", aktion="standarddatei_vorschlagen", kontext={"log_path": log_path}, ) self.pruefmanager.verarbeite(pe_log) return log_path # ------------------------------------------------------------------ # # Hilfsfunktionen intern # ------------------------------------------------------------------ # def _write_layer_to_gpkg( self, layer_name: str, layer: Any, ) -> Optional[str]: """ Schreibt einen QgsVectorLayer in die Ziel-GPKG. Voraussetzungen: - self.gpkg_path ist ein str - layer ist ein gültiger QgsVectorLayer """ if layer is None or not hasattr(layer, "isValid") or not layer.isValid(): return "Ungültiger Layer zum Schreiben übergeben" try: opts = qgiscore.QgsVectorFileWriter.SaveVectorOptions() opts.driverName = "GPKG" opts.layerName = layer_name opts.fileEncoding = "UTF-8" # Datei existiert → Layer überschreiben # Datei existiert nicht → neue GPKG anlegen if not os.path.exists(self.gpkg_path): opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteFile else: opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteLayer err = qgiscore.QgsVectorFileWriter.writeAsVectorFormatV3( layer, self.gpkg_path, qgiscore.QgsProject.instance().transformContext(), opts, ) # QGIS ≥3 liefert ein Tupel: (error_code, error_message, new_filename, new_layer_name) if isinstance(err, tuple): error_code = err[0] error_msg = err[1] if len(err) > 1 else "" else: error_code = err error_msg = "" if error_code != qgiscore.QgsVectorFileWriter.NoError: return f"Fehler beim Schreiben (Code {error_code}, msg='{error_msg}')" return None except Exception as exc: return str(exc)