apply ruff formatting

This commit is contained in:
Taylor Wilsdon
2025-12-13 13:49:28 -08:00
parent 1d80a24ca4
commit 6b8352a354
50 changed files with 4010 additions and 2842 deletions

View File

@@ -15,9 +15,9 @@ from .sheets_tools import (
__all__ = [
"list_spreadsheets",
"get_spreadsheet_info",
"get_spreadsheet_info",
"read_sheet_values",
"modify_sheet_values",
"create_spreadsheet",
"create_sheet",
]
]

View File

@@ -85,8 +85,7 @@ def _parse_a1_range(range_name: str, sheets: List[dict]) -> dict:
break
if target_sheet is None:
available_titles = [
sheet.get("properties", {}).get("title", "Untitled")
for sheet in sheets
sheet.get("properties", {}).get("title", "Untitled") for sheet in sheets
]
available_list = ", ".join(available_titles) if available_titles else "none"
raise UserInputError(
@@ -209,7 +208,9 @@ def _grid_range_to_a1(grid_range: dict, sheet_titles: dict[int, str]) -> str:
end_label = f"{col_label(end_col - 1 if end_col is not None else None)}{row_label(end_row - 1 if end_row is not None else None)}"
if start_label and end_label:
range_ref = start_label if start_label == end_label else f"{start_label}:{end_label}"
range_ref = (
start_label if start_label == end_label else f"{start_label}:{end_label}"
)
elif start_label:
range_ref = start_label
elif end_label:
@@ -227,7 +228,9 @@ def _summarize_conditional_rule(
Produce a concise human-readable summary of a conditional formatting rule.
"""
ranges = rule.get("ranges", [])
range_labels = [_grid_range_to_a1(rng, sheet_titles) for rng in ranges] or ["(no range)"]
range_labels = [_grid_range_to_a1(rng, sheet_titles) for rng in ranges] or [
"(no range)"
]
if "booleanRule" in rule:
boolean_rule = rule["booleanRule"]
@@ -288,7 +291,9 @@ def _format_conditional_rules_section(
lines = [f'{indent}Conditional formats for "{sheet_title}" ({len(rules)}):']
for idx, rule in enumerate(rules):
lines.append(f"{indent} {_summarize_conditional_rule(rule, idx, sheet_titles)}")
lines.append(
f"{indent} {_summarize_conditional_rule(rule, idx, sheet_titles)}"
)
return "\n".join(lines)
@@ -379,7 +384,7 @@ def _parse_condition_values(
parsed = json.loads(parsed)
except json.JSONDecodeError as exc:
raise UserInputError(
'condition_values must be a list or a JSON-encoded list (e.g., \'["=$B2>1000"]\').'
"condition_values must be a list or a JSON-encoded list (e.g., '[\"=$B2>1000\"]')."
) from exc
if parsed is not None and not isinstance(parsed, list):
@@ -396,7 +401,7 @@ def _parse_condition_values(
def _parse_gradient_points(
gradient_points: Optional[Union[str, List[dict]]]
gradient_points: Optional[Union[str, List[dict]]],
) -> Optional[List[dict]]:
"""
Normalize gradient points into a list of dicts with type/value/color.
@@ -424,7 +429,9 @@ def _parse_gradient_points(
normalized_points: List[dict] = []
for idx, point in enumerate(parsed):
if not isinstance(point, dict):
raise UserInputError(f"gradient_points[{idx}] must be an object with type/color.")
raise UserInputError(
f"gradient_points[{idx}] must be an object with type/color."
)
point_type = point.get("type")
if not point_type or point_type.upper() not in GRADIENT_POINT_TYPES:
@@ -432,7 +439,11 @@ def _parse_gradient_points(
f"gradient_points[{idx}].type must be one of {sorted(GRADIENT_POINT_TYPES)}."
)
color_raw = point.get("color")
color_dict = _parse_hex_color(color_raw) if not isinstance(color_raw, dict) else color_raw
color_dict = (
_parse_hex_color(color_raw)
if not isinstance(color_raw, dict)
else color_raw
)
if not color_dict:
raise UserInputError(f"gradient_points[{idx}].color is required.")
@@ -462,7 +473,9 @@ def _build_boolean_rule(
cond_type_normalized = condition_type.upper()
if cond_type_normalized not in CONDITION_TYPES:
raise UserInputError(f"condition_type must be one of {sorted(CONDITION_TYPES)}.")
raise UserInputError(
f"condition_type must be one of {sorted(CONDITION_TYPES)}."
)
condition = {"type": cond_type_normalized}
if condition_values:

View File

@@ -30,6 +30,7 @@ from gsheets.sheets_helpers import (
# Configure module logger
logger = logging.getLogger(__name__)
@server.tool()
@handle_http_errors("list_spreadsheets", is_read_only=True, service_type="sheets")
@require_google_service("drive", "drive_read")
@@ -68,7 +69,7 @@ async def list_spreadsheets(
return f"No spreadsheets found for {user_google_email}."
spreadsheets_list = [
f"- \"{file['name']}\" (ID: {file['id']}) | Modified: {file.get('modifiedTime', 'Unknown')} | Link: {file.get('webViewLink', 'No link')}"
f'- "{file["name"]}" (ID: {file["id"]}) | Modified: {file.get("modifiedTime", "Unknown")} | Link: {file.get("webViewLink", "No link")}'
for file in files
]
@@ -77,7 +78,9 @@ async def list_spreadsheets(
+ "\n".join(spreadsheets_list)
)
logger.info(f"Successfully listed {len(files)} spreadsheets for {user_google_email}.")
logger.info(
f"Successfully listed {len(files)} spreadsheets for {user_google_email}."
)
return text_output
@@ -99,7 +102,9 @@ async def get_spreadsheet_info(
Returns:
str: Formatted spreadsheet information including title, locale, and sheets list.
"""
logger.info(f"[get_spreadsheet_info] Invoked. Email: '{user_google_email}', Spreadsheet ID: {spreadsheet_id}")
logger.info(
f"[get_spreadsheet_info] Invoked. Email: '{user_google_email}', Spreadsheet ID: {spreadsheet_id}"
)
spreadsheet = await asyncio.to_thread(
service.spreadsheets()
@@ -133,7 +138,7 @@ async def get_spreadsheet_info(
rules = sheet.get("conditionalFormats", []) or []
sheets_info.append(
f" - \"{sheet_name}\" (ID: {sheet_id}) | Size: {rows}x{cols} | Conditional formats: {len(rules)}"
f' - "{sheet_name}" (ID: {sheet_id}) | Size: {rows}x{cols} | Conditional formats: {len(rules)}'
)
if rules:
sheets_info.append(
@@ -145,13 +150,15 @@ async def get_spreadsheet_info(
sheets_section = "\n".join(sheets_info) if sheets_info else " No sheets found"
text_output = "\n".join(
[
f"Spreadsheet: \"{title}\" (ID: {spreadsheet_id}) | Locale: {locale}",
f'Spreadsheet: "{title}" (ID: {spreadsheet_id}) | Locale: {locale}',
f"Sheets ({len(sheets)}):",
sheets_section,
]
)
logger.info(f"Successfully retrieved info for spreadsheet {spreadsheet_id} for {user_google_email}.")
logger.info(
f"Successfully retrieved info for spreadsheet {spreadsheet_id} for {user_google_email}."
)
return text_output
@@ -175,7 +182,9 @@ async def read_sheet_values(
Returns:
str: The formatted values from the specified range.
"""
logger.info(f"[read_sheet_values] Invoked. Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Range: {range_name}")
logger.info(
f"[read_sheet_values] Invoked. Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Range: {range_name}"
)
result = await asyncio.to_thread(
service.spreadsheets()
@@ -232,27 +241,37 @@ async def modify_sheet_values(
str: Confirmation message of the successful modification operation.
"""
operation = "clear" if clear_values else "write"
logger.info(f"[modify_sheet_values] Invoked. Operation: {operation}, Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Range: {range_name}")
logger.info(
f"[modify_sheet_values] Invoked. Operation: {operation}, Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Range: {range_name}"
)
# Parse values if it's a JSON string (MCP passes parameters as JSON strings)
if values is not None and isinstance(values, str):
try:
parsed_values = json.loads(values)
if not isinstance(parsed_values, list):
raise ValueError(f"Values must be a list, got {type(parsed_values).__name__}")
raise ValueError(
f"Values must be a list, got {type(parsed_values).__name__}"
)
# Validate it's a list of lists
for i, row in enumerate(parsed_values):
if not isinstance(row, list):
raise ValueError(f"Row {i} must be a list, got {type(row).__name__}")
raise ValueError(
f"Row {i} must be a list, got {type(row).__name__}"
)
values = parsed_values
logger.info(f"[modify_sheet_values] Parsed JSON string to Python list with {len(values)} rows")
logger.info(
f"[modify_sheet_values] Parsed JSON string to Python list with {len(values)} rows"
)
except json.JSONDecodeError as e:
raise UserInputError(f"Invalid JSON format for values: {e}")
except ValueError as e:
raise UserInputError(f"Invalid values structure: {e}")
if not clear_values and not values:
raise UserInputError("Either 'values' must be provided or 'clear_values' must be True.")
raise UserInputError(
"Either 'values' must be provided or 'clear_values' must be True."
)
if clear_values:
result = await asyncio.to_thread(
@@ -264,7 +283,9 @@ async def modify_sheet_values(
cleared_range = result.get("clearedRange", range_name)
text_output = f"Successfully cleared range '{cleared_range}' in spreadsheet {spreadsheet_id} for {user_google_email}."
logger.info(f"Successfully cleared range '{cleared_range}' for {user_google_email}.")
logger.info(
f"Successfully cleared range '{cleared_range}' for {user_google_email}."
)
else:
body = {"values": values}
@@ -288,7 +309,9 @@ async def modify_sheet_values(
f"Successfully updated range '{range_name}' in spreadsheet {spreadsheet_id} for {user_google_email}. "
f"Updated: {updated_cells} cells, {updated_rows} rows, {updated_columns} columns."
)
logger.info(f"Successfully updated {updated_cells} cells for {user_google_email}.")
logger.info(
f"Successfully updated {updated_cells} cells for {user_google_email}."
)
return text_output
@@ -380,9 +403,7 @@ async def format_sheet_range(
user_entered_format["backgroundColor"] = bg_color_parsed
fields.append("userEnteredFormat.backgroundColor")
if text_color_parsed:
user_entered_format["textFormat"] = {
"foregroundColor": text_color_parsed
}
user_entered_format["textFormat"] = {"foregroundColor": text_color_parsed}
fields.append("userEnteredFormat.textFormat.foregroundColor")
if number_format:
user_entered_format["numberFormat"] = number_format
@@ -485,7 +506,9 @@ async def add_conditional_formatting(
target_sheet = sheet
break
if target_sheet is None:
raise UserInputError("Target sheet not found while adding conditional formatting.")
raise UserInputError(
"Target sheet not found while adding conditional formatting."
)
current_rules = target_sheet.get("conditionalFormats", []) or []
@@ -613,7 +636,9 @@ async def update_conditional_formatting(
target_sheet = _select_sheet(sheets, sheet_name)
if target_sheet is None:
raise UserInputError("Target sheet not found while updating conditional formatting.")
raise UserInputError(
"Target sheet not found while updating conditional formatting."
)
sheet_props = target_sheet.get("properties", {})
sheet_id = sheet_props.get("sheetId")
@@ -661,10 +686,14 @@ async def update_conditional_formatting(
if not cond_type:
raise UserInputError("condition_type is required for boolean rules.")
if cond_type not in CONDITION_TYPES:
raise UserInputError(f"condition_type must be one of {sorted(CONDITION_TYPES)}.")
raise UserInputError(
f"condition_type must be one of {sorted(CONDITION_TYPES)}."
)
if condition_values_list is not None:
cond_values = [{"userEnteredValue": str(val)} for val in condition_values_list]
cond_values = [
{"userEnteredValue": str(val)} for val in condition_values_list
]
else:
cond_values = existing_condition.get("values")
@@ -706,7 +735,9 @@ async def update_conditional_formatting(
format_parts = []
if "backgroundColor" in new_format:
format_parts.append("background updated")
if "textFormat" in new_format and new_format["textFormat"].get("foregroundColor"):
if "textFormat" in new_format and new_format["textFormat"].get(
"foregroundColor"
):
format_parts.append("text color updated")
format_desc = ", ".join(format_parts) if format_parts else "format preserved"
@@ -841,13 +872,11 @@ async def create_spreadsheet(
Returns:
str: Information about the newly created spreadsheet including ID, URL, and locale.
"""
logger.info(f"[create_spreadsheet] Invoked. Email: '{user_google_email}', Title: {title}")
logger.info(
f"[create_spreadsheet] Invoked. Email: '{user_google_email}', Title: {title}"
)
spreadsheet_body = {
"properties": {
"title": title
}
}
spreadsheet_body = {"properties": {"title": title}}
if sheet_names:
spreadsheet_body["sheets"] = [
@@ -873,7 +902,9 @@ async def create_spreadsheet(
f"ID: {spreadsheet_id} | URL: {spreadsheet_url} | Locale: {locale}"
)
logger.info(f"Successfully created spreadsheet for {user_google_email}. ID: {spreadsheet_id}")
logger.info(
f"Successfully created spreadsheet for {user_google_email}. ID: {spreadsheet_id}"
)
return text_output
@@ -897,19 +928,11 @@ async def create_sheet(
Returns:
str: Confirmation message of the successful sheet creation.
"""
logger.info(f"[create_sheet] Invoked. Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Sheet: {sheet_name}")
logger.info(
f"[create_sheet] Invoked. Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Sheet: {sheet_name}"
)
request_body = {
"requests": [
{
"addSheet": {
"properties": {
"title": sheet_name
}
}
}
]
}
request_body = {"requests": [{"addSheet": {"properties": {"title": sheet_name}}}]}
response = await asyncio.to_thread(
service.spreadsheets()
@@ -919,11 +942,11 @@ async def create_sheet(
sheet_id = response["replies"][0]["addSheet"]["properties"]["sheetId"]
text_output = (
f"Successfully created sheet '{sheet_name}' (ID: {sheet_id}) in spreadsheet {spreadsheet_id} for {user_google_email}."
)
text_output = f"Successfully created sheet '{sheet_name}' (ID: {sheet_id}) in spreadsheet {spreadsheet_id} for {user_google_email}."
logger.info(f"Successfully created sheet for {user_google_email}. Sheet ID: {sheet_id}")
logger.info(
f"Successfully created sheet for {user_google_email}. Sheet ID: {sheet_id}"
)
return text_output
@@ -931,7 +954,7 @@ async def create_sheet(
_comment_tools = create_comment_tools("spreadsheet", "spreadsheet_id")
# Extract and register the functions
read_sheet_comments = _comment_tools['read_comments']
create_sheet_comment = _comment_tools['create_comment']
reply_to_sheet_comment = _comment_tools['reply_to_comment']
resolve_sheet_comment = _comment_tools['resolve_comment']
read_sheet_comments = _comment_tools["read_comments"]
create_sheet_comment = _comment_tools["create_comment"]
reply_to_sheet_comment = _comment_tools["reply_to_comment"]
resolve_sheet_comment = _comment_tools["resolve_comment"]