This commit is contained in:
2025-04-17 18:28:45 +00:00
parent 04585f2b20
commit 9a615a88fc

View File

@@ -1092,82 +1092,116 @@ def token_count(text):
class GoogleSheetHandler:
def __init__(self):
"""Initialisiert den Handler, verbindet und lädt initiale Daten."""
self.sheet = None
self.sheet_values = []
self.headers = []
self.headers = [] # Speichert die erste Zeile als Header-Namen
try:
self._connect()
if self.sheet:
self.load_data()
self.load_data() # Erste Datenladung bei Initialisierung
except Exception as e:
debug_print(f"FATAL: Fehler bei Initialisierung von GoogleSheetHandler: {e}")
# Wirft einen Fehler, damit das Hauptprogramm weiß, dass es nicht weitergehen kann
raise ConnectionError(f"Google Sheet Handler Init failed: {e}")
# retry_on_failure Decorator sollte hier angewendet werden
@retry_on_failure
def _connect(self):
self.sheet = None
"""Stellt Verbindung zum Google Sheet her."""
self.sheet = None # Sicherstellen, dass sheet vor try None ist
debug_print("Verbinde mit Google Sheets...")
try:
scope = ["https://www.googleapis.com/auth/spreadsheets"]
creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
gc = gspread.authorize(creds)
sh = gc.open_by_url(Config.SHEET_URL)
self.sheet = sh.sheet1
self.sheet = sh.sheet1 # Greift auf das erste Blatt zu
debug_print("Verbindung zu Google Sheets erfolgreich.")
except gspread.exceptions.APIError as e:
# Logge spezifische API-Fehler von Google
debug_print(f"FEHLER bei Google API Verbindung: Status {e.response.status_code} - {e.response.text[:200]}")
raise e
raise e # Fehler weitergeben, damit retry greift
except Exception as e:
# Logge andere Verbindungsfehler
debug_print(f"FEHLER bei der Google Sheets Verbindung: {type(e).__name__} - {e}")
raise e
raise e # Fehler weitergeben
# retry_on_failure Decorator sollte hier angewendet werden
@retry_on_failure
def load_data(self):
"""Lädt alle Daten aus dem Sheet und aktualisiert self.sheet_values und self.headers."""
if not self.sheet:
debug_print("Fehler: Keine Sheet-Verbindung zum Laden der Daten.")
self.sheet_values = []
self.headers = []
return False
return False # Signalisiert Fehler
debug_print("Lade Daten aus Google Sheet...")
try:
self.sheet_values = self.sheet.get_all_values()
self.sheet_values = self.sheet.get_all_values() # Daten neu holen
if not self.sheet_values:
debug_print("Warnung: Google Sheet scheint leer zu sein oder keine Daten zurückgegeben.")
self.headers = []
return True
if len(self.sheet_values) >= 1: self.headers = self.sheet_values[0]
else: self.headers = []
return True # Kein Fehler beim Laden, aber keine Daten
if len(self.sheet_values) >= 1:
self.headers = self.sheet_values[0] # Speichere die erste Zeile als Header
else:
self.headers = [] # Sollte nicht passieren, wenn sheet_values nicht leer war
debug_print(f"Daten neu geladen: {len(self.sheet_values)} Zeilen insgesamt.")
return True
return True # Signalisiert Erfolg
except gspread.exceptions.APIError as e:
debug_print(f"Google API Fehler beim Laden der Sheet Daten: Status {e.response.status_code} - {e.response.text[:200]}")
raise e
raise e # Damit retry greift
except Exception as e:
debug_print(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}")
raise e
raise e # Damit retry greift
def get_data(self):
header_rows = 5
"""Gibt die aktuell im Handler gespeicherten Daten zurück (ohne die ersten 5 Header-Zeilen)."""
header_rows = 5 # Definiert die Anzahl der zu überspringenden Header-Zeilen
if not self.sheet_values or len(self.sheet_values) <= header_rows:
if self.sheet_values:
if self.sheet_values: # Logge nur, wenn Daten da, aber zu wenige
debug_print(f"Warnung in get_data: Nur {len(self.sheet_values)} Zeilen vorhanden, weniger als {header_rows} Header-Zeilen erwartet.")
return []
# Gibt eine Slice der Liste zurück, die die Datenzeilen enthält
return self.sheet_values[header_rows:]
def get_all_data_with_headers(self):
"""Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurück."""
if not self.sheet_values:
debug_print("Warnung in get_all_data_with_headers: Keine Daten im Handler gespeichert.")
return []
return self.sheet_values
def _get_col_letter(self, col_idx_1_based):
string = ""; n = col_idx_1_based
if n < 1: return None
while n > 0: n, remainder = divmod(n - 1, 26); string = chr(65 + remainder) + string
""" Konvertiert 1-basierten Spaltenindex in Buchstaben (A, B, ..., Z, AA, ...). """
string = ""
n = col_idx_1_based
if n < 1: return None # Ungültiger Index
while n > 0:
n, remainder = divmod(n - 1, 26)
string = chr(65 + remainder) + string
return string
# Angepasst: Sucht nur noch nach EXAKT LEER ("")
def get_start_row_index(self, check_column_key, min_sheet_row=7):
if not self.load_data(): return -1
"""
Findet den Index der ersten Zeile (0-basiert für Daten nach Header),
ab einer Mindestzeilennummer im Sheet, in der der Wert in der
Spalte (definiert durch check_column_key) EXAKT LEER ("") ist.
Lädt die Daten vor der Prüfung neu.
Args:
check_column_key (str): Der Schlüssel in COLUMN_MAP für die zu prüfende Spalte.
min_sheet_row (int): Die 1-basierte Zeilennummer im Sheet, ab der gesucht werden soll.
Returns:
int: Der 0-basierte Index in der Datenliste (ohne Header),
oder -1 bei Fehler (z.B. Schlüssel nicht gefunden),
oder der Index nach der letzten Datenzeile, wenn alle gefüllt sind.
"""
if not self.load_data(): return -1 # Fehlerindikator
header_rows = 5
data_rows = self.get_data()
if not data_rows: return 0
@@ -1179,6 +1213,7 @@ class GoogleSheetHandler:
actual_col_letter = self._get_col_letter(check_column_index + 1)
search_start_index_in_data = max(0, min_sheet_row - header_rows - 1)
debug_print(f"get_start_row_index: Suche ab Daten-Index {search_start_index_in_data} nach EXAKT LEEREM Wert (=='') in Spalte '{check_column_key}' ({actual_col_letter})...")
if search_start_index_in_data >= len(data_rows):
@@ -1192,7 +1227,7 @@ class GoogleSheetHandler:
if len(row) > check_column_index:
cell_value = row[check_column_index]
if cell_value != "": is_exactly_empty = False
log_debug = (i == search_start_index_in_data or i % 1000 == 0 or is_exactly_empty or i in range(10110, 10116))
log_debug = (i == search_start_index_in_data or i % 1000 == 0 or is_exactly_empty or i in range(10110, 10116)) # Angepasste Log-Punkte
if log_debug: debug_print(f" -> Prüfe Daten-Index {i} (Sheet {current_sheet_row}): Wert in {actual_col_letter}='{cell_value}' (Typ: {type(cell_value)}). Ist exakt leer ('')? {is_exactly_empty}")
if is_exactly_empty:
debug_print(f"Erste Zeile ab {min_sheet_row} mit EXAKT LEEREM Wert in Spalte {actual_col_letter} gefunden: Zeile {current_sheet_row} (Daten-Index {i})")
@@ -1202,13 +1237,58 @@ class GoogleSheetHandler:
debug_print(f"Alle Zeilen ab Daten-Index {search_start_index_in_data} haben einen nicht-leeren Wert in Spalte {actual_col_letter}. Nächster Daten-Index wäre {last_index}.")
return last_index
# --- ÜBERARBEITETE METHODE mit besserem Error Handling ---
@retry_on_failure
def batch_update_cells(self, update_data):
if not self.sheet: debug_print("FEHLER: Keine Sheet-Verbindung für Batch-Update."); return False
if not update_data: return True
try: self.sheet.batch_update(update_data, value_input_option='USER_ENTERED'); return True
except gspread.exceptions.APIError as e: debug_print(f"Google API Fehler Batch-Update: Status {e.response.status_code} - {e.response.text[:500]}"); raise e
except Exception as e: debug_print(f"Allgemeiner Fehler Batch-Update: {type(e).__name__} - {e}"); raise e
"""
Führt ein Batch-Update im Google Sheet durch. Beinhaltet robustere
Fehlerbehandlung und gibt nur True bei echtem Erfolg zurück.
Args:
update_data (list): Eine Liste von Dictionaries, jedes mit 'range' und 'values'.
z.B. [{'range': 'A1', 'values': [['Wert']]}, ...]
Returns:
bool: True bei Erfolg, False bei Fehler nach Retries.
"""
if not self.sheet:
debug_print("FEHLER: Keine Sheet-Verbindung für Batch-Update.")
return False
if not update_data:
# debug_print("Keine Daten für Batch-Update vorhanden.") # Weniger Lärm
return True # Nichts zu tun ist technisch ein Erfolg
success = False # Standard: Nicht erfolgreich
try:
debug_print(f" -> Versuche sheet.batch_update mit {len(update_data)} Operationen...")
self.sheet.batch_update(update_data, value_input_option='USER_ENTERED')
# Wenn keine Exception aufgetreten ist, war es erfolgreich
success = True
# Logge Erfolg nicht mehr hier, sondern in der aufrufenden Funktion
# debug_print(f" -> sheet.batch_update erfolgreich abgeschlossen.")
except gspread.exceptions.APIError as e:
# Spezifische Fehler loggen
debug_print(f" -> FEHLER (Google API Error) beim Batch-Update: Status {e.response.status_code}")
# Logge die ersten 500 Zeichen der Fehlermeldung von Google
try:
error_details = e.response.json() # Versuche JSON zu parsen
debug_print(f" -> Details: {str(error_details)[:500]}")
except: # Falls die Antwort kein JSON ist
debug_print(f" -> Raw Response Text: {e.response.text[:500]}")
# WICHTIG: Fehler weitergeben, damit retry_on_failure greifen kann
raise e
except Exception as e:
# Andere Fehler loggen
debug_print(f" -> FEHLER (Allgemein) beim Batch-Update: {type(e).__name__} - {e}")
import traceback
debug_print(traceback.format_exc()) # Gib den vollen Traceback aus
# Fehler weitergeben, damit retry_on_failure greifen kann
raise e # Oder return False, wenn Retries nicht helfen sollen? Besser weitergeben.
# Gib den Erfolgsstatus zurück
return success
# --- Ende GoogleSheetHandler Klasse ---