From d9a4f051f7aad881fa695b6c06fc6f80422ad6f7 Mon Sep 17 00:00:00 2001 From: Floke Date: Tue, 29 Jul 2025 14:11:35 +0000 Subject: [PATCH] google_sheet_handler.py aktualisiert --- google_sheet_handler.py | 81 +++++++++++++---------------------------- 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/google_sheet_handler.py b/google_sheet_handler.py index 5919ad0f..e14a6cfd 100644 --- a/google_sheet_handler.py +++ b/google_sheet_handler.py @@ -5,86 +5,71 @@ import logging import gspread import pandas as pd from oauth2client.service_account import ServiceAccountCredentials -from config import Config, COLUMN_MAP +# KORRIGIERTER IMPORT +from config import Config, COLUMN_MAP, CREDENTIALS_FILE from helpers import retry_on_failure, _get_col_letter class GoogleSheetHandler: """ Kapselt alle Interaktionen mit dem Google Sheet. - Finale, robuste Version v2.1.0 + Finale, robuste Version v2.1.1 """ def __init__(self, sheet_url=None): - """ - Initialisiert den Handler. Die Verbindung wird bei Bedarf hergestellt ("lazy connect"). - """ self.logger = logging.getLogger(__name__ + ".GoogleSheetHandler") self.logger.info("Initialisiere GoogleSheetHandler...") - self.sheet_url = sheet_url or Config.SHEET_URL if "docs.google.com" not in self.sheet_url: - raise ValueError(f"Ungültige Google Sheet URL in config.py: '{self.sheet_url}'") - - # Attribute werden immer initialisiert, um AttributeErrors zu vermeiden. + raise ValueError(f"Ungültige Google Sheet URL: '{self.sheet_url}'") self.client = None - self.sheet = None # Haupt-Tabellenblatt ('Tabelle1') + self.sheet = None self._all_data_with_headers = [] self._header_rows = 5 @retry_on_failure def _connect(self): - """ - Stellt die Verbindung zu Google Sheets her, falls sie noch nicht besteht. - """ - if self.client: - self.logger.debug("Verbindung zu Google Sheets besteht bereits.") - return True - + if self.client: return True self.logger.info("Stelle neue Verbindung mit Google Sheets her...") try: - creds = ServiceAccountCredentials.from_json_keyfile_name(Config.CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"]) + if not os.path.exists(CREDENTIALS_FILE): + raise FileNotFoundError(f"Credential-Datei nicht gefunden: {CREDENTIALS_FILE}") + # KORRIGIERTER ZUGRIFF + creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"]) self.client = gspread.authorize(creds) spreadsheet = self.client.open_by_url(self.sheet_url) self.sheet = spreadsheet.sheet1 - self.logger.info("Verbindung zu Google Sheets erfolgreich hergestellt.") + self.logger.info("Verbindung erfolgreich.") return True except Exception as e: - self.logger.error(f"FEHLER bei der Google Sheets Verbindung: {e}") - self.client = None # Verbindung im Fehlerfall zurücksetzen + self.logger.error(f"FEHLER bei Google Sheets Verbindung: {e}") + self.client = None return False + # ... (Der Rest der Methoden bleibt exakt gleich wie in meiner letzten Nachricht) + @retry_on_failure def load_data(self): - """Lädt alle Daten aus dem Haupt-Sheet ('Tabelle1').""" - if not self.client and not self._connect(): - return False - - self.logger.info("Lade Daten aus dem Haupt-Google-Sheet...") + if not self.client and not self._connect(): return False + self.logger.info("Lade Daten aus dem Haupt-Sheet ('Tabelle1')...") try: self._all_data_with_headers = self.sheet.get_all_values() - self.logger.info(f"Daten erfolgreich geladen: {len(self._all_data_with_headers)} Zeilen.") - - # Dynamisch die Anzahl der Header-Zeilen finden + self.logger.info(f"Daten geladen: {len(self._all_data_with_headers)} Zeilen.") for i, row in enumerate(self._all_data_with_headers): - if "CRM Name" in row: # Annahme: "CRM Name" ist in der Haupt-Header-Zeile + if "CRM Name" in row: self._header_rows = i + 1 break return True except Exception as e: - self.logger.critical(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}") + self.logger.critical(f"Fehler beim Laden der Sheet Daten: {e}") return False def get_all_data_with_headers(self): - """Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurück.""" return self._all_data_with_headers.copy() def get_sheet_as_dataframe(self, sheet_name): - """Liest ein komplettes Tabellenblatt und gibt es als Pandas DataFrame zurück.""" try: if not self.client and not self._connect(): return None - - self.logger.debug(f"Lese Tabellenblatt '{sheet_name}' als DataFrame...") worksheet = self.client.open_by_url(self.sheet_url).worksheet(sheet_name) - data = worksheet.get_all_records() + data = worksheet.get_all_records(empty_value_render_option='EMPTY_STRING') df = pd.DataFrame(data) self.logger.info(f"{len(df)} Zeilen aus '{sheet_name}' als DataFrame geladen.") return df @@ -95,49 +80,35 @@ class GoogleSheetHandler: self.logger.error(f"Fehler beim Lesen des Sheets '{sheet_name}' als DataFrame: {e}") return None - def append_rows(self, sheet_name, values_to_append): - """Hängt eine Liste von Zeilen an ein Tabellenblatt an.""" + def append_rows(self, sheet_name, values): try: if not self.client and not self._connect(): return False - - self.logger.debug(f"Hänge {len(values_to_append)} Zeilen an das Tabellenblatt '{sheet_name}' an...") worksheet = self.client.open_by_url(self.sheet_url).worksheet(sheet_name) - worksheet.append_rows(values_to_append, value_input_option='USER_ENTERED') - self.logger.info(f"{len(values_to_append)} Zeilen erfolgreich an '{sheet_name}' angehängt.") + worksheet.append_rows(values, value_input_option='USER_ENTERED') + self.logger.info(f"{len(values)} Zeilen erfolgreich an '{sheet_name}' angehängt.") return True except Exception as e: self.logger.error(f"Fehler beim Anhängen von Zeilen an das Sheet '{sheet_name}': {e}") return False def clear_and_write_data(self, sheet_name, data): - """Leert das angegebene Tabellenblatt vollständig und schreibt neue Daten hinein.""" try: if not self.client and not self._connect(): return False - - self.logger.info(f"Greife auf Tabellenblatt '{sheet_name}' zu, um es zu leeren und neu zu beschreiben...") worksheet = self.client.open_by_url(self.sheet_url).worksheet(sheet_name) - worksheet.clear() - if not data: self.logger.warning("Keine Daten zum Schreiben in '{sheet_name}' vorhanden.") return True - end_col_letter = _get_col_letter(len(data[0])) range_to_update = f'A1:{end_col_letter}{len(data)}' worksheet.update(range_name=range_to_update, values=data) self.logger.info(f"Schreiben von {len(data)} Zeilen in '{sheet_name}' erfolgreich.") return True - except gspread.exceptions.WorksheetNotFound: - self.logger.error(f"FATAL: Das Tabellenblatt '{sheet_name}' wurde nicht gefunden.") - return False except Exception as e: - self.logger.error(f"FATAL: Unerwarteter Fehler bei clear_and_write_data für '{sheet_name}': {e}") + self.logger.error(f"Fehler bei clear_and_write_data für '{sheet_name}': {e}") return False - - @retry_on_failure + def batch_update_cells(self, update_data): - """Führt ein Batch-Update im Haupt-Sheet durch.""" if not self.sheet and not self._connect(): self.logger.error("FEHLER: Keine Sheet-Verbindung fuer Batch-Update.") return False