From 9a615a88fceb9ae209c8b39e876f4bdbb6bf928f Mon Sep 17 00:00:00 2001 From: Floke Date: Thu, 17 Apr 2025 18:28:45 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 132 +++++++++++++++++++++++++++++++++--------- 1 file changed, 106 insertions(+), 26 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 45e9f679..af0d64c9 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -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 ---