tab_a_ui, tab_a_logic überarbeitet; UI lauffähig, beim Klick auf "Fachdaten laden" mit leerem Verfahrens-DB-Feld kommt die Abfrage, ob temporär gearbeitet werden soll, bei Auswahl einer bestehenden gpkg kommt Blödsinn

bei neuer Datei wird korrekt der datei_erzeugen_modus übergeben. Data Grabber noch nicht angebunden
This commit is contained in:
2026-03-04 15:31:36 +01:00
parent b5f663d9de
commit fc55735477
3 changed files with 312 additions and 404 deletions

View File

@@ -1,7 +1,45 @@
#sn_plan41/ui/dockwidget.py
from sn_basis.ui.tabs.settings_tab import SettingsTab
from sn_plan41.ui.tab_a_ui import TabA
#from sn_plan41.ui.tabs.tab_b import TabB
from sn_basis.ui.base_dockwidget import BaseDockWidget
from sn_basis.ui.base_dockwidget import BaseDockWidget
from sn_basis.functions.qt_wrapper import QTabWidget
from sn_basis.functions.message_wrapper import error
class DockWidget(BaseDockWidget):
tabs = [TabA, SettingsTab]
def __init__(self, parent=None, subtitle="", pruefmanager=None, data_grabber=None):
super().__init__(parent, subtitle)
# Services als Attribute speichern
self.pruefmanager = pruefmanager
self.data_grabber = data_grabber
# Tabs NACH Services initialisieren (override der Basis-Logik)
self._init_tabs_with_services()
def _init_tabs_with_services(self):
"""Tabs mit pruefmanager/data_grabber initialisieren"""
try:
# Bestehendes TabWidget löschen
self.setWidget(None)
tab_widget = QTabWidget()
for tab_class in self.tabs:
tab_instance = tab_class(self) # parent=self.dockwidget
tab_title = getattr(tab_class, "tab_title", tab_class.__name__)
tab_widget.addTab(tab_instance, tab_title)
# Services durchreichen
if hasattr(tab_instance, 'set_services'):
tab_instance.set_services(
pruefmanager=self.pruefmanager,
data_grabber=self.data_grabber
)
self.setWidget(tab_widget)
except Exception as e:
error("Services-Tabs konnten nicht initialisiert werden", str(e))

View File

@@ -4,87 +4,112 @@ sn_plan41/ui/tab_a_logic.py Fachlogik für Tab A (Daten)
from __future__ import annotations
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional
from collections.abc import Mapping as _Mapping
import os
from sn_basis.functions.variable_wrapper import ( # type: ignore
from sn_basis.functions.qgiscore_wrapper import (
QgsVectorFileWriter,
QgsVectorLayer,
QgsProject,
)
from sn_basis.functions.variable_wrapper import (
get_variable,
set_variable,
)
from sn_basis.functions.sys_wrapper import ( # type: ignore
file_exists,
write_text,
)
from sn_basis.functions.ly_existence_wrapper import layer_exists # type: ignore
from sn_basis.functions.ly_metadata_wrapper import get_layer_type # type: ignore
from sn_basis.functions.sys_wrapper import file_exists
from sn_basis.functions.ly_existence_wrapper import layer_exists
from sn_basis.functions.ly_metadata_wrapper import get_layer_type
# Prüfer-Typen (werden als Instanzen erwartet)
from sn_basis.modules.Pruefmanager import Pruefmanager # type: ignore
from sn_basis.modules.linkpruefer import Linkpruefer # type: ignore
from sn_basis.modules.stilpruefer import Stilpruefer # type: ignore
# Prüfer-Typen
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.linkpruefer import Linkpruefer
from sn_basis.modules.stilpruefer import Stilpruefer
# Typalias für Klarheit
Row = Dict[str, Any]
DataDict = Dict[str, List[Row]]
class TabALogic:
"""
Kapselt die komplette Logik von Tab A:
- Verfahrens-Datenbank
- optionale Linkliste
- Verfahrensgebiet-Layer
Diese Klasse erwartet beim Erzeugen Instanzen von Pruefmanager, Linkpruefer
und Stilpruefer. Die validate_and_filter_rows-Methode verwendet diese
Instanzen über self.
Kapselt die Fachlogik von Tab A. Verfahrens-DB wird **nicht** bei Pfad-Auswahl,
sondern erst beim ersten Layer-Schreiben angelegt (alte Logik).
"""
def __init__(self, pruefmanager: Pruefmanager, link_pruefer: Linkpruefer, stil_pruefer: Stilpruefer) -> None:
"""
:param pruefmanager: Instanz des Pruefmanagers (für verarbeite/report)
:param link_pruefer: Instanz des Linkpruefers (mit methode pruefe)
:param stil_pruefer: Instanz des Stilpruefers (mit methode pruefe)
"""
self.pruefmanager = pruefmanager
self.link_pruefer = link_pruefer
self.stil_pruefer = stil_pruefer
# -------------------------------
# Verfahrens-Datenbank
# Verfahrens-Datenbank (Pfad-Management)
# -------------------------------
def load_verfahrens_db(self) -> Optional[str]:
"""
Lädt die gespeicherte Verfahrens-Datenbank.
"""
"""Lädt den gespeicherten Verfahrens-DB-Pfad (Datei muss nicht existieren)."""
path = get_variable("verfahrens_db", scope="project")
if path and file_exists(path):
return path
return None
return path or None
def set_verfahrens_db(self, path: Optional[str]) -> None:
"""
Speichert oder löscht die Verfahrens-Datenbank.
"""
"""Speichert den Verfahrens-DB-Pfad (Datei wird später angelegt)."""
if path:
set_variable("verfahrens_db", path, scope="project")
else:
set_variable("verfahrens_db", "", scope="project")
def create_new_verfahrens_db(self, path: str) -> bool:
# -------------------------------
# Layer → Verfahrens-DB schreiben (alte Logik!)
# -------------------------------
def write_layer_to_verfahrens_db(
self,
source_layer: QgsVectorLayer,
zielpfad: str,
layer_name: str,
) -> bool:
"""
Legt eine neue leere GPKG-Datei an.
Schreibt einen Layer in die Verfahrens-DB.
Legt GPKG **bei Bedarf neu an** (wie puffer_setzen im alten Code).
Args:
source_layer: Layer zum Exportieren (z.B. aus DataGrabber)
zielpfad: Vom Dateiprüfer geprüfter Ziel-GPKG-Pfad
layer_name: Name des Layers in der GPKG
Returns:
True wenn erfolgreich
"""
if not path:
if not zielpfad or not source_layer or not source_layer.isValid():
return False
try:
write_text(path, "")
except Exception:
# Optionen wie im alten puffer_setzen
opts = QgsVectorFileWriter.SaveVectorOptions()
opts.driverName = "GPKG"
opts.fileEncoding = "UTF-8"
opts.layerName = layer_name
# Alte Logik: bei neuem Pfad komplett neue GPKG, sonst Layer überschreiben
if not os.path.exists(zielpfad):
opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
else:
opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
transform_context = QgsProject.instance().transformContext()
error = QgsVectorFileWriter.writeAsVectorFormatV3(
source_layer,
zielpfad,
transform_context,
opts,
)
if error != QgsVectorFileWriter.NoError:
print(f"Fehler beim Schreiben nach {zielpfad}: {error}")
return False
self.set_verfahrens_db(path)
# Pfad jetzt auch als "Verfahrens-DB" merken
self.set_verfahrens_db(zielpfad)
return True
# -------------------------------
@@ -92,18 +117,12 @@ class TabALogic:
# -------------------------------
def load_linkliste(self) -> Optional[str]:
"""
Lädt die gespeicherte lokale Linkliste.
"""
path = get_variable("linkliste", scope="project")
if path and file_exists(path):
return path
return None
def set_linkliste(self, path: Optional[str]) -> None:
"""
Speichert oder löscht die lokale Linkliste.
"""
if path:
set_variable("linkliste", path, scope="project")
else:
@@ -114,10 +133,6 @@ class TabALogic:
# -------------------------------
def save_verfahrensgebiet_layer(self, layer) -> None:
"""
Speichert die ID des Verfahrensgebiet-Layers.
Ungültige Layer werden ignoriert.
"""
if layer is None:
set_variable("verfahrensgebiet_layer", "", scope="project")
return
@@ -139,20 +154,12 @@ class TabALogic:
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
def load_verfahrensgebiet_layer_id(self) -> Optional[str]:
"""
Lädt die gespeicherte Layer-ID.
"""
value = get_variable("verfahrensgebiet_layer", scope="project")
return value or None
def is_valid_verfahrensgebiet_layer(self, layer) -> bool:
"""
Prüft, ob ein Layer als Verfahrensgebiet geeignet ist.
"""
if not layer_exists(layer):
return False
layer_type = get_layer_type(layer)
return layer_type == "vector"

View File

@@ -1,4 +1,7 @@
# sn_plan41/ui/tab_a_ui.py UI für Tab A (Daten)
"""
sn_plan41/ui/tab_a_ui.py UI für Tab A (Daten)
"""
from __future__ import annotations
from typing import Optional
@@ -21,76 +24,79 @@ from sn_basis.functions.qt_wrapper import (
from sn_basis.functions.qgisui_wrapper import QgsFileWidget, QgsMapLayerComboBox
from sn_basis.functions.qgiscore_wrapper import QgsProject, QgsMapLayerProxyModel
from sn_basis.functions.variable_wrapper import get_variable, set_variable
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
from sn_plan41.ui.tab_a_logic import TabALogic
# PrüfWorkflow / DataGrabber (werden zur Laufzeit vom Pruefmanager/Pruefern verwendet)
from sn_basis.modules.Dateipruefer import Dateipruefer
# Services (werden von DockWidget injiziert)
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.DataGrabber import DataGrabber
from sn_basis.modules.linkpruefer import Linkpruefer
from sn_basis.modules.stilpruefer import Stilpruefer
from sn_basis.modules.Datenschreiber import Datenschreiber
from sn_basis.modules.Dateipruefer import Dateipruefer
from sn_plan41.ui.tab_a_logic import TabALogic
# Raumfilter-Optionen
# Konstanten
RAUMFILTER_VAR = "Raumfilter"
RAUMFILTER_OPTIONS = ("Verfahrensgebiet", "Pufferlayer", "ohne")
RAUMFILTER_DEFAULT = "Pufferlayer"
pm = Pruefmanager(ui_modus="qgis")
lp = Linkpruefer()
sp = Stilpruefer()
class TabA(QWidget):
"""
UI-Klasse für Tab A (Daten).
Diese bereinigte Version enthält ausschließlich UI-Elemente und
einfache, nicht-validierende Callback-Handler. Alle fachlichen Prüfungen
und Fehlerbehandlungen werden zur Laufzeit vom Pruefmanager und den Prüfern
übernommen.
UI-Klasse für Tab A (Daten) des Plan41-Plugins.
Zuständig für:
- Anzeige und Auswahl von Verfahrens-DB, Linklisten und Layern
- Steuerung der Pipeline über "Fachdaten laden"
- Persistierung von UI-States via Projektvariablen
Services (Pruefmanager, DataGrabber) werden zur Laufzeit vom DockWidget injiziert.
Alle fachlichen Prüfungen laufen über den zentralen Pruefmanager.
"""
tab_title = "Daten"
def __init__(self, parent=None, pruefmanager=None, link_pruefer=None, stil_pruefer=None, build_ui=True):
tab_title = "Daten" #: Tab-Titel für BaseDockWidget
def __init__(self, parent: Optional[QWidget] = None):
"""
Initialisiert die UI-Struktur.
Services werden später über :meth:`set_services` injiziert.
:param parent: Parent-Widget (typischerweise DockWidget)
"""
super().__init__(parent)
self.parent = parent
self.tab_title = "Daten"
# Logik-Adapter (TabALogic verwaltet persistente Projektvariablen)
self.logic = TabALogic(pruefmanager=pruefmanager, link_pruefer=link_pruefer, stil_pruefer=stil_pruefer)
# Prüfmanager-Instanz (UI-Modus wird zur Laufzeit vom Pruefmanager gehandhabt)
self.pruefmanager = Pruefmanager(ui_modus="qgis")
# DataGrabber-Instanz (synchroner Aufruf; Prüfungen übernimmt Pruefmanager/Pruefer)
# Hinweis: DataGrabber erwartet ggf. Prüfer-Objekte; hier werden sie nicht übergeben,
# da TabALogic / Pruefmanager diese zur Laufzeit bereitstellen können.
self.data_grabber = DataGrabber(pruefmanager=self.pruefmanager)
# Platzhalter, die vom Plugin oder Nutzer gesetzt werden können
self._attributes_list = [] # optionale Attributliste (z. B. Excel-Import)
self._pufferlayer = None # optionaler Layer (Verfahrensgebiet)
# Services (werden von DockWidget gesetzt)
self.pruefmanager: Optional[Pruefmanager] = None
self.data_grabber: Optional[DataGrabber] = None
self.logic: Optional[TabALogic] = None
# UI-State
self.verfahrens_db: Optional[str] = None
self.lokale_linkliste: Optional[str] = None
# UI-Widget-Referenz für Raumfilter
self._pufferlayer = None
self._attributes_list = []
# UI-Referenzen
self._raumfilter_combo: Optional[QComboBox] = None
self._build_ui()
self._restore_state()
if build_ui:
self._build_ui()
self._restore_state()
def set_services(self, pruefmanager: Pruefmanager, data_grabber: DataGrabber) -> None:
"""
Injiziert Services vom übergeordneten DockWidget.
:param pruefmanager: Zentrale Prüfmanager-Instanz
:param data_grabber: DataGrabber für Quellenprüfung/Abruf
"""
self.pruefmanager = pruefmanager
self.data_grabber = data_grabber
# ---------------------------------------------------------
# UI-Aufbau
# ---------------------------------------------------------
def _build_ui(self) -> None:
"""Erstellt die komplette UI-Hierarchie mit allen Gruppen."""
main_layout = QVBoxLayout()
main_layout.setSpacing(4)
main_layout.setContentsMargins(4, 4, 4, 4)
# Verfahrens-Datenbank Gruppe
# === VERFAHRENS-DATENBANK ===
self.group_button = QToolButton()
self.group_button.setText("Verfahrens-Datenbank")
self.group_button.setCheckable(True)
@@ -103,29 +109,26 @@ class TabA(QWidget):
self.group_content = QWidget()
self.group_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum)
group_layout = QVBoxLayout()
group_layout.setSpacing(2)
group_layout.setContentsMargins(10, 4, 4, 4)
group_layout.addWidget(QLabel("bestehende Datei auswählen"))
self.file_widget = QgsFileWidget()
self.file_widget.setStorageMode(QgsFileWidget.GetFile)
self.file_widget.setFilter("Geopackage (*.gpkg)")
self.file_widget.fileChanged.connect(self._on_verfahrens_db_changed)
group_layout.addWidget(self.file_widget)
group_layout.addWidget(QLabel("-oder-"))
self.btn_new = QPushButton("Neue Verfahrens-DB anlegen")
self.btn_new.clicked.connect(self._create_new_gpkg)
group_layout.addWidget(self.btn_new)
self.group_content.setLayout(group_layout)
main_layout.addWidget(self.group_content)
# Optionale Linkliste
# === OPTIONALE LINKLISTE ===
self.optional_button = QToolButton()
self.optional_button.setText("Optional: Lokale Linkliste")
self.optional_button.setCheckable(True)
@@ -138,90 +141,43 @@ class TabA(QWidget):
self.optional_content = QWidget()
self.optional_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum)
optional_layout = QVBoxLayout()
optional_layout.setSpacing(2)
optional_layout.setContentsMargins(10, 4, 4, 20)
optional_layout.addWidget(QLabel("(frei lassen für globale Linkliste)"))
self.linkliste_widget = QgsFileWidget()
self.linkliste_widget.setStorageMode(QgsFileWidget.GetFile)
self.linkliste_widget.setFilter("Excelliste (*.xlsx)")
self.linkliste_widget.fileChanged.connect(self._on_linkliste_changed)
optional_layout.addWidget(self.linkliste_widget)
self.optional_content.setLayout(optional_layout)
self.optional_content.setVisible(False)
main_layout.addWidget(self.optional_content)
# Layer-Auswahl
# === LAYER-AUSWAHL + RAUMFILTER ===
layer_label = QLabel("Verfahrensgebiet-Layer auswählen")
layer_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(layer_label)
self.layer_combo = QgsMapLayerComboBox()
self.layer_combo.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum)
self.layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)
self.layer_combo.layerChanged.connect(self._on_layer_changed)
main_layout.addWidget(self.layer_combo)
# Raumfilter-Label + ComboBox (unterhalb der Layer-Auswahl)
main_layout.addWidget(QLabel("Raumfilter"))
self._raumfilter_combo = QComboBox(self)
# Fülle Optionen (Wrapper stellt addItems bereit)
try:
self._raumfilter_combo.addItems(list(RAUMFILTER_OPTIONS))
except Exception:
# fallback: iterativ hinzufügen, falls Wrapper andere API hat
for opt in RAUMFILTER_OPTIONS:
if hasattr(self._raumfilter_combo, "addItem"):
self._raumfilter_combo.addItem(opt)
# Initialisiere Auswahl aus Projekt-Variable oder Default
stored = get_variable(RAUMFILTER_VAR, scope="project")
if isinstance(stored, str) and stored in RAUMFILTER_OPTIONS:
try:
self._raumfilter_combo.setCurrentText(stored)
except Exception:
try:
idx = self._raumfilter_combo.findText(stored)
if idx is not None and idx >= 0:
self._raumfilter_combo.setCurrentIndex(idx)
except Exception:
pass
else:
try:
self._raumfilter_combo.setCurrentText(RAUMFILTER_DEFAULT)
except Exception:
try:
idx = self._raumfilter_combo.findText(RAUMFILTER_DEFAULT)
if idx is not None and idx >= 0:
self._raumfilter_combo.setCurrentIndex(idx)
except Exception:
pass
# persistiere Default, falls noch kein Wert gesetzt
if not stored:
set_variable(RAUMFILTER_VAR, RAUMFILTER_DEFAULT, scope="project")
# Signal: bei Änderung Variable setzen
try:
self._raumfilter_combo.currentTextChanged.connect(self._on_raumfilter_changed)
except Exception:
try:
self._raumfilter_combo.current_text_changed.connect(self._on_raumfilter_changed)
except Exception:
pass
self._raumfilter_combo.addItems(RAUMFILTER_OPTIONS)
self._raumfilter_combo.currentTextChanged.connect(self._on_raumfilter_changed)
main_layout.addWidget(self._raumfilter_combo)
# Neuer Button direkt unterhalb der Raumfilter-Combo: "Fachdaten laden"
# === PIPELINE-STEUERUNG ===
self.btn_pipeline = QPushButton("Fachdaten laden")
self.btn_pipeline.setToolTip("Starte Pipeline: Linkliste → DataGrabber → Datenschreiber → Log")
self.btn_pipeline.clicked.connect(self._on_run_pipeline)
main_layout.addWidget(self.btn_pipeline)
# (Optional) bestehender Button weiter unten für alternative Platzierung
self.btn_load = QPushButton("Fachdaten laden (alt)")
self.btn_load.clicked.connect(self._on_load_fachdaten)
main_layout.addWidget(self.btn_load)
@@ -229,267 +185,174 @@ class TabA(QWidget):
main_layout.addStretch(1)
self.setLayout(main_layout)
# ---------------------------------------------------------
# State Restore (UI-Wiederherstellung ohne Prüfungen)
# ---------------------------------------------------------
def _restore_state(self) -> None:
db = self.logic.load_verfahrens_db()
if db:
self.verfahrens_db = db
try:
self.file_widget.setFilePath(db)
except Exception:
pass
self._update_group_color()
link = self.logic.load_linkliste()
if link:
self.lokale_linkliste = link
try:
self.linkliste_widget.setFilePath(link)
except Exception:
pass
layer_id = self.logic.load_verfahrensgebiet_layer_id()
if layer_id:
layer = QgsProject.instance().mapLayer(layer_id)
if layer:
self.layer_combo.setLayer(layer)
# Raumfilter aus Variable wiederherstellen (falls Combo existiert)
"""Stellt UI-State aus Projektvariablen/Persistenz wieder her."""
# Verfahrens-DB
try:
stored = get_variable(RAUMFILTER_VAR, scope="project")
if stored and self._raumfilter_combo is not None:
try:
self._raumfilter_combo.setCurrentText(stored)
except Exception:
idx = self._raumfilter_combo.findText(stored)
if idx is not None and idx >= 0:
self._raumfilter_combo.setCurrentIndex(idx)
db = get_variable("tab_a_verfahrens_db", scope="project")
if db and self.file_widget:
self.file_widget.setFilePath(db)
self.verfahrens_db = db
self._update_group_color()
except Exception:
pass
# ---------------------------------------------------------
# UI-Callbacks (ohne Prüfungen / Exceptions)
# ---------------------------------------------------------
# Linkliste
try:
link = get_variable("tab_a_linkliste", scope="project")
if link and self.linkliste_widget:
self.linkliste_widget.setFilePath(link)
self.lokale_linkliste = link
except Exception:
pass
# Layer
try:
layer_id = get_variable("tab_a_layer_id", scope="project")
if layer_id:
layer = QgsProject.instance().mapLayer(layer_id)
if layer and self.layer_combo:
self.layer_combo.setLayer(layer)
self._pufferlayer = layer
except Exception:
pass
# Raumfilter (schon im _build_ui behandelt)
# === UI CALLBACKS ===
def _toggle_group(self, checked: bool) -> None:
"""Zeigt/verbirgt Verfahrens-DB-Gruppe."""
self.group_button.setArrowType(ArrowDown if checked else ArrowRight)
self.group_content.setVisible(checked)
def _toggle_optional(self, checked: bool) -> None:
"""Zeigt/verbirgt optionale Linkliste."""
self.optional_button.setArrowType(ArrowDown if checked else ArrowRight)
self.optional_content.setVisible(checked)
def _on_verfahrens_db_changed(self, path: str) -> None:
"""Persistieret Verfahrens-DB-Pfad."""
self.verfahrens_db = path
self.logic.set_verfahrens_db(path)
set_variable("tab_a_verfahrens_db", path, scope="project")
self._update_group_color()
def _on_linkliste_changed(self, path: str) -> None:
"""Persistieret lokale Linkliste."""
self.lokale_linkliste = path
self.logic.set_linkliste(path)
set_variable("tab_a_linkliste", path, scope="project")
def _on_layer_changed(self, layer) -> None:
self.logic.save_verfahrensgebiet_layer(layer)
"""Persistieret Layer-Auswahl."""
self._pufferlayer = layer
if layer:
set_variable("tab_a_layer_id", layer.id(), scope="project")
def _create_new_gpkg(self) -> None:
file_path, _ = QFileDialog.getSaveFileName(
self,
"Neue Verfahrens-Datenbank anlegen",
"",
"Geopackage (*.gpkg)",
)
if not file_path:
return
if not file_path.lower().endswith(".gpkg"):
file_path += ".gpkg"
# Delegation an TabALogic; TabALogic / Pruefmanager übernehmen Prüfungen
self.logic.create_new_verfahrens_db(file_path)
self.verfahrens_db = file_path
try:
self.file_widget.setFilePath(file_path)
except Exception:
pass
self._update_group_color()
def _on_load_fachdaten(self) -> None:
"""
Bestehender, kompakter Handler für 'Fachdaten laden'.
Führt Dateiprüfung und DataGrabber.run aus (wie zuvor).
"""
pfad = self.file_widget.filePath()
# Dateipruefer wird zur Laufzeit verwendet; hier nur der Aufruf
pruefer = Dateipruefer(pfad=pfad, temporaer_erlaubt=True)
ergebnis = pruefer.pruefe()
ergebnis = self.pruefmanager.verarbeite(ergebnis)
zielpfad = None
if ergebnis.kontext is not None:
try:
zielpfad = str(ergebnis.kontext)
except Exception:
zielpfad = ergebnis.kontext
# DataGrabber.run wird wie bisher aufgerufen; Signatur kann variieren.
# Wir übergeben die bekannten Parameter; DataGrabber ist verantwortlich,
# die Linkliste intern zu verwenden (z. B. aus TabALogic oder über Argumente).
try:
self.data_grabber.run(
attributes_list=self._attributes_list,
pufferlayer=self._pufferlayer,
zielpfad=zielpfad,
temporaer=(ergebnis.aktion == "temporaer_erzeugen"),
temporaer_erlaubt=True,
)
except Exception:
# Fehler werden vom Pruefmanager / DataGrabber protokolliert
pass
def _on_run_pipeline(self) -> None:
"""
Neuer, vollständiger Pipeline-Handler, der:
- Dateiprüfung (Verfahrens-DB)
- DataGrabber-Ausführung (mit Linkliste)
- Datenschreiber (schreiben, laden)
- Logschreiber (Log-Datei)
ausführt und Ergebnisse über den Pruefmanager protokolliert.
"""
# 1) Verfahrens-DB prüfen / ermitteln
pfad = self.file_widget.filePath()
pruefer = Dateipruefer(pfad=pfad, temporaer_erlaubt=True)
ergebnis = pruefer.pruefe()
ergebnis = self.pruefmanager.verarbeite(ergebnis)
zielpfad = None
if ergebnis.kontext is not None:
try:
zielpfad = str(ergebnis.kontext)
except Exception:
zielpfad = ergebnis.kontext
if not zielpfad:
# Falls kein Zielpfad ermittelt werden konnte, protokollieren und abbrechen
pe_err = pruef_ergebnis(
ok=False,
meldung="Kein gültiger Speicherort für Verfahrens-DB ermittelt; Pipeline abgebrochen.",
aktion="kein_dateipfad",
kontext={},
)
self.pruefmanager.verarbeite(pe_err)
return
# 2) DataGrabber ausführen
# Erwartung: DataGrabber.run gibt (daten_dict, processed_results) zurück.
# Falls die konkrete Implementierung anders ist, passt dieser Aufruf entsprechend an.
try:
run_result = self.data_grabber.run(
attributes_list=self._attributes_list,
pufferlayer=self._pufferlayer,
zielpfad=zielpfad,
temporaer=(ergebnis.aktion == "temporaer_erzeugen"),
temporaer_erlaubt=True,
)
except Exception as exc:
pe_err = pruef_ergebnis(
ok=False,
meldung=f"DataGrabber-Fehler: {exc}",
aktion="datenabruf",
kontext={},
)
self.pruefmanager.verarbeite(pe_err)
return
# Normalisiere Rückgabe: unterstütze sowohl None, einzelnes dict oder Tuple
daten_dict = {}
processed_results = []
if isinstance(run_result, tuple) and len(run_result) >= 2:
daten_dict, processed_results = run_result[0], run_result[1]
elif isinstance(run_result, dict) and "daten" in run_result:
daten_dict = run_result
# processed_results bleiben leer oder werden vom DataGrabber intern protokolliert
else:
# Wenn run() nichts zurückgibt, versuchen wir, auf DataGrabber intern gespeicherte Ergebnisse zuzugreifen
daten_dict = getattr(self.data_grabber, "last_daten_dict", {}) or {}
processed_results = getattr(self.data_grabber, "last_processed_results", []) or []
# 3) Datenschreiber: Daten in GPKG schreiben
try:
ds = Datenschreiber(pruefmanager=self.pruefmanager, gpkg_path=zielpfad)
layer_infos = ds.schreibe_Daten(daten_dict=daten_dict, processed_results=processed_results, speicherort=zielpfad)
except Exception as exc:
pe_err = pruef_ergebnis(
ok=False,
meldung=f"Fehler beim Schreiben der Daten: {exc}",
aktion="save_exception",
kontext={},
)
self.pruefmanager.verarbeite(pe_err)
return
# 4) Layer laden und Stile anwenden
try:
ds.lade_Layer(layer_infos)
except Exception as exc:
pe_warn = pruef_ergebnis(
ok=True,
meldung=f"Fehler beim Laden der Layer: {exc}",
aktion="layer_nicht_gefunden",
kontext={},
)
self.pruefmanager.verarbeite(pe_warn)
# 5) Log schreiben
try:
log_path = ds.schreibe_log(processed_results=processed_results, speicherort=zielpfad)
# Optional: zeige Erfolgsmeldung
try:
QMessageBox.information(self, "Pipeline abgeschlossen", f"Pipeline erfolgreich abgeschlossen.\nLog: {log_path}")
except Exception:
pass
except Exception as exc:
pe_warn = pruef_ergebnis(
ok=True,
meldung=f"Log konnte nicht geschrieben werden: {exc}",
aktion="standarddatei_vorschlagen",
kontext={},
)
self.pruefmanager.verarbeite(pe_warn)
# ---------------------------------------------------------
# Raumfilter Callback
# ---------------------------------------------------------
def _on_raumfilter_changed(self, value: str) -> None:
# Persistiere Auswahl in Projekt-Variable; Prüfungen übernimmt die Laufzeitlogik
"""Persistieret Raumfilter-Auswahl."""
set_variable(RAUMFILTER_VAR, value, scope="project")
# ---------------------------------------------------------
# UI-Helfer
# ---------------------------------------------------------
def _prompt_user_to_select_file(self) -> None:
fname, _ = QFileDialog.getOpenFileName(
self,
"Verfahrens-DB auswählen",
"",
"Geopackage (*.gpkg)",
def _create_new_gpkg(self) -> None:
"""Delegiert GPKG-Erstellung (Prüfungen über Services)."""
file_path, _ = QFileDialog.getSaveFileName(
self, "Neue Verfahrens-Datenbank", "", "Geopackage (*.gpkg)"
)
if fname:
try:
self.file_widget.setFilePath(fname)
except Exception:
try:
self.file_widget.setFileName(fname)
except Exception:
self.file_widget.setProperty("filePath", fname)
self.verfahrens_db = fname
self.logic.set_verfahrens_db(fname)
if file_path:
if not file_path.lower().endswith(".gpkg"):
file_path += ".gpkg"
self.verfahrens_db = file_path
self.file_widget.setFilePath(file_path)
set_variable("tab_a_verfahrens_db", file_path, scope="project")
self._update_group_color()
def _update_group_color(self) -> None:
"""Visuelles Feedback für Verfahrens-DB-Status."""
if self.verfahrens_db:
self.group_button.setStyleSheet("font-weight: bold;")
self.group_button.setStyleSheet("font-weight: bold; background-color: #e0f7e0;")
else:
self.group_button.setStyleSheet("")
self.group_button.setStyleSheet("font-weight: bold;")
# === PIPELINE ===
def _on_run_pipeline(self) -> None:
"""DEBUG: Pipeline mit maximaler Ausgabe."""
print("\n" + "="*60)
print("🚀 _on_run_pipeline GESTARTET")
print("="*60)
# 🔥 DEBUG QT STATUS
from sn_basis.functions import qt_wrapper
qt_wrapper.debug_qt_status() # ← Zeigt EXAKT was läuft!
# 1. Services prüfen
print(f"pruefmanager: {self.pruefmanager is not None}")
print(f"data_grabber: {self.data_grabber is not None}")
print(f"logic: {hasattr(self, 'logic')}")
if not self.pruefmanager:
print("❌ FEHLER: self.pruefmanager fehlt!")
return
if not self.data_grabber:
print("❌ FEHLER: self.data_grabber fehlt!")
return
print("✅ Services OK")
# 2. FileWidget
source = self.file_widget.filePath()
print(f"📁 Eingabe: '{source}' (len={len(source or '')})")
# 3. Dateipruefer
print("🔍 Dateipruefer starte...")
try:
pruefer = Dateipruefer(
source,
basis_pfad="",
leereingabe_erlaubt=False,
standarddatei=None,
temporaer_erlaubt=True, # ✅ Explizit True
verfahrens_db_modus=True # ✅ Keyword-only
)
ergebnis1 = pruefer.pruefe()
print(f" → ok={ergebnis1.ok}, aktion='{ergebnis1.aktion}', kontext={ergebnis1.kontext}")
except Exception as e:
print(f"💥 Dateipruefer FEHLER: {e}")
import traceback
traceback.print_exc()
return
# 4. Pruefmanager
print("🤖 Pruefmanager starte...")
try:
ergebnis2 = self.pruefmanager.verarbeite(ergebnis1)
print(f" → ok={ergebnis2.ok}, aktion='{ergebnis2.aktion}', kontext={ergebnis2.kontext}")
except Exception as e:
print(f"💥 Pruefmanager FEHLER: {e}")
import traceback
traceback.print_exc()
return
# 5. Entscheidung
weiter = ergebnis2.ok
print(f"➡️ Weiter? {weiter} (aktion='{ergebnis2.aktion}')")
if weiter:
final_pfad = ergebnis2.kontext if ergebnis2.kontext else source
print(f"🚀 DataGrabber mit: '{final_pfad}'")
try:
self.data_grabber.run(final_pfad)
print("✅ DataGrabber aufgerufen!")
except Exception as e:
print(f"💥 DataGrabber FEHLER: {e}")
import traceback
traceback.print_exc()
else:
print("⏹️ Pipeline gestoppt (erwartet bei leerem Pfad)")
print("="*60 + "\n")
def _on_load_fachdaten(self) -> None:
"""Kompatibilitäts-Handler → neue Pipeline."""
self._on_run_pipeline()