google_sheet_handler.py aktualisiert
This commit is contained in:
@@ -1,358 +1,159 @@
|
|||||||
# --- START OF FILE google_sheet_handler.py ---
|
# google_sheet_handler.py
|
||||||
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
google_sheet_handler.py
|
|
||||||
|
|
||||||
Klasse zur Kapselung der Interaktionen mit dem Google Sheet.
|
|
||||||
Stellt Verbindung her, lädt Daten und führt Batch-Updates durch.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import gspread
|
import gspread
|
||||||
|
import pandas as pd
|
||||||
from oauth2client.service_account import ServiceAccountCredentials
|
from oauth2client.service_account import ServiceAccountCredentials
|
||||||
|
from config import Config, COLUMN_MAP
|
||||||
# Import der abhängigen Module
|
from helpers import retry_on_failure, _get_col_letter
|
||||||
from config import Config, CREDENTIALS_FILE, COLUMN_MAP
|
|
||||||
from helpers import retry_on_failure
|
|
||||||
|
|
||||||
class GoogleSheetHandler:
|
class GoogleSheetHandler:
|
||||||
"""
|
"""
|
||||||
Kapselt die Interaktionen mit dem Google Sheet, inklusive Verbindung,
|
Kapselt alle Interaktionen mit dem Google Sheet.
|
||||||
Daten laden und Batch-Updates. Nutzt den retry_on_failure Decorator.
|
Finale, robuste Version v2.1.0
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, sheet_url=None):
|
||||||
"""
|
"""
|
||||||
Initialisiert den Handler, stellt die Verbindung her und laedt die Daten.
|
Initialisiert den Handler. Die Verbindung wird bei Bedarf hergestellt ("lazy connect").
|
||||||
"""
|
"""
|
||||||
self.logger = logging.getLogger(__name__ + ".GoogleSheetHandler")
|
self.logger = logging.getLogger(__name__ + ".GoogleSheetHandler")
|
||||||
# WICHTIG: Attribute hier initialisieren
|
|
||||||
self.client = None
|
|
||||||
self.sheet = None
|
|
||||||
self._all_data = []
|
|
||||||
self._header_rows = 5
|
|
||||||
|
|
||||||
self.logger.info("Initialisiere GoogleSheetHandler...")
|
self.logger.info("Initialisiere GoogleSheetHandler...")
|
||||||
try:
|
|
||||||
self._connect()
|
|
||||||
if self.sheet:
|
|
||||||
self.load_data()
|
|
||||||
else:
|
|
||||||
raise ConnectionError("Google Sheet Handler Init failed: Verbindung konnte nicht hergestellt werden.")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.critical(f"FATAL: Fehler bei Initialisierung von GoogleSheetHandler: {e}")
|
|
||||||
raise ConnectionError(f"Google Sheet Handler Init failed: {e}")
|
|
||||||
|
|
||||||
try:
|
self.sheet_url = sheet_url or Config.SHEET_URL
|
||||||
self._connect()
|
if "docs.google.com" not in self.sheet_url:
|
||||||
if self.sheet:
|
raise ValueError(f"Ungültige Google Sheet URL in config.py: '{self.sheet_url}'")
|
||||||
self.load_data()
|
|
||||||
else:
|
# Attribute werden immer initialisiert, um AttributeErrors zu vermeiden.
|
||||||
self.logger.critical(
|
self.client = None
|
||||||
"GoogleSheetHandler Init FEHLER: Verbindung konnte nicht hergestellt werden (sheet ist None)."
|
self.sheet = None # Haupt-Tabellenblatt ('Tabelle1')
|
||||||
)
|
self._all_data_with_headers = []
|
||||||
raise ConnectionError("Google Sheet Handler Init failed: Verbindung konnte nicht hergestellt werden.")
|
self._header_rows = 5
|
||||||
except Exception as e:
|
|
||||||
self.logger.critical(f"FATAL: Fehler bei Initialisierung von GoogleSheetHandler: {type(e).__name__} - {e}")
|
|
||||||
self.logger.debug(traceback.format_exc())
|
|
||||||
raise ConnectionError(f"Google Sheet Handler Init failed: {e}")
|
|
||||||
|
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
"""Stellt Verbindung zum Google Sheet her."""
|
"""
|
||||||
self.sheet = None
|
Stellt die Verbindung zu Google Sheets her, falls sie noch nicht besteht.
|
||||||
self.client = None # Zurücksetzen bei jedem Versuch
|
"""
|
||||||
self.logger.info("Versuche Verbindung mit Google Sheets herstellen...")
|
if self.client:
|
||||||
try:
|
self.logger.debug("Verbindung zu Google Sheets besteht bereits.")
|
||||||
if not os.path.exists(CREDENTIALS_FILE):
|
return True
|
||||||
raise FileNotFoundError(f"Credential-Datei nicht gefunden: {CREDENTIALS_FILE}")
|
|
||||||
|
|
||||||
scope = ["https://www.googleapis.com/auth/spreadsheets"]
|
self.logger.info("Stelle neue Verbindung mit Google Sheets her...")
|
||||||
creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
|
try:
|
||||||
# WICHTIG: Den Client in self.client speichern
|
creds = ServiceAccountCredentials.from_json_keyfile_name(Config.CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"])
|
||||||
self.client = gspread.authorize(creds)
|
self.client = gspread.authorize(creds)
|
||||||
sh = self.client.open_by_url(Config.SHEET_URL)
|
spreadsheet = self.client.open_by_url(self.sheet_url)
|
||||||
self.sheet = sh.sheet1 # Das Hauptblatt für Leseoperationen
|
self.sheet = spreadsheet.sheet1
|
||||||
self.logger.info("Verbindung zu Google Sheets erfolgreich.")
|
self.logger.info("Verbindung zu Google Sheets erfolgreich hergestellt.")
|
||||||
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"FEHLER bei der Google Sheets Verbindung: {e}")
|
self.logger.error(f"FEHLER bei der Google Sheets Verbindung: {e}")
|
||||||
self.logger.debug(traceback.format_exc())
|
self.client = None # Verbindung im Fehlerfall zurücksetzen
|
||||||
raise e
|
return False
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"FEHLER bei der Google Sheets Verbindung: {type(e).__name__} - {e}")
|
|
||||||
self.logger.debug(traceback.format_exc())
|
|
||||||
raise e
|
|
||||||
|
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def load_data(self):
|
def load_data(self):
|
||||||
"""
|
"""Lädt alle Daten aus dem Haupt-Sheet ('Tabelle1')."""
|
||||||
Laedt alle Daten aus dem Sheet und aktualisiert die internen Datenstrukturen.
|
if not self.client and not self._connect():
|
||||||
Führt eine Validierung der Spaltenanzahl durch.
|
|
||||||
"""
|
|
||||||
if not self.sheet:
|
|
||||||
self.logger.error("Fehler: Keine Sheet-Verbindung zum Laden der Daten.")
|
|
||||||
self._all_data = []
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.logger.info("Lade Daten aus Google Sheet...")
|
self.logger.info("Lade Daten aus dem Haupt-Google-Sheet...")
|
||||||
try:
|
try:
|
||||||
self._all_data = self.sheet.get_all_values()
|
self._all_data_with_headers = self.sheet.get_all_values()
|
||||||
|
self.logger.info(f"Daten erfolgreich geladen: {len(self._all_data_with_headers)} Zeilen.")
|
||||||
|
|
||||||
if not self._all_data:
|
# Dynamisch die Anzahl der Header-Zeilen finden
|
||||||
self.logger.warning("Google Sheet scheint leer zu sein.")
|
for i, row in enumerate(self._all_data_with_headers):
|
||||||
self.headers = []
|
if "CRM Name" in row: # Annahme: "CRM Name" ist in der Haupt-Header-Zeile
|
||||||
return True
|
|
||||||
|
|
||||||
num_rows = len(self._all_data)
|
|
||||||
num_cols = len(self._all_data[0]) if num_rows > 0 else 0
|
|
||||||
self.logger.info(f"Daten neu geladen: {num_rows} Zeilen, {num_cols} Spalten.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
all_indices = [v['index'] for v in COLUMN_MAP.values()]
|
|
||||||
if not all_indices: raise ValueError("COLUMN_MAP leer")
|
|
||||||
|
|
||||||
max_col_idx_in_map = max(all_indices)
|
|
||||||
expected_min_cols = max_col_idx_in_map + 1
|
|
||||||
if num_cols < expected_min_cols:
|
|
||||||
self.logger.warning(
|
|
||||||
f"Sheet hat nur {num_cols} Spalten, aber COLUMN_MAP erwartet mind. {expected_min_cols}.")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Fehler bei der Pruefung der Spaltenanzahl: {e}")
|
|
||||||
|
|
||||||
for i, row in enumerate(self._all_data):
|
|
||||||
if "CRM Name" in row:
|
|
||||||
self._header_rows = i + 1
|
self._header_rows = i + 1
|
||||||
self.headers = row
|
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
self.headers = self._all_data[0] if self._all_data else []
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.critical(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}", exc_info=True)
|
self.logger.critical(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
"""
|
|
||||||
Gibt die aktuell im Handler gespeicherten Datenzeilen zurueck
|
|
||||||
(ohne die ersten N Header-Zeilen).
|
|
||||||
"""
|
|
||||||
if not self._all_data or len(self._all_data) <= self._header_rows:
|
|
||||||
self.logger.debug(
|
|
||||||
f"get_data: Keine Datenzeilen verfuegbar "
|
|
||||||
f"(geladen: {len(self._all_data) if self._all_data else 0} Zeilen, "
|
|
||||||
f"{self._header_rows} Header)."
|
|
||||||
)
|
|
||||||
return []
|
|
||||||
return self._all_data[self._header_rows:].copy()
|
|
||||||
|
|
||||||
def get_all_data_with_headers(self):
|
def get_all_data_with_headers(self):
|
||||||
"""Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurueck."""
|
"""Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurück."""
|
||||||
if not self._all_data:
|
return self._all_data_with_headers.copy()
|
||||||
self.logger.debug("get_all_data_with_headers: Keine Daten im Handler gespeichert.")
|
|
||||||
return []
|
|
||||||
return self._all_data.copy()
|
|
||||||
|
|
||||||
def _get_col_letter(self, col_idx_1_based):
|
|
||||||
"""
|
|
||||||
Konvertiert einen 1-basierten Spaltenindex in den entsprechenden
|
|
||||||
Google Sheets Spaltenbuchstaben (A, B, ..., Z, AA, ...).
|
|
||||||
"""
|
|
||||||
if not isinstance(col_idx_1_based, int) or col_idx_1_based < 1:
|
|
||||||
self.logger.error(f"Ungueltiger Spaltenindex ({col_idx_1_based}) fuer _get_col_letter erhalten.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
string = ""
|
|
||||||
n = col_idx_1_based
|
|
||||||
while n > 0:
|
|
||||||
n, remainder = divmod(n - 1, 26)
|
|
||||||
string = chr(65 + remainder) + string
|
|
||||||
return string
|
|
||||||
|
|
||||||
def get_start_row_index(self, check_column_key, min_sheet_row=7):
|
|
||||||
"""
|
|
||||||
Findet den 0-basierten Index in der DATENliste (ohne Header),
|
|
||||||
ab einer Mindestzeilennummer im Sheet, in der der Wert in der
|
|
||||||
Spalte (definiert durch check_column_key) EXAKT LEER ("") ist.
|
|
||||||
"""
|
|
||||||
# Daten müssen nicht extra geladen werden, da dies im aufrufenden Prozess geschieht.
|
|
||||||
data_rows = self.get_data()
|
|
||||||
if not data_rows:
|
|
||||||
self.logger.info("Keine Datenzeilen im Sheet gefunden. Startindex fuer leere Zelle ist 0.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# KORREKTUR: Greife auf den 'index'-Wert zu
|
|
||||||
col_info = COLUMN_MAP.get(check_column_key)
|
|
||||||
if col_info is None or 'index' not in col_info:
|
|
||||||
self.logger.critical(f"FEHLER: Schluessel '{check_column_key}' oder sein 'index' nicht in COLUMN_MAP gefunden!")
|
|
||||||
return -1
|
|
||||||
|
|
||||||
check_column_index = col_info['index']
|
|
||||||
|
|
||||||
actual_col_letter = self._get_col_letter(check_column_index + 1)
|
|
||||||
if actual_col_letter is None:
|
|
||||||
actual_col_letter = f"Index_{check_column_index + 1}"
|
|
||||||
|
|
||||||
search_start_index_in_data = max(0, (min_sheet_row - 1) - self._header_rows)
|
|
||||||
self.logger.info(
|
|
||||||
f"get_start_row_index: Suche ab Daten-Index {search_start_index_in_data} "
|
|
||||||
f"(Sheet-Zeile {search_start_index_in_data + self._header_rows + 1}) "
|
|
||||||
f"nach EXAKT LEEREM Wert (=='') in Spalte '{check_column_key}' ({actual_col_letter})..."
|
|
||||||
)
|
|
||||||
|
|
||||||
if search_start_index_in_data >= len(data_rows):
|
|
||||||
self.logger.warning(
|
|
||||||
f"Start-Suchindex in Daten ({search_start_index_in_data}) liegt hinter der letzten Datenzeile ({len(data_rows)})."
|
|
||||||
)
|
|
||||||
return len(data_rows)
|
|
||||||
|
|
||||||
for i in range(search_start_index_in_data, len(data_rows)):
|
|
||||||
row = data_rows[i]
|
|
||||||
current_sheet_row = i + self._header_rows + 1
|
|
||||||
is_exactly_empty = True
|
|
||||||
if len(row) > check_column_index:
|
|
||||||
cell_value = row[check_column_index]
|
|
||||||
if cell_value != "":
|
|
||||||
is_exactly_empty = False
|
|
||||||
|
|
||||||
if is_exactly_empty:
|
|
||||||
self.logger.info(
|
|
||||||
f"Erste Zeile ab Sheet-Zeile {min_sheet_row} mit EXAKT LEEREM Wert in Spalte "
|
|
||||||
f"{actual_col_letter} gefunden: Zeile {current_sheet_row} (Daten-Index {i})"
|
|
||||||
)
|
|
||||||
return i
|
|
||||||
|
|
||||||
last_data_index = len(data_rows)
|
|
||||||
self.logger.info(
|
|
||||||
f"Alle Zeilen ab Daten-Index {search_start_index_in_data} haben einen "
|
|
||||||
f"nicht-leeren Wert in Spalte {actual_col_letter}. Naechster Daten-Index waere {last_data_index}."
|
|
||||||
)
|
|
||||||
return last_data_index
|
|
||||||
|
|
||||||
def clear_and_write_data(self, sheet_name, data):
|
|
||||||
"""
|
|
||||||
Leert das angegebene Tabellenblatt vollständig und schreibt neue Daten hinein.
|
|
||||||
Die Daten sollten eine Liste von Listen sein (inklusive Header).
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Der Client wurde bereits in __init__ erstellt. Wir prüfen nur, ob er existiert.
|
|
||||||
if not self.client:
|
|
||||||
self.logger.error("Kein Google-Client vorhanden. Breche Schreibvorgang ab.")
|
|
||||||
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(Config.SHEET_URL).worksheet(sheet_name)
|
|
||||||
|
|
||||||
self.logger.debug("Leere das gesamte Tabellenblatt...")
|
|
||||||
worksheet.clear()
|
|
||||||
|
|
||||||
num_rows = len(data)
|
|
||||||
num_cols = len(data[0]) if data else 0
|
|
||||||
|
|
||||||
if num_rows == 0:
|
|
||||||
self.logger.warning("Keine Daten zum Schreiben vorhanden.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.logger.info(f"Schreibe {num_rows - 1} neue Datenzeilen (insgesamt {num_rows} Zeilen mit Header) in '{sheet_name}'...")
|
|
||||||
|
|
||||||
end_col_letter = self._get_col_letter(num_cols + 1) # Korrigiert auf +1 für 1-basiert
|
|
||||||
range_to_update = f'A1:{end_col_letter}{num_rows}'
|
|
||||||
|
|
||||||
worksheet.update(range_name=range_to_update, values=data)
|
|
||||||
self.logger.info(f"Schreiben in Tabellenblatt '{sheet_name}' erfolgreich abgeschlossen.")
|
|
||||||
return True
|
|
||||||
except gspread.exceptions.WorksheetNotFound:
|
|
||||||
self.logger.error(f"FATAL: Das Tabellenblatt '{sheet_name}' wurde nicht gefunden. Bitte prüfen Sie den Namen.")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"FATAL: Ein unerwarteter Fehler ist beim Schreiben in '{sheet_name}' aufgetreten: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_sheet_as_dataframe(self, sheet_name):
|
def get_sheet_as_dataframe(self, sheet_name):
|
||||||
"""Liest ein komplettes Tabellenblatt und gibt es als Pandas DataFrame zurück."""
|
"""Liest ein komplettes Tabellenblatt und gibt es als Pandas DataFrame zurück."""
|
||||||
try:
|
try:
|
||||||
if not self.client:
|
if not self.client and not self._connect(): return None
|
||||||
if not self._connect(): return None
|
|
||||||
|
|
||||||
logging.debug(f"Lese Tabellenblatt '{sheet_name}' als DataFrame...")
|
self.logger.debug(f"Lese Tabellenblatt '{sheet_name}' als DataFrame...")
|
||||||
worksheet = self.client.open_by_url(self.sheet_url).worksheet(sheet_name)
|
worksheet = self.client.open_by_url(self.sheet_url).worksheet(sheet_name)
|
||||||
data = worksheet.get_all_records()
|
data = worksheet.get_all_records()
|
||||||
df = pd.DataFrame(data)
|
df = pd.DataFrame(data)
|
||||||
logging.info(f"{len(df)} Zeilen aus '{sheet_name}' als DataFrame geladen.")
|
self.logger.info(f"{len(df)} Zeilen aus '{sheet_name}' als DataFrame geladen.")
|
||||||
return df
|
return df
|
||||||
except gspread.exceptions.WorksheetNotFound:
|
except gspread.exceptions.WorksheetNotFound:
|
||||||
logging.warning(f"Tabellenblatt '{sheet_name}' nicht gefunden. Gehe davon aus, dass es leer ist.")
|
self.logger.warning(f"Tabellenblatt '{sheet_name}' nicht gefunden. Erstelle leeren DataFrame.")
|
||||||
return None
|
return pd.DataFrame()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Fehler beim Lesen des Sheets als DataFrame: {e}")
|
self.logger.error(f"Fehler beim Lesen des Sheets '{sheet_name}' als DataFrame: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def append_rows(self, sheet_name, values):
|
def append_rows(self, sheet_name, values_to_append):
|
||||||
"""Hängt eine Liste von Zeilen an ein Tabellenblatt an."""
|
"""Hängt eine Liste von Zeilen an ein Tabellenblatt an."""
|
||||||
try:
|
try:
|
||||||
if not self.client:
|
if not self.client and not self._connect(): return False
|
||||||
if not self._connect(): return False
|
|
||||||
|
|
||||||
logging.debug(f"Hänge {len(values)} Zeilen an das Tabellenblatt '{sheet_name}' an...")
|
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 = self.client.open_by_url(self.sheet_url).worksheet(sheet_name)
|
||||||
worksheet.append_rows(values, value_input_option='USER_ENTERED')
|
worksheet.append_rows(values_to_append, value_input_option='USER_ENTERED')
|
||||||
logging.info(f"{len(values)} Zeilen erfolgreich an '{sheet_name}' angehängt.")
|
self.logger.info(f"{len(values_to_append)} Zeilen erfolgreich an '{sheet_name}' angehängt.")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Fehler beim Anhängen von Zeilen an das Sheet: {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}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def batch_update_cells(self, update_data):
|
def batch_update_cells(self, update_data):
|
||||||
"""
|
"""Führt ein Batch-Update im Haupt-Sheet durch."""
|
||||||
Fuehrt ein Batch-Update im Google Sheet durch.
|
if not self.sheet and not self._connect():
|
||||||
NEU: Konvertiert alle zu schreibenden Werte explizit in Strings, um Fehler zu vermeiden.
|
|
||||||
"""
|
|
||||||
if not self.sheet:
|
|
||||||
self.logger.error("FEHLER: Keine Sheet-Verbindung fuer Batch-Update.")
|
self.logger.error("FEHLER: Keine Sheet-Verbindung fuer Batch-Update.")
|
||||||
return False
|
return False
|
||||||
if not update_data:
|
if not update_data:
|
||||||
self.logger.debug("batch_update_cells: Keine Daten zum Aktualisieren erhalten.")
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# --- NEUE, ROBUSTE DATENAUFBEREITUNG ---
|
|
||||||
sanitized_update_data = []
|
sanitized_update_data = []
|
||||||
for item in update_data:
|
for item in update_data:
|
||||||
if 'range' in item and 'values' in item and isinstance(item['values'], list):
|
if 'range' in item and 'values' in item and isinstance(item['values'], list):
|
||||||
# Konvertiere jeden einzelnen Zellwert im values-Array sicher in einen String
|
sanitized_values = [[str(cell) if cell is not None else "" for cell in row] for row in item['values']]
|
||||||
sanitized_values = [
|
sanitized_update_data.append({'range': item['range'], 'values': sanitized_values})
|
||||||
[str(cell_value) if cell_value is not None else "" for cell_value in row]
|
|
||||||
for row in item['values']
|
|
||||||
]
|
|
||||||
sanitized_update_data.append({
|
|
||||||
'range': item['range'],
|
|
||||||
'values': sanitized_values
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
self.logger.warning(f"Überspringe ungültiges Update-Objekt: {item}")
|
|
||||||
|
|
||||||
if not sanitized_update_data:
|
if not sanitized_update_data: return True
|
||||||
self.logger.warning("Keine gültigen Daten nach der Bereinigung für das Batch-Update übrig.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
try:
|
|
||||||
total_cells_to_update = sum(len(row) for item in sanitized_update_data for row in item.get('values', []))
|
|
||||||
self.logger.debug(f" -> Versuche sheet.batch_update mit {len(sanitized_update_data)} Anfragen ({total_cells_to_update} Zellen)...")
|
|
||||||
|
|
||||||
# Logge das erste Datenobjekt zur Überprüfung
|
|
||||||
if self.logger.level == logging.DEBUG and sanitized_update_data:
|
|
||||||
self.logger.debug(f" -> Beispiel-Update-Daten: {str(sanitized_update_data[0])}")
|
|
||||||
|
|
||||||
|
total_cells = sum(len(row) for item in sanitized_update_data for row in item.get('values', []))
|
||||||
|
self.logger.debug(f"Sende Batch-Update mit {len(sanitized_update_data)} Anfragen ({total_cells} Zellen)...")
|
||||||
self.sheet.batch_update(sanitized_update_data, value_input_option='USER_ENTERED')
|
self.sheet.batch_update(sanitized_update_data, value_input_option='USER_ENTERED')
|
||||||
self.logger.info(f"Batch-Update mit {total_cells_to_update} Zellen erfolgreich gesendet.")
|
self.logger.info(f"Batch-Update mit {total_cells} Zellen erfolgreich gesendet.")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Endgueltiger Fehler beim Batch-Update nach Retries: {e}", exc_info=True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# --- END OF FILE google_sheet_handler.py ---
|
|
||||||
Reference in New Issue
Block a user