"""
Google Sheets Integration Module
Full CRUD operations with formatting support
"""
from googleapiclient.discovery import build
from src.google_auth import get_credentials
def get_sheets_service():
creds = get_credentials()
return build("sheets", "v4", credentials=creds)
def get_drive_service():
creds = get_credentials()
return build("drive", "v3", credentials=creds)
def create_spreadsheet(title, sheet_names=None):
"""Create a new spreadsheet with optional multiple sheets/tabs"""
try:
service = get_sheets_service()
spreadsheet_body = {"properties": {"title": title}}
if sheet_names:
spreadsheet_body["sheets"] = [
{"properties": {"title": name}} for name in sheet_names
]
spreadsheet = (
service.spreadsheets()
.create(body=spreadsheet_body, fields="spreadsheetId,spreadsheetUrl,sheets")
.execute()
)
return {
"success": True,
"spreadsheetId": spreadsheet.get("spreadsheetId"),
"spreadsheetUrl": spreadsheet.get("spreadsheetUrl"),
"sheets": [
{
"sheetId": sheet["properties"]["sheetId"],
"title": sheet["properties"]["title"],
}
for sheet in spreadsheet.get("sheets", [])
],
}
except Exception as error:
return {"success": False, "error": str(error)}
def read_sheet_data(spreadsheet_id, range_name):
"""Read data from a Google Sheet range"""
try:
service = get_sheets_service()
result = (
service.spreadsheets()
.values()
.get(spreadsheetId=spreadsheet_id, range=range_name)
.execute()
)
values = result.get("values", [])
return {
"success": True,
"data": values,
"range": result.get("range", range_name),
}
except Exception as error:
return {"success": False, "error": str(error)}
def write_sheet_data(spreadsheet_id, range_name, values):
"""Write data to a Google Sheet range"""
try:
service = get_sheets_service()
result = (
service.spreadsheets()
.values()
.update(
spreadsheetId=spreadsheet_id,
range=range_name,
valueInputOption="USER_ENTERED",
body={"values": values},
)
.execute()
)
return {
"success": True,
"updatedCells": result.get("updatedCells", 0),
"updatedRange": result.get("updatedRange", range_name),
}
except Exception as error:
return {"success": False, "error": str(error)}
def append_sheet_data(spreadsheet_id, range_name, values):
"""Append data to a Google Sheet"""
try:
service = get_sheets_service()
result = (
service.spreadsheets()
.values()
.append(
spreadsheetId=spreadsheet_id,
range=range_name,
valueInputOption="USER_ENTERED",
insertDataOption="INSERT_ROWS",
body={"values": values},
)
.execute()
)
updates = result.get("updates", {})
return {
"success": True,
"updatedCells": updates.get("updatedCells", 0),
"updatedRange": updates.get("updatedRange", range_name),
}
except Exception as error:
return {"success": False, "error": str(error)}
def get_sheet_info(spreadsheet_id):
"""Get information about a spreadsheet"""
try:
service = get_sheets_service()
result = service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute()
properties = result.get("properties", {})
sheets_info = []
for sheet_info in result.get("sheets", []):
sheet_props = sheet_info.get("properties", {})
grid_props = sheet_props.get("gridProperties", {})
sheets_info.append(
{
"title": sheet_props.get("title", ""),
"sheetId": sheet_props.get("sheetId", ""),
"rowCount": grid_props.get("rowCount", 0),
"columnCount": grid_props.get("columnCount", 0),
}
)
return {
"success": True,
"title": properties.get("title", ""),
"spreadsheetId": spreadsheet_id,
"sheets": sheets_info,
}
except Exception as error:
return {"success": False, "error": str(error)}
def create_sheet_tab(spreadsheet_id, tab_name):
"""Create a new tab/worksheet in an existing spreadsheet"""
try:
service = get_sheets_service()
request_body = {"requests": [{"addSheet": {"properties": {"title": tab_name}}}]}
response = (
service.spreadsheets()
.batchUpdate(spreadsheetId=spreadsheet_id, body=request_body)
.execute()
)
reply = response.get("replies", [{}])[0]
sheet_props = reply.get("addSheet", {}).get("properties", {})
return {
"success": True,
"sheetId": sheet_props.get("sheetId"),
"title": sheet_props.get("title"),
}
except Exception as error:
return {"success": False, "error": str(error)}
def list_spreadsheets(max_results=50):
"""List all spreadsheets in Google Drive"""
try:
drive_service = get_drive_service()
results = (
drive_service.files()
.list(
q="mimeType='application/vnd.google-apps.spreadsheet'",
spaces="drive",
fields="files(id, name, createdTime, modifiedTime, webViewLink)",
orderBy="modifiedTime desc",
pageSize=max_results,
)
.execute()
)
files = results.get("files", [])
return {
"success": True,
"spreadsheets": [
{
"id": f["id"],
"name": f["name"],
"createdTime": f.get("createdTime", ""),
"modifiedTime": f.get("modifiedTime", ""),
"webViewLink": f.get("webViewLink", ""),
}
for f in files
],
"total": len(files),
}
except Exception as error:
return {"success": False, "error": str(error)}
def format_cells(spreadsheet_id, range_name, format_options):
"""
Format cells in a spreadsheet
format_options can include:
- bold: bool
- italic: bool
- fontSize: int
- foregroundColor: {red, green, blue} (0-1 values)
- backgroundColor: {red, green, blue} (0-1 values)
- horizontalAlignment: 'LEFT', 'CENTER', 'RIGHT'
- verticalAlignment: 'TOP', 'MIDDLE', 'BOTTOM'
- wrapStrategy: 'OVERFLOW_CELL', 'CLIP', 'WRAP'
- borders: {top, bottom, left, right} with {style, color}
"""
try:
service = get_sheets_service()
# First get sheet info to resolve range to grid coordinates
sheet_info = (
service.spreadsheets()
.get(
spreadsheetId=spreadsheet_id, ranges=[range_name], includeGridData=False
)
.execute()
)
# Parse range to get sheet ID and grid range
sheets = sheet_info.get("sheets", [])
if not sheets:
return {"success": False, "error": "Sheet not found"}
sheet_id = sheets[0]["properties"]["sheetId"]
# Parse range string (e.g., "Sheet1!A1:B5" or "A1:B5")
grid_range = _parse_range(range_name, sheet_id)
# Build format request
cell_format = {}
# Text format
text_format = {}
if "bold" in format_options:
text_format["bold"] = format_options["bold"]
if "italic" in format_options:
text_format["italic"] = format_options["italic"]
if "fontSize" in format_options:
text_format["fontSize"] = format_options["fontSize"]
if "foregroundColor" in format_options:
text_format["foregroundColor"] = format_options["foregroundColor"]
if text_format:
cell_format["textFormat"] = text_format
# Background color
if "backgroundColor" in format_options:
cell_format["backgroundColor"] = format_options["backgroundColor"]
# Alignment
if "horizontalAlignment" in format_options:
cell_format["horizontalAlignment"] = format_options["horizontalAlignment"]
if "verticalAlignment" in format_options:
cell_format["verticalAlignment"] = format_options["verticalAlignment"]
# Wrap strategy
if "wrapStrategy" in format_options:
cell_format["wrapStrategy"] = format_options["wrapStrategy"]
# Build the request
requests = [
{
"repeatCell": {
"range": grid_range,
"cell": {"userEnteredFormat": cell_format},
"fields": "userEnteredFormat",
}
}
]
# Add borders if specified
if "borders" in format_options:
border_request = _build_border_request(
grid_range, format_options["borders"]
)
if border_request:
requests.append(border_request)
response = (
service.spreadsheets()
.batchUpdate(spreadsheetId=spreadsheet_id, body={"requests": requests})
.execute()
)
return {
"success": True,
"updatedRange": range_name,
"appliedFormat": format_options,
}
except Exception as error:
return {"success": False, "error": str(error)}
def _parse_range(range_str, sheet_id):
"""Parse a range string like 'Sheet1!A1:B5' into grid coordinates"""
# Remove sheet name if present
if "!" in range_str:
range_str = range_str.split("!")[1]
# Split into start and end cells
if ":" in range_str:
start, end = range_str.split(":")
else:
start = end = range_str
start_col, start_row = _parse_cell(start)
end_col, end_row = _parse_cell(end)
return {
"sheetId": sheet_id,
"startRowIndex": start_row - 1,
"endRowIndex": end_row,
"startColumnIndex": start_col,
"endColumnIndex": end_col + 1,
}
def _parse_cell(cell_str):
"""Parse a cell reference like 'A1' into (column_index, row_number)"""
col_str = ""
row_str = ""
for char in cell_str:
if char.isalpha():
col_str += char.upper()
else:
row_str += char
# Convert column letters to index (A=0, B=1, ..., AA=26, etc.)
col_index = 0
for char in col_str:
col_index = col_index * 26 + (ord(char) - ord("A") + 1)
col_index -= 1
row_num = int(row_str) if row_str else 1
return col_index, row_num
def _build_border_request(grid_range, borders):
"""Build a border update request"""
border_style = {"style": "SOLID", "color": {"red": 0, "green": 0, "blue": 0}}
update_borders = {}
for side in ["top", "bottom", "left", "right"]:
if side in borders:
side_config = borders[side]
if isinstance(side_config, bool) and side_config:
update_borders[side] = border_style
elif isinstance(side_config, dict):
update_borders[side] = {
"style": side_config.get("style", "SOLID"),
"color": side_config.get(
"color", {"red": 0, "green": 0, "blue": 0}
),
}
if update_borders:
return {"updateBorders": {"range": grid_range, **update_borders}}
return None
def add_formula(spreadsheet_id, cell, formula):
"""Add a formula to a specific cell"""
try:
service = get_sheets_service()
result = (
service.spreadsheets()
.values()
.update(
spreadsheetId=spreadsheet_id,
range=cell,
valueInputOption="USER_ENTERED",
body={"values": [[formula]]},
)
.execute()
)
return {"success": True, "updatedCell": cell, "formula": formula}
except Exception as error:
return {"success": False, "error": str(error)}
def share_spreadsheet(spreadsheet_id, email, role="reader"):
"""
Share a spreadsheet with a user
role: 'reader', 'writer', 'commenter'
"""
try:
drive_service = get_drive_service()
permission = {"type": "user", "role": role, "emailAddress": email}
result = (
drive_service.permissions()
.create(fileId=spreadsheet_id, body=permission, sendNotificationEmail=True)
.execute()
)
return {
"success": True,
"permissionId": result.get("id"),
"sharedWith": email,
"role": role,
}
except Exception as error:
return {"success": False, "error": str(error)}
# Helper function for creating job tracker
def create_job_tracker(title="Job Applications Tracker"):
"""Create a job tracker spreadsheet with predefined columns"""
try:
# Create spreadsheet
result = create_spreadsheet(title, ["Applications", "Interviews", "Offers"])
if not result["success"]:
return result
spreadsheet_id = result["spreadsheetId"]
# Add headers to Applications sheet
headers = [
[
"Company",
"Position",
"Location",
"Salary Range",
"Date Applied",
"Status",
"Contact",
"Email",
"Notes",
"Follow-up Date",
]
]
write_sheet_data(spreadsheet_id, "Applications!A1:J1", headers)
# Add headers to Interviews sheet
interview_headers = [
[
"Company",
"Position",
"Interview Date",
"Interview Type",
"Interviewer",
"Status",
"Notes",
"Next Steps",
]
]
write_sheet_data(spreadsheet_id, "Interviews!A1:H1", interview_headers)
# Add headers to Offers sheet
offer_headers = [
[
"Company",
"Position",
"Base Salary",
"Bonus",
"Benefits",
"Start Date",
"Deadline",
"Status",
"Notes",
]
]
write_sheet_data(spreadsheet_id, "Offers!A1:I1", offer_headers)
# Format headers (bold)
format_cells(
spreadsheet_id,
"Applications!A1:J1",
{"bold": True, "backgroundColor": {"red": 0.9, "green": 0.9, "blue": 0.9}},
)
format_cells(
spreadsheet_id,
"Interviews!A1:H1",
{"bold": True, "backgroundColor": {"red": 0.9, "green": 0.9, "blue": 0.9}},
)
format_cells(
spreadsheet_id,
"Offers!A1:I1",
{"bold": True, "backgroundColor": {"red": 0.9, "green": 0.9, "blue": 0.9}},
)
return {
"success": True,
"spreadsheetId": spreadsheet_id,
"spreadsheetUrl": result["spreadsheetUrl"],
"message": "Job tracker created with Applications, Interviews, and Offers sheets",
}
except Exception as error:
return {"success": False, "error": str(error)}