Files
Brancheneinstufung2/google_sheet_handler.py
Floke f5058a8093 v2.0.1: feat: Implement modular versioning
- Einführung von __version__ Attributen in allen Kernmodulen (data_processor, helpers etc.).
- Erstellung einer zentralen Funktion log_module_versions in helpers.py.
- Integration des Version-Loggings beim Start des Hauptskripts für volle Nachvollziehbarkeit.
2025-08-04 09:48:16 +00:00

145 lines
6.5 KiB
Python

# google_sheet_handler.py
__version__ = "v2.0.1"
import os
import logging
import gspread
import pandas as pd
from oauth2client.service_account import ServiceAccountCredentials
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.2
"""
def __init__(self, sheet_url=None):
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: '{self.sheet_url}'")
self.client = None
self.sheet = None
self._all_data_with_headers = []
self._header_rows = 5
@retry_on_failure
def _connect(self):
if self.client: return True
self.logger.info("Stelle neue Verbindung mit Google Sheets her...")
try:
if not os.path.exists(CREDENTIALS_FILE):
raise FileNotFoundError(f"Credential-Datei nicht gefunden: {CREDENTIALS_FILE}")
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 erfolgreich.")
return True
except Exception as e:
self.logger.error(f"FEHLER bei Google Sheets Verbindung: {e}")
self.client = None
return False
@retry_on_failure
def load_data(self):
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 geladen: {len(self._all_data_with_headers)} Zeilen.")
for i, row in enumerate(self._all_data_with_headers):
if "CRM Name" in row:
self._header_rows = i + 1
break
return True
except Exception as e:
self.logger.critical(f"Fehler beim Laden der Sheet Daten: {e}")
return False
def get_all_data_with_headers(self):
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.
NEU: Funktioniert auch, wenn die Header-Zeile doppelte Spaltennamen enthält.
"""
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)
# Lese alle Werte als Liste von Listen, das ist robuster
all_values = worksheet.get_all_values()
if not all_values:
self.logger.warning(f"Tabellenblatt '{sheet_name}' ist leer. Erstelle leeren DataFrame.")
return pd.DataFrame()
# Nimm die erste Zeile als Header und die restlichen als Daten
header = all_values[0]
data = all_values[1:]
df = pd.DataFrame(data, columns=header)
self.logger.info(f"{len(df)} Zeilen aus '{sheet_name}' als DataFrame geladen.")
return df
except gspread.exceptions.WorksheetNotFound:
self.logger.warning(f"Tabellenblatt '{sheet_name}' nicht gefunden. Erstelle leeren DataFrame.")
return pd.DataFrame()
except Exception as e:
self.logger.error(f"Fehler beim Lesen des Sheets '{sheet_name}' als DataFrame: {e}")
return None
def append_rows(self, sheet_name, values):
try:
if not self.client and not self._connect(): return False
worksheet = self.client.open_by_url(self.sheet_url).worksheet(sheet_name)
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):
try:
if not self.client and not self._connect(): return False
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 Exception as e:
self.logger.error(f"Fehler bei clear_and_write_data für '{sheet_name}': {e}")
return False
def batch_update_cells(self, update_data):
if not self.sheet and not self._connect():
self.logger.error("FEHLER: Keine Sheet-Verbindung fuer Batch-Update.")
return False
if not update_data:
return True
sanitized_update_data = []
for item in update_data:
if 'range' in item and 'values' in item and isinstance(item['values'], list):
sanitized_values = [[str(cell) if cell is not None else "" for cell in row] for row in item['values']]
sanitized_update_data.append({'range': item['range'], 'values': sanitized_values})
if not sanitized_update_data: return True
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.logger.info(f"Batch-Update mit {total_cells} Zellen erfolgreich gesendet.")
return True