""" Google Sheets API client. Isolated client that can run without FastAPI. """ from typing import Optional from google.oauth2.credentials import Credentials from googleapiclient.discovery import build from googleapiclient.errors import HttpError class GoogleSheetsError(Exception): """Sheets API error.""" pass class GoogleSheetsClient: """ Google Sheets API client. Provides methods to read spreadsheet data. """ def __init__(self, credentials: Credentials): """ Initialize Sheets client. Args: credentials: Google OAuth2 credentials """ self.credentials = credentials self.service = build("sheets", "v4", credentials=credentials) def get_spreadsheet_metadata(self, spreadsheet_id: str) -> dict: """ Get spreadsheet metadata (title, sheets, etc.). Args: spreadsheet_id: The spreadsheet ID Returns: Spreadsheet metadata """ try: result = self.service.spreadsheets().get( spreadsheetId=spreadsheet_id ).execute() return result except HttpError as e: raise GoogleSheetsError(f"Failed to get spreadsheet: {e}") def get_sheet_values( self, spreadsheet_id: str, range_name: str, value_render_option: str = "FORMATTED_VALUE", ) -> list[list]: """ Get values from a sheet range. Args: spreadsheet_id: The spreadsheet ID range_name: A1 notation range (e.g., 'Sheet1!A1:D10') value_render_option: How values should be rendered - FORMATTED_VALUE: Values formatted as strings (default) - UNFORMATTED_VALUE: Values in calculated format - FORMULA: Formulas Returns: List of rows, each row is a list of cell values """ try: result = self.service.spreadsheets().values().get( spreadsheetId=spreadsheet_id, range=range_name, valueRenderOption=value_render_option, ).execute() return result.get("values", []) except HttpError as e: raise GoogleSheetsError(f"Failed to get values: {e}") def get_all_sheets(self, spreadsheet_id: str) -> list[dict]: """ Get list of all sheets in a spreadsheet. Args: spreadsheet_id: The spreadsheet ID Returns: List of sheet metadata (title, id, index, etc.) """ metadata = self.get_spreadsheet_metadata(spreadsheet_id) return [ { "title": sheet["properties"]["title"], "sheet_id": sheet["properties"]["sheetId"], "index": sheet["properties"]["index"], "row_count": sheet["properties"]["gridProperties"].get("rowCount", 0), "column_count": sheet["properties"]["gridProperties"].get("columnCount", 0), } for sheet in metadata.get("sheets", []) ] def batch_get_values( self, spreadsheet_id: str, ranges: list[str], value_render_option: str = "FORMATTED_VALUE", ) -> dict: """ Get multiple ranges in a single request. Args: spreadsheet_id: The spreadsheet ID ranges: List of A1 notation ranges value_render_option: How values should be rendered Returns: Dict with spreadsheetId and valueRanges list """ try: result = self.service.spreadsheets().values().batchGet( spreadsheetId=spreadsheet_id, ranges=ranges, valueRenderOption=value_render_option, ).execute() return result except HttpError as e: raise GoogleSheetsError(f"Failed to batch get values: {e}")