""" sn_basis/modules/Dateipruefer.py Erweiterter Dateiprüfer für Verfahrens-DB-Workflows mit vollständiger Unterstützung der Anforderungen 1-2.e (leerer Pfad, fehlende Datei, bestehende Datei). """ from pathlib import Path from typing import Optional, Literal from sn_basis.functions.sys_wrapper import join_path, file_exists from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion DateiTyp = Literal["excel","datenbank","unbekannt"] class Dateipruefer: """ Prüft Dateieingaben für Verfahrens-DB-Workflows und liefert :class:`pruef_ergebnis`. **Funktionsweise (deine Anforderungen 1-2.e):** +---------------------+------------------------------------------+---------------+ | **Fall** | **Ergebnis** | **ok** | +=====================+==========================================+===============+ | 1. Leerer Pfad | ``temporaer_erlaubt`` | False | +---------------------+------------------------------------------+---------------+ | 2.a Leerer Pfad | Pruefmanager fragt → ``temporaer_erzeugen`` | True | +---------------------+------------------------------------------+---------------+ | 2.b Datei existiert | ``ok`` | True | +---------------------+------------------------------------------+---------------+ | 2.c Ungültiger Pfad | ``datei_nicht_gefunden`` | False | +---------------------+------------------------------------------+---------------+ | **2.d Datei fehlt** | **``datei_wird_erzeugt``** | **True** | +---------------------+------------------------------------------+---------------+ | **2.e Datei da** | **``datei_existiert``** | **False** | +---------------------+------------------------------------------+---------------+ Der Dateiprüfer führt **keine UI-Interaktion** durch. Entscheidungen werden ausschließlich vom :class:`Pruefmanager` getroffen. """ def __init__( self, pfad: Optional[str], basis_pfad: str = "", leereingabe_erlaubt: bool = False, standarddatei: Optional[str] = None, temporaer_erlaubt: bool = False, *, verfahrens_db_modus: bool = True, # 🆕 Verfahrens-DB-spezifische Logik ) -> None: """ Parameters ---------- pfad : Optional[str] Vom UI gelieferter Dateipfad (kann leer oder Whitespace sein). basis_pfad : str, optional Basisverzeichnis für relative Pfade (default: ""). leereingabe_erlaubt : bool, optional Ob leere Eingabe grundsätzlich erlaubt ist (default: False). standarddatei : Optional[str], optional Optionaler Standardpfad (default: None). temporaer_erlaubt : bool, optional Ob bei leerer Eingabe temporäre Layer erlaubt sind (default: False). verfahrens_db_modus : bool, optional Aktiviert Verfahrens-DB-spezifische Logik (2.d, 2.e) (default: True). """ self.pfad = pfad self.basis_pfad = basis_pfad self.leereingabe_erlaubt = leereingabe_erlaubt self.standarddatei = standarddatei self.temporaer_erlaubt = temporaer_erlaubt self.verfahrens_db_modus = verfahrens_db_modus # ------------------------------------------------------------------ # Hilfsfunktionen # ------------------------------------------------------------------ def erkenne_dateityp(self, pfad: Path) -> DateiTyp: """ Erkennt den Dateityp anhand der Endung. """ suffix = pfad.suffix.lower() if suffix == ".xlsx": return "excel" if suffix in (".gpkg", ".sqlite"): return "datenbank" return "unbekannt" def _pfad(self, relativer_pfad: str) -> Path: """Erzeugt OS-unabhängigen Pfad relativ zum Basisverzeichnis.""" return join_path(self.basis_pfad, relativer_pfad) def _ist_leer(self) -> bool: """ Prüft robust, ob Eingabe als „leer" zu behandeln ist. Returns ------- bool True bei None, leerem String oder reinem Whitespace. """ if self.pfad is None: return True if not isinstance(self.pfad, str): return True return not self.pfad.strip() def _ist_gueltiger_gpkg_pfad(self, pfad: Path) -> bool: """ Prüft, ob Pfad für GPKG geeignet ist (Endung + Schreibrechte). Returns ------- bool True wenn `.gpkg`-Endung und Verzeichnis beschreibbar. """ if not str(pfad).lower().endswith('.gpkg'): return False # Verzeichnis muss beschreibbar sein return pfad.parent.exists() and pfad.parent.is_dir() # ------------------------------------------------------------------ # Hauptlogik: deine Anforderungen 1-2.e # ------------------------------------------------------------------ def pruefe(self) -> pruef_ergebnis: """ 🆕 Prüft Dateieingabe gemäß Anforderungen 1-2.e. **Workflow:** 1. **Leere Eingabe** → ``temporaer_erlaubt`` (Pruefmanager fragt) 2. **Pfad prüfen**: - **Ungültig** → 2.c ``datei_nicht_gefunden`` - **Gültig, fehlt** → **2.d** ``datei_wird_erzeugt`` (ok=True!) - **Gültig, existiert** → **2.e** ``datei_existiert`` (Pruefmanager fragt) 3. **Datei OK** → 2.b ``ok`` Returns ------- pruef_ergebnis Mit korrekter Aktion für jeden Fall. """ # 1. 🎯 ANFORDERUNG 1: Leere Eingabe if self._ist_leer(): return self._handle_leere_eingabe() # 2. Pfad normalisieren pfad = self._pfad(self.pfad.strip()) #Excel-dateien erkennen dateityp = self.erkenne_dateityp(pfad) if dateityp == "excel": if not file_exists(pfad): return pruef_ergebnis( ok=False, meldung=f"Excel-Datei '{self.pfad}' wurde nicht gefunden.", aktion="datei_nicht_gefunden", kontext=pfad, ) return pruef_ergebnis( ok=True, meldung="Excel-Datei ist gültig.", aktion="ok", kontext=pfad, ) if dateityp != "datenbank": return pruef_ergebnis( ok=False, meldung=f"Der Pfad '{self.pfad}' ist kein unterstützter Dateityp.", aktion="unbekannter_dateityp", kontext=pfad, ) # 🆕 2.c: Ungültiger GPKG-Pfad? if not self.verfahrens_db_modus or not self._ist_gueltiger_gpkg_pfad(pfad): return pruef_ergebnis( ok=False, meldung=f"Der Pfad '{self.pfad}' ist kein gültiger GPKG-Pfad.", aktion="datei_nicht_gefunden", kontext=pfad, ) # 🆕 2.d: Gültiger Pfad, Datei fehlt → DIREKT WEITER (ok=True!) if not file_exists(pfad): return pruef_ergebnis( ok=True, # 🎯 WICHTIG: Pipeline fortsetzen! meldung=f"Datei '{self.pfad}' wird erzeugt.", aktion="datei_wird_erzeugt", kontext=pfad, ) # 🆕 2.e: Datei existiert → Pruefmanager fragt Überschreiben/etc. return pruef_ergebnis( ok=False, # 🎯 Pruefmanager soll 4-Optionen-Dialog zeigen meldung=f"Datei '{self.pfad}' existiert bereits.", aktion="datei_existiert", kontext=pfad, ) # 2.b: Wird nicht erreicht (durch 2.e abgefangen) # ------------------------------------------------------------------ # Leere Eingabe (ANFORDERUNG 1, 2.a) # ------------------------------------------------------------------ def _handle_leere_eingabe(self) -> pruef_ergebnis: """ Behandelt leere Eingaben (Priorität: leereingabe → Standard → temporär → Fehler). """ if self.leereingabe_erlaubt: return pruef_ergebnis( ok=False, meldung="Das Dateifeld ist leer. Soll ohne Datei fortgefahren werden?", aktion="leereingabe_erlaubt", kontext=None, ) if self.standarddatei: return pruef_ergebnis( ok=False, meldung=( "Es wurde keine Datei angegeben. " f"Soll die Standarddatei '{self.standarddatei}' verwendet werden?" ), aktion="standarddatei_vorschlagen", kontext=self._pfad(self.standarddatei), ) if self.temporaer_erlaubt: return pruef_ergebnis( ok=False, meldung=( "Es wurde keine Datei angegeben. " "Sollen temporäre Layer erzeugt werden?" ), aktion="temporaer_erlaubt", kontext=None, ) return pruef_ergebnis( ok=False, meldung="Es wurde keine Datei angegeben.", aktion="leereingabe_nicht_erlaubt", kontext=None, )