Wrappe modular aufgebaut, Tests erfolgreich, Menüleiste und Werzeugleiste werden eingetragen (QT6 und QT5)- (Es fehlen noch Fachplugins, um zu prüfen, ob es auch wirklich in QGIS geht)

This commit is contained in:
2025-12-19 14:29:52 +01:00
parent e8fea163b5
commit f88b5da51f
37 changed files with 1886 additions and 1679 deletions

View File

@@ -1,12 +1,13 @@
"""
sn_basis/modulesdateipruefer.py Prüfung von Dateieingaben für das Plugin.
Verwendet syswrapper und gibt pruef_ergebnis an den Pruefmanager zurück.
sn_basis/modules/Dateipruefer.py Prüfung von Dateieingaben für das Plugin.
Verwendet sys_wrapper und gibt pruef_ergebnis an den Pruefmanager zurück.
"""
from sn_basis.functions.syswrapper import (
file_exists,
is_file,
from pathlib import Path
from sn_basis.functions import (
join_path,
file_exists,
)
from sn_basis.modules.Pruefmanager import pruef_ergebnis
@@ -32,13 +33,14 @@ class Dateipruefer:
self.standarddatei = standarddatei
self.temporaer_erlaubt = temporaer_erlaubt
# ---------------------------------------------------------
# Hilfsfunktion
# ---------------------------------------------------------
def _pfad(self, relativer_pfad: str) -> str:
"""Erzeugt einen OSunabhängigen Pfad relativ zum Basisverzeichnis."""
def _pfad(self, relativer_pfad: str) -> Path:
"""
Erzeugt einen OSunabhängigen Pfad relativ zum Basisverzeichnis.
"""
return join_path(self.basis_pfad, relativer_pfad)
# ---------------------------------------------------------
@@ -62,12 +64,12 @@ class Dateipruefer:
# -----------------------------------------------------
pfad = self._pfad(self.pfad)
if not file_exists(pfad) or not is_file(pfad):
if not file_exists(pfad):
return pruef_ergebnis(
ok=False,
meldung=f"Die Datei '{self.pfad}' wurde nicht gefunden.",
aktion="datei_nicht_gefunden",
pfad=pfad,
kontext=pfad,
)
# -----------------------------------------------------
@@ -77,7 +79,7 @@ class Dateipruefer:
ok=True,
meldung="Datei gefunden.",
aktion="ok",
pfad=pfad,
kontext=pfad,
)
# ---------------------------------------------------------
@@ -96,25 +98,31 @@ class Dateipruefer:
ok=False,
meldung="Das Dateifeld ist leer. Soll ohne Datei fortgefahren werden?",
aktion="leereingabe_erlaubt",
pfad=None,
kontext=None,
)
# 2. Standarddatei verfügbar → Nutzer fragen, ob sie verwendet werden soll
if self.standarddatei:
return pruef_ergebnis(
ok=False,
meldung=f"Es wurde keine Datei angegeben. Soll die Standarddatei '{self.standarddatei}' verwendet werden?",
meldung=(
f"Es wurde keine Datei angegeben. "
f"Soll die Standarddatei '{self.standarddatei}' verwendet werden?"
),
aktion="standarddatei_vorschlagen",
pfad=self._pfad(self.standarddatei),
kontext=self._pfad(self.standarddatei),
)
# 3. Temporäre Datei erlaubt → Nutzer fragen, ob temporär gearbeitet werden soll
if self.temporaer_erlaubt:
return pruef_ergebnis(
ok=False,
meldung="Es wurde keine Datei angegeben. Soll eine temporäre Datei erzeugt werden?",
meldung=(
"Es wurde keine Datei angegeben. "
"Soll eine temporäre Datei erzeugt werden?"
),
aktion="temporaer_erlaubt",
pfad=None,
kontext=None,
)
# 4. Leereingabe nicht erlaubt → Fehler
@@ -122,5 +130,5 @@ class Dateipruefer:
ok=False,
meldung="Es wurde keine Datei angegeben.",
aktion="leereingabe_nicht_erlaubt",
pfad=None,
kontext=None,
)

View File

@@ -1,14 +1,14 @@
"""
sn_basis/modules/pruefmanager.py zentrale Verarbeitung von pruef_ergebnis-Objekten.
Steuert die Nutzerinteraktion über qgisqt_wrapper.
sn_basis/modules/Pruefmanager.py zentrale Verarbeitung von pruef_ergebnis-Objekten.
Steuert die Nutzerinteraktion über Wrapper.
"""
from sn_basis.functions.qgisqt_wrapper import (
from sn_basis.functions import (
ask_yes_no,
info,
warning,
error,
set_layer_visible, # optional, falls implementiert
set_layer_visible,
)
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
@@ -36,6 +36,7 @@ class Pruefmanager:
return ergebnis
aktion = ergebnis.aktion
kontext = ergebnis.kontext
# -----------------------------------------------------
# Allgemeine Aktionen
@@ -47,7 +48,12 @@ class Pruefmanager:
if aktion == "leereingabe_erlaubt":
if ask_yes_no("Ohne Eingabe fortfahren", ergebnis.meldung):
return pruef_ergebnis(True, "Ohne Eingabe fortgefahren.", "ok", None)
return pruef_ergebnis(
ok=True,
meldung="Ohne Eingabe fortgefahren.",
aktion="ok",
kontext=None,
)
return ergebnis
if aktion == "leereingabe_nicht_erlaubt":
@@ -56,12 +62,22 @@ class Pruefmanager:
if aktion == "standarddatei_vorschlagen":
if ask_yes_no("Standarddatei verwenden", ergebnis.meldung):
return pruef_ergebnis(True, "Standarddatei wird verwendet.", "ok", ergebnis.pfad)
return pruef_ergebnis(
ok=True,
meldung="Standarddatei wird verwendet.",
aktion="ok",
kontext=kontext,
)
return ergebnis
if aktion == "temporaer_erlaubt":
if ask_yes_no("Temporäre Datei erzeugen", ergebnis.meldung):
return pruef_ergebnis(True, "Temporäre Datei soll erzeugt werden.", "temporaer_erzeugen", None)
return pruef_ergebnis(
ok=True,
meldung="Temporäre Datei soll erzeugt werden.",
aktion="temporaer_erzeugen",
kontext=None,
)
return ergebnis
if aktion == "datei_nicht_gefunden":
@@ -94,12 +110,18 @@ class Pruefmanager:
if aktion == "layer_unsichtbar":
if ask_yes_no("Layer einblenden", ergebnis.meldung):
# Falls set_layer_visible implementiert ist
try:
set_layer_visible(ergebnis.pfad, True)
except Exception:
pass
return pruef_ergebnis(True, "Layer wurde eingeblendet.", "ok", ergebnis.pfad)
if kontext is not None:
try:
set_layer_visible(kontext, True)
except Exception:
pass
return pruef_ergebnis(
ok=True,
meldung="Layer wurde eingeblendet.",
aktion="ok",
kontext=kontext,
)
return ergebnis
if aktion == "falscher_geotyp":

View File

@@ -1,9 +1,9 @@
"""
sn_basis/modules/layerpruefer.py Prüfung von QGIS-Layern.
Verwendet ausschließlich qgisqt_wrapper und gibt pruef_ergebnis zurück.
Verwendet ausschließlich Wrapper und gibt pruef_ergebnis zurück.
"""
from sn_basis.functions.qgisqt_wrapper import (
from sn_basis.functions import (
layer_exists,
get_layer_geometry_type,
get_layer_feature_count,
@@ -15,7 +15,7 @@ from sn_basis.functions.qgisqt_wrapper import (
is_layer_editable,
)
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion
class Layerpruefer:
@@ -57,8 +57,8 @@ class Layerpruefer:
return pruef_ergebnis(
ok=False,
meldung="Der Layer existiert nicht oder wurde nicht geladen.",
aktion="layer_nicht_gefunden", # type: ignore
pfad=None,
aktion="layer_nicht_gefunden",
kontext=None,
)
# -----------------------------------------------------
@@ -69,8 +69,8 @@ class Layerpruefer:
return pruef_ergebnis(
ok=False,
meldung="Der Layer ist unsichtbar. Soll er eingeblendet werden?",
aktion="layer_unsichtbar", # type: ignore
pfad=self.layer, # Layerobjekt wird übergeben
aktion="layer_unsichtbar",
kontext=self.layer, # Layerobjekt als Kontext
)
# -----------------------------------------------------
@@ -80,9 +80,12 @@ class Layerpruefer:
if self.erwarteter_layertyp and layertyp != self.erwarteter_layertyp:
return pruef_ergebnis(
ok=False,
meldung=f"Der Layer hat den Typ '{layertyp}', erwartet wurde '{self.erwarteter_layertyp}'.",
meldung=(
f"Der Layer hat den Typ '{layertyp}', "
f"erwartet wurde '{self.erwarteter_layertyp}'."
),
aktion="falscher_layertyp",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -92,9 +95,12 @@ class Layerpruefer:
if self.erwarteter_geotyp and geotyp != self.erwarteter_geotyp:
return pruef_ergebnis(
ok=False,
meldung=f"Der Layer hat den Geometrietyp '{geotyp}', erwartet wurde '{self.erwarteter_geotyp}'.",
meldung=(
f"Der Layer hat den Geometrietyp '{geotyp}', "
f"erwartet wurde '{self.erwarteter_geotyp}'."
),
aktion="falscher_geotyp",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -106,7 +112,7 @@ class Layerpruefer:
ok=False,
meldung="Der Layer enthält keine Objekte.",
aktion="layer_leer",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -116,9 +122,12 @@ class Layerpruefer:
if self.erwartetes_crs and crs != self.erwartetes_crs:
return pruef_ergebnis(
ok=False,
meldung=f"Der Layer hat das CRS '{crs}', erwartet wurde '{self.erwartetes_crs}'.",
meldung=(
f"Der Layer hat das CRS '{crs}', "
f"erwartet wurde '{self.erwartetes_crs}'."
),
aktion="falsches_crs",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -130,9 +139,12 @@ class Layerpruefer:
if fehlende:
return pruef_ergebnis(
ok=False,
meldung=f"Der Layer enthält nicht alle erforderlichen Felder: {', '.join(fehlende)}",
meldung=(
"Der Layer enthält nicht alle erforderlichen Felder: "
+ ", ".join(fehlende)
),
aktion="felder_fehlen",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -144,7 +156,7 @@ class Layerpruefer:
ok=False,
meldung=f"Die Datenquelle '{quelle}' ist nicht erlaubt.",
aktion="datenquelle_unerwartet",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -156,7 +168,7 @@ class Layerpruefer:
ok=False,
meldung="Der Layer ist nicht editierbar.",
aktion="layer_nicht_editierbar",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -166,5 +178,5 @@ class Layerpruefer:
ok=True,
meldung="Layerprüfung erfolgreich.",
aktion="ok",
pfad=None,
kontext=None,
)

View File

@@ -1,20 +1,17 @@
"""
sn_basis/modules/linkpruefer.py Prüfung von URLs und lokalen Links.
Verwendet syswrapper und qgisqt_wrapper.
Gibt pruef_ergebnis an den Pruefmanager zurück.
Verwendet Wrapper und gibt pruef_ergebnis an den Pruefmanager zurück.
"""
from sn_basis.functions.syswrapper import (
file_exists,
is_file,
join_path,
)
from pathlib import Path
from sn_basis.functions.qgisqt_wrapper import (
from sn_basis.functions import (
file_exists,
join_path,
network_head,
)
from sn_basis.modules.Pruefmanager import pruef_ergebnis
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion
class Linkpruefer:
@@ -33,14 +30,18 @@ class Linkpruefer:
# Hilfsfunktionen
# ---------------------------------------------------------
def _pfad(self, relativer_pfad: str) -> str:
"""Erzeugt einen OS-unabhängigen Pfad relativ zum Basisverzeichnis."""
def _pfad(self, relativer_pfad: str) -> Path:
"""
Erzeugt einen OSunabhängigen Pfad relativ zum Basisverzeichnis.
"""
if not self.basis:
return relativer_pfad
return Path(relativer_pfad)
return join_path(self.basis, relativer_pfad)
def _ist_url(self, text: str) -> bool:
"""Einfache URL-Erkennung."""
"""
Einfache URL-Erkennung.
"""
return text.startswith("http://") or text.startswith("https://")
# ---------------------------------------------------------
@@ -58,7 +59,7 @@ class Linkpruefer:
ok=False,
meldung="Es wurde kein Link angegeben.",
aktion="leer",
pfad=None,
kontext=None,
)
# -----------------------------------------------------
@@ -88,7 +89,7 @@ class Linkpruefer:
ok=False,
meldung=f"Die URL '{url}' konnte nicht geprüft werden.",
aktion="netzwerkfehler",
pfad=url,
kontext=url,
)
if reply.error != 0:
@@ -96,14 +97,14 @@ class Linkpruefer:
ok=False,
meldung=f"Die URL '{url}' ist nicht erreichbar.",
aktion="url_nicht_erreichbar",
pfad=url,
kontext=url,
)
return pruef_ergebnis(
ok=True,
meldung="URL ist erreichbar.",
aktion="ok",
pfad=url,
kontext=url,
)
# ---------------------------------------------------------
@@ -122,20 +123,12 @@ class Linkpruefer:
ok=False,
meldung=f"Der Pfad '{eingabe}' wurde nicht gefunden.",
aktion="pfad_nicht_gefunden",
pfad=pfad,
)
if not is_file(pfad):
return pruef_ergebnis(
ok=False,
meldung=f"Der Pfad '{eingabe}' ist keine Datei.",
aktion="kein_dateipfad",
pfad=pfad,
kontext=pfad,
)
return pruef_ergebnis(
ok=True,
meldung="Dateipfad ist gültig.",
aktion="ok",
pfad=pfad,
kontext=pfad,
)

View File

@@ -1,10 +1,10 @@
"""
sn_basis/modules/pruef_ergebnis.py Ergebnisobjekt für alle Prüfer.
"""
from dataclasses import dataclass
from typing import Optional, Literal
from pathlib import Path
from typing import Any, Optional, Literal
# Alle möglichen Aktionen, die ein Prüfer auslösen kann.
@@ -31,28 +31,19 @@ PruefAktion = Literal[
"temporaer_erzeugen",
"stil_nicht_anwendbar",
"layer_unsichtbar",
"layer_nicht_gefunden",
"unbekannt",
"stil_anwendbar",
"falsche_endung",
]
@dataclass
@dataclass(slots=True)
class pruef_ergebnis:
"""
Reines Datenobjekt, das das Ergebnis einer Prüfung beschreibt.
ok: True → Prüfung erfolgreich
False → Nutzerinteraktion oder Fehler nötig
meldung: Text, der dem Nutzer angezeigt werden soll
aktion: Maschinenlesbarer Code, der dem Pruefmanager sagt,
wie er weiter verfahren soll
pfad: Optionaler Pfad oder URL, die geprüft wurde oder
verwendet werden soll
"""
ok: bool
meldung: str
aktion: PruefAktion
pfad: Optional[str] = None
kontext: Optional[Any] = None

View File

@@ -1,59 +1,75 @@
"""
sn_basis/modules/stilpruefer.py Prüfung und Anwendung von Layerstilen.
Verwendet ausschließlich qgisqt_wrapper und gibt pruef_ergebnis zurück.
sn_basis/modules/stilpruefer.py Prüfung von Layerstilen.
Prüft ausschließlich, ob ein Stilpfad gültig ist.
Die Anwendung erfolgt später über eine Aktion.
"""
from sn_basis.functions.qgisqt_wrapper import (
apply_style,
)
from pathlib import Path
from sn_basis.functions import file_exists
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
class Stilpruefer:
"""
Prüft, ob ein Stil auf einen Layer angewendet werden kann.
Die eigentliche Nutzerinteraktion übernimmt der Pruefmanager.
Prüft, ob ein Stilpfad gültig ist und angewendet werden kann.
Keine Seiteneffekte, keine QGIS-Aufrufe.
"""
def __init__(self, layer, stil_pfad: str):
"""
layer: QGIS-Layer oder Mock-Layer
stil_pfad: relativer oder absoluter Pfad zum .qml-Stil
"""
self.layer = layer
self.stil_pfad = stil_pfad
def __init__(self):
pass
# ---------------------------------------------------------
# Hauptfunktion
# ---------------------------------------------------------
def pruefe(self) -> pruef_ergebnis:
def pruefe(self, stil_pfad: str) -> pruef_ergebnis:
"""
Versucht, den Stil anzuwenden.
Prüft einen Stilpfad.
Rückgabe: pruef_ergebnis
"""
# Wrapper übernimmt:
# - Pfadberechnung
# - Existenzprüfung
# - loadNamedStyle
# - Fehlerbehandlung
# - Mock-Modus
erfolg, meldung = apply_style(self.layer, self.stil_pfad)
if erfolg:
# -----------------------------------------------------
# 1. Kein Stil angegeben → OK
# -----------------------------------------------------
if not stil_pfad:
return pruef_ergebnis(
ok=True,
meldung=f"Stil erfolgreich angewendet: {self.stil_pfad}",
meldung="Kein Stil angegeben.",
aktion="ok",
pfad=self.stil_pfad,
kontext=None,
)
# Fehlerfall → Nutzerinteraktion nötig
pfad = Path(stil_pfad)
# -----------------------------------------------------
# 2. Datei existiert nicht
# -----------------------------------------------------
if not file_exists(pfad):
return pruef_ergebnis(
ok=False,
meldung=f"Die Stil-Datei '{stil_pfad}' wurde nicht gefunden.",
aktion="datei_nicht_gefunden",
kontext=pfad,
)
# -----------------------------------------------------
# 3. Falsche Endung
# -----------------------------------------------------
if pfad.suffix.lower() != ".qml":
return pruef_ergebnis(
ok=False,
meldung="Die Stil-Datei muss die Endung '.qml' haben.",
aktion="falsche_endung",
kontext=pfad,
)
# -----------------------------------------------------
# 4. Stil ist gültig → Anwendung später
# -----------------------------------------------------
return pruef_ergebnis(
ok=False,
meldung=meldung,
aktion="stil_nicht_anwendbar",
pfad=self.stil_pfad,
ok=True,
meldung="Stil-Datei ist gültig.",
aktion="stil_anwendbar",
kontext=pfad,
)