universal-mcp-applications 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. universal_mcp/applications/ahrefs/README.md +51 -0
  2. universal_mcp/applications/ahrefs/__init__.py +1 -0
  3. universal_mcp/applications/ahrefs/app.py +2291 -0
  4. universal_mcp/applications/airtable/README.md +22 -0
  5. universal_mcp/applications/airtable/__init__.py +1 -0
  6. universal_mcp/applications/airtable/app.py +479 -0
  7. universal_mcp/applications/apollo/README.md +44 -0
  8. universal_mcp/applications/apollo/__init__.py +1 -0
  9. universal_mcp/applications/apollo/app.py +1847 -0
  10. universal_mcp/applications/asana/README.md +199 -0
  11. universal_mcp/applications/asana/__init__.py +1 -0
  12. universal_mcp/applications/asana/app.py +9509 -0
  13. universal_mcp/applications/aws-s3/README.md +0 -0
  14. universal_mcp/applications/aws-s3/__init__.py +1 -0
  15. universal_mcp/applications/aws-s3/app.py +552 -0
  16. universal_mcp/applications/bill/README.md +0 -0
  17. universal_mcp/applications/bill/__init__.py +1 -0
  18. universal_mcp/applications/bill/app.py +8705 -0
  19. universal_mcp/applications/box/README.md +307 -0
  20. universal_mcp/applications/box/__init__.py +1 -0
  21. universal_mcp/applications/box/app.py +15987 -0
  22. universal_mcp/applications/braze/README.md +106 -0
  23. universal_mcp/applications/braze/__init__.py +1 -0
  24. universal_mcp/applications/braze/app.py +4754 -0
  25. universal_mcp/applications/cal-com-v2/README.md +150 -0
  26. universal_mcp/applications/cal-com-v2/__init__.py +1 -0
  27. universal_mcp/applications/cal-com-v2/app.py +5541 -0
  28. universal_mcp/applications/calendly/README.md +53 -0
  29. universal_mcp/applications/calendly/__init__.py +1 -0
  30. universal_mcp/applications/calendly/app.py +1436 -0
  31. universal_mcp/applications/canva/README.md +43 -0
  32. universal_mcp/applications/canva/__init__.py +1 -0
  33. universal_mcp/applications/canva/app.py +941 -0
  34. universal_mcp/applications/clickup/README.md +135 -0
  35. universal_mcp/applications/clickup/__init__.py +1 -0
  36. universal_mcp/applications/clickup/app.py +5009 -0
  37. universal_mcp/applications/coda/README.md +108 -0
  38. universal_mcp/applications/coda/__init__.py +1 -0
  39. universal_mcp/applications/coda/app.py +3671 -0
  40. universal_mcp/applications/confluence/README.md +198 -0
  41. universal_mcp/applications/confluence/__init__.py +1 -0
  42. universal_mcp/applications/confluence/app.py +6273 -0
  43. universal_mcp/applications/contentful/README.md +17 -0
  44. universal_mcp/applications/contentful/__init__.py +1 -0
  45. universal_mcp/applications/contentful/app.py +364 -0
  46. universal_mcp/applications/crustdata/README.md +25 -0
  47. universal_mcp/applications/crustdata/__init__.py +1 -0
  48. universal_mcp/applications/crustdata/app.py +586 -0
  49. universal_mcp/applications/dialpad/README.md +202 -0
  50. universal_mcp/applications/dialpad/__init__.py +1 -0
  51. universal_mcp/applications/dialpad/app.py +5949 -0
  52. universal_mcp/applications/digitalocean/README.md +463 -0
  53. universal_mcp/applications/digitalocean/__init__.py +1 -0
  54. universal_mcp/applications/digitalocean/app.py +20835 -0
  55. universal_mcp/applications/domain-checker/README.md +13 -0
  56. universal_mcp/applications/domain-checker/__init__.py +1 -0
  57. universal_mcp/applications/domain-checker/app.py +265 -0
  58. universal_mcp/applications/e2b/README.md +12 -0
  59. universal_mcp/applications/e2b/__init__.py +1 -0
  60. universal_mcp/applications/e2b/app.py +187 -0
  61. universal_mcp/applications/elevenlabs/README.md +88 -0
  62. universal_mcp/applications/elevenlabs/__init__.py +1 -0
  63. universal_mcp/applications/elevenlabs/app.py +3235 -0
  64. universal_mcp/applications/exa/README.md +15 -0
  65. universal_mcp/applications/exa/__init__.py +1 -0
  66. universal_mcp/applications/exa/app.py +221 -0
  67. universal_mcp/applications/falai/README.md +17 -0
  68. universal_mcp/applications/falai/__init__.py +1 -0
  69. universal_mcp/applications/falai/app.py +331 -0
  70. universal_mcp/applications/figma/README.md +49 -0
  71. universal_mcp/applications/figma/__init__.py +1 -0
  72. universal_mcp/applications/figma/app.py +1090 -0
  73. universal_mcp/applications/firecrawl/README.md +20 -0
  74. universal_mcp/applications/firecrawl/__init__.py +1 -0
  75. universal_mcp/applications/firecrawl/app.py +514 -0
  76. universal_mcp/applications/fireflies/README.md +25 -0
  77. universal_mcp/applications/fireflies/__init__.py +1 -0
  78. universal_mcp/applications/fireflies/app.py +506 -0
  79. universal_mcp/applications/fpl/README.md +23 -0
  80. universal_mcp/applications/fpl/__init__.py +1 -0
  81. universal_mcp/applications/fpl/app.py +1327 -0
  82. universal_mcp/applications/fpl/utils/api.py +142 -0
  83. universal_mcp/applications/fpl/utils/fixtures.py +629 -0
  84. universal_mcp/applications/fpl/utils/helper.py +982 -0
  85. universal_mcp/applications/fpl/utils/league_utils.py +546 -0
  86. universal_mcp/applications/fpl/utils/position_utils.py +68 -0
  87. universal_mcp/applications/ghost-content/README.md +25 -0
  88. universal_mcp/applications/ghost-content/__init__.py +1 -0
  89. universal_mcp/applications/ghost-content/app.py +654 -0
  90. universal_mcp/applications/github/README.md +1049 -0
  91. universal_mcp/applications/github/__init__.py +1 -0
  92. universal_mcp/applications/github/app.py +50600 -0
  93. universal_mcp/applications/gong/README.md +63 -0
  94. universal_mcp/applications/gong/__init__.py +1 -0
  95. universal_mcp/applications/gong/app.py +2297 -0
  96. universal_mcp/applications/google-ads/README.md +0 -0
  97. universal_mcp/applications/google-ads/__init__.py +1 -0
  98. universal_mcp/applications/google-ads/app.py +23 -0
  99. universal_mcp/applications/google-calendar/README.md +21 -0
  100. universal_mcp/applications/google-calendar/__init__.py +1 -0
  101. universal_mcp/applications/google-calendar/app.py +574 -0
  102. universal_mcp/applications/google-docs/README.md +25 -0
  103. universal_mcp/applications/google-docs/__init__.py +1 -0
  104. universal_mcp/applications/google-docs/app.py +760 -0
  105. universal_mcp/applications/google-drive/README.md +68 -0
  106. universal_mcp/applications/google-drive/__init__.py +1 -0
  107. universal_mcp/applications/google-drive/app.py +4936 -0
  108. universal_mcp/applications/google-gemini/README.md +25 -0
  109. universal_mcp/applications/google-gemini/__init__.py +1 -0
  110. universal_mcp/applications/google-gemini/app.py +663 -0
  111. universal_mcp/applications/google-mail/README.md +31 -0
  112. universal_mcp/applications/google-mail/__init__.py +1 -0
  113. universal_mcp/applications/google-mail/app.py +1354 -0
  114. universal_mcp/applications/google-searchconsole/README.md +21 -0
  115. universal_mcp/applications/google-searchconsole/__init__.py +1 -0
  116. universal_mcp/applications/google-searchconsole/app.py +320 -0
  117. universal_mcp/applications/google-sheet/README.md +36 -0
  118. universal_mcp/applications/google-sheet/__init__.py +1 -0
  119. universal_mcp/applications/google-sheet/app.py +1941 -0
  120. universal_mcp/applications/hashnode/README.md +20 -0
  121. universal_mcp/applications/hashnode/__init__.py +1 -0
  122. universal_mcp/applications/hashnode/app.py +455 -0
  123. universal_mcp/applications/heygen/README.md +44 -0
  124. universal_mcp/applications/heygen/__init__.py +1 -0
  125. universal_mcp/applications/heygen/app.py +961 -0
  126. universal_mcp/applications/http-tools/README.md +16 -0
  127. universal_mcp/applications/http-tools/__init__.py +1 -0
  128. universal_mcp/applications/http-tools/app.py +153 -0
  129. universal_mcp/applications/hubspot/README.md +239 -0
  130. universal_mcp/applications/hubspot/__init__.py +1 -0
  131. universal_mcp/applications/hubspot/app.py +416 -0
  132. universal_mcp/applications/jira/README.md +600 -0
  133. universal_mcp/applications/jira/__init__.py +1 -0
  134. universal_mcp/applications/jira/app.py +28804 -0
  135. universal_mcp/applications/klaviyo/README.md +313 -0
  136. universal_mcp/applications/klaviyo/__init__.py +1 -0
  137. universal_mcp/applications/klaviyo/app.py +11236 -0
  138. universal_mcp/applications/linkedin/README.md +15 -0
  139. universal_mcp/applications/linkedin/__init__.py +1 -0
  140. universal_mcp/applications/linkedin/app.py +243 -0
  141. universal_mcp/applications/mailchimp/README.md +281 -0
  142. universal_mcp/applications/mailchimp/__init__.py +1 -0
  143. universal_mcp/applications/mailchimp/app.py +10937 -0
  144. universal_mcp/applications/markitdown/README.md +12 -0
  145. universal_mcp/applications/markitdown/__init__.py +1 -0
  146. universal_mcp/applications/markitdown/app.py +63 -0
  147. universal_mcp/applications/miro/README.md +151 -0
  148. universal_mcp/applications/miro/__init__.py +1 -0
  149. universal_mcp/applications/miro/app.py +5429 -0
  150. universal_mcp/applications/ms-teams/README.md +42 -0
  151. universal_mcp/applications/ms-teams/__init__.py +1 -0
  152. universal_mcp/applications/ms-teams/app.py +1823 -0
  153. universal_mcp/applications/neon/README.md +74 -0
  154. universal_mcp/applications/neon/__init__.py +1 -0
  155. universal_mcp/applications/neon/app.py +2018 -0
  156. universal_mcp/applications/notion/README.md +30 -0
  157. universal_mcp/applications/notion/__init__.py +1 -0
  158. universal_mcp/applications/notion/app.py +527 -0
  159. universal_mcp/applications/openai/README.md +22 -0
  160. universal_mcp/applications/openai/__init__.py +1 -0
  161. universal_mcp/applications/openai/app.py +759 -0
  162. universal_mcp/applications/outlook/README.md +20 -0
  163. universal_mcp/applications/outlook/__init__.py +1 -0
  164. universal_mcp/applications/outlook/app.py +444 -0
  165. universal_mcp/applications/perplexity/README.md +12 -0
  166. universal_mcp/applications/perplexity/__init__.py +1 -0
  167. universal_mcp/applications/perplexity/app.py +65 -0
  168. universal_mcp/applications/pipedrive/README.md +284 -0
  169. universal_mcp/applications/pipedrive/__init__.py +1 -0
  170. universal_mcp/applications/pipedrive/app.py +12924 -0
  171. universal_mcp/applications/posthog/README.md +132 -0
  172. universal_mcp/applications/posthog/__init__.py +1 -0
  173. universal_mcp/applications/posthog/app.py +7125 -0
  174. universal_mcp/applications/reddit/README.md +135 -0
  175. universal_mcp/applications/reddit/__init__.py +1 -0
  176. universal_mcp/applications/reddit/app.py +4652 -0
  177. universal_mcp/applications/replicate/README.md +18 -0
  178. universal_mcp/applications/replicate/__init__.py +1 -0
  179. universal_mcp/applications/replicate/app.py +495 -0
  180. universal_mcp/applications/resend/README.md +40 -0
  181. universal_mcp/applications/resend/__init__.py +1 -0
  182. universal_mcp/applications/resend/app.py +881 -0
  183. universal_mcp/applications/retell/README.md +21 -0
  184. universal_mcp/applications/retell/__init__.py +1 -0
  185. universal_mcp/applications/retell/app.py +333 -0
  186. universal_mcp/applications/rocketlane/README.md +70 -0
  187. universal_mcp/applications/rocketlane/__init__.py +1 -0
  188. universal_mcp/applications/rocketlane/app.py +4346 -0
  189. universal_mcp/applications/semanticscholar/README.md +25 -0
  190. universal_mcp/applications/semanticscholar/__init__.py +1 -0
  191. universal_mcp/applications/semanticscholar/app.py +482 -0
  192. universal_mcp/applications/semrush/README.md +44 -0
  193. universal_mcp/applications/semrush/__init__.py +1 -0
  194. universal_mcp/applications/semrush/app.py +2081 -0
  195. universal_mcp/applications/sendgrid/README.md +362 -0
  196. universal_mcp/applications/sendgrid/__init__.py +1 -0
  197. universal_mcp/applications/sendgrid/app.py +9752 -0
  198. universal_mcp/applications/sentry/README.md +186 -0
  199. universal_mcp/applications/sentry/__init__.py +1 -0
  200. universal_mcp/applications/sentry/app.py +7471 -0
  201. universal_mcp/applications/serpapi/README.md +14 -0
  202. universal_mcp/applications/serpapi/__init__.py +1 -0
  203. universal_mcp/applications/serpapi/app.py +293 -0
  204. universal_mcp/applications/sharepoint/README.md +0 -0
  205. universal_mcp/applications/sharepoint/__init__.py +1 -0
  206. universal_mcp/applications/sharepoint/app.py +215 -0
  207. universal_mcp/applications/shopify/README.md +321 -0
  208. universal_mcp/applications/shopify/__init__.py +1 -0
  209. universal_mcp/applications/shopify/app.py +15392 -0
  210. universal_mcp/applications/shortcut/README.md +128 -0
  211. universal_mcp/applications/shortcut/__init__.py +1 -0
  212. universal_mcp/applications/shortcut/app.py +4478 -0
  213. universal_mcp/applications/slack/README.md +0 -0
  214. universal_mcp/applications/slack/__init__.py +1 -0
  215. universal_mcp/applications/slack/app.py +570 -0
  216. universal_mcp/applications/spotify/README.md +91 -0
  217. universal_mcp/applications/spotify/__init__.py +1 -0
  218. universal_mcp/applications/spotify/app.py +2526 -0
  219. universal_mcp/applications/supabase/README.md +87 -0
  220. universal_mcp/applications/supabase/__init__.py +1 -0
  221. universal_mcp/applications/supabase/app.py +2970 -0
  222. universal_mcp/applications/tavily/README.md +12 -0
  223. universal_mcp/applications/tavily/__init__.py +1 -0
  224. universal_mcp/applications/tavily/app.py +51 -0
  225. universal_mcp/applications/trello/README.md +266 -0
  226. universal_mcp/applications/trello/__init__.py +1 -0
  227. universal_mcp/applications/trello/app.py +10875 -0
  228. universal_mcp/applications/twillo/README.md +0 -0
  229. universal_mcp/applications/twillo/__init__.py +1 -0
  230. universal_mcp/applications/twillo/app.py +269 -0
  231. universal_mcp/applications/twitter/README.md +100 -0
  232. universal_mcp/applications/twitter/__init__.py +1 -0
  233. universal_mcp/applications/twitter/api_segments/__init__.py +0 -0
  234. universal_mcp/applications/twitter/api_segments/api_segment_base.py +51 -0
  235. universal_mcp/applications/twitter/api_segments/compliance_api.py +122 -0
  236. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +255 -0
  237. universal_mcp/applications/twitter/api_segments/dm_events_api.py +140 -0
  238. universal_mcp/applications/twitter/api_segments/likes_api.py +159 -0
  239. universal_mcp/applications/twitter/api_segments/lists_api.py +395 -0
  240. universal_mcp/applications/twitter/api_segments/openapi_json_api.py +34 -0
  241. universal_mcp/applications/twitter/api_segments/spaces_api.py +309 -0
  242. universal_mcp/applications/twitter/api_segments/trends_api.py +40 -0
  243. universal_mcp/applications/twitter/api_segments/tweets_api.py +1403 -0
  244. universal_mcp/applications/twitter/api_segments/usage_api.py +40 -0
  245. universal_mcp/applications/twitter/api_segments/users_api.py +1498 -0
  246. universal_mcp/applications/twitter/app.py +46 -0
  247. universal_mcp/applications/unipile/README.md +28 -0
  248. universal_mcp/applications/unipile/__init__.py +1 -0
  249. universal_mcp/applications/unipile/app.py +829 -0
  250. universal_mcp/applications/whatsapp/README.md +23 -0
  251. universal_mcp/applications/whatsapp/__init__.py +1 -0
  252. universal_mcp/applications/whatsapp/app.py +595 -0
  253. universal_mcp/applications/whatsapp-business/README.md +34 -0
  254. universal_mcp/applications/whatsapp-business/__init__.py +1 -0
  255. universal_mcp/applications/whatsapp-business/app.py +1065 -0
  256. universal_mcp/applications/wrike/README.md +46 -0
  257. universal_mcp/applications/wrike/__init__.py +1 -0
  258. universal_mcp/applications/wrike/app.py +1583 -0
  259. universal_mcp/applications/youtube/README.md +57 -0
  260. universal_mcp/applications/youtube/__init__.py +1 -0
  261. universal_mcp/applications/youtube/app.py +1696 -0
  262. universal_mcp/applications/zenquotes/README.md +12 -0
  263. universal_mcp/applications/zenquotes/__init__.py +1 -0
  264. universal_mcp/applications/zenquotes/app.py +31 -0
  265. universal_mcp_applications-0.1.1.dist-info/METADATA +172 -0
  266. universal_mcp_applications-0.1.1.dist-info/RECORD +268 -0
  267. universal_mcp_applications-0.1.1.dist-info/WHEEL +4 -0
  268. universal_mcp_applications-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1941 @@
1
+ from typing import Any
2
+
3
+ from universal_mcp.applications.application import APIApplication
4
+ from universal_mcp.integrations import Integration
5
+ from universal_mcp_google_sheet.helper import (
6
+ analyze_sheet_for_tables,
7
+ analyze_table_schema,
8
+ )
9
+
10
+
11
+ class GoogleSheetApp(APIApplication):
12
+ """
13
+ Application for interacting with Google Sheets API.
14
+ Provides tools to create and manage Google Spreadsheets.
15
+ """
16
+
17
+ def __init__(self, integration: Integration | None = None) -> None:
18
+ super().__init__(name="google-sheet", integration=integration)
19
+ self.base_url = "https://sheets.googleapis.com/v4/spreadsheets"
20
+
21
+ def create_spreadsheet(self, title: str) -> dict[str, Any]:
22
+ """
23
+ Creates a new blank Google Spreadsheet with the specified title and returns the API response.
24
+
25
+ Args:
26
+ title: String representing the desired title for the new spreadsheet
27
+
28
+ Returns:
29
+ Dictionary containing the full response from the Google Sheets API, including the spreadsheet's metadata and properties
30
+
31
+ Raises:
32
+ HTTPError: When the API request fails due to invalid authentication, network issues, or API limitations
33
+ ValueError: When the title parameter is empty or contains invalid characters
34
+
35
+ Tags:
36
+ create, spreadsheet, google-sheets, api, important
37
+ """
38
+ url = self.base_url
39
+ spreadsheet_data = {"properties": {"title": title}}
40
+ response = self._post(url, data=spreadsheet_data)
41
+ return self._handle_response(response)
42
+
43
+ def get_spreadsheet(self, spreadsheet_id: str) -> dict[str, Any]:
44
+ """
45
+ Retrieves detailed information about a specific Google Spreadsheet using its ID excluding cell data.
46
+
47
+ Args:
48
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to retrieve (found in the spreadsheet's URL)
49
+
50
+ Returns:
51
+ A dictionary containing the full spreadsheet metadata and contents, including properties, sheets, named ranges, and other spreadsheet-specific information from the Google Sheets API
52
+
53
+ Raises:
54
+ HTTPError: When the API request fails due to invalid spreadsheet_id or insufficient permissions
55
+ ConnectionError: When there's a network connectivity issue
56
+ ValueError: When the response cannot be parsed as JSON
57
+
58
+ Tags:
59
+ get, retrieve, spreadsheet, api, metadata, read, important
60
+ """
61
+ url = f"{self.base_url}/{spreadsheet_id}"
62
+ response = self._get(url)
63
+ return self._handle_response(response)
64
+
65
+ def get_values(
66
+ self,
67
+ spreadsheetId: str,
68
+ range: str,
69
+ majorDimension: str | None = None,
70
+ valueRenderOption: str | None = None,
71
+ dateTimeRenderOption: str | None = None,
72
+ ) -> dict[str, Any]:
73
+ """
74
+ Retrieves values from a specific range in a Google Spreadsheet.
75
+
76
+ Args:
77
+ spreadsheetId: The unique identifier of the Google Spreadsheet to retrieve values from
78
+ range: A1 notation range string (e.g., 'Sheet1!A1:B2')
79
+ majorDimension: The major dimension that results should use. "ROWS" or "COLUMNS". Example: "ROWS"
80
+ valueRenderOption: How values should be represented in the output. "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA". Example: "FORMATTED_VALUE"
81
+ dateTimeRenderOption: How dates, times, and durations should be represented. "SERIAL_NUMBER" or "FORMATTED_STRING". Example: "FORMATTED_STRING"
82
+
83
+ Returns:
84
+ A dictionary containing the API response with the requested spreadsheet values and metadata
85
+
86
+ Raises:
87
+ HTTPError: If the API request fails due to invalid spreadsheet_id, insufficient permissions, or invalid range format
88
+ ValueError: If the spreadsheet_id is empty or invalid
89
+
90
+ Tags:
91
+ get, read, spreadsheet, values, important
92
+ """
93
+ url = f"{self.base_url}/{spreadsheetId}/values/{range}"
94
+ params = {}
95
+
96
+ if majorDimension:
97
+ params["majorDimension"] = majorDimension
98
+ if valueRenderOption:
99
+ params["valueRenderOption"] = valueRenderOption
100
+ if dateTimeRenderOption:
101
+ params["dateTimeRenderOption"] = dateTimeRenderOption
102
+
103
+ response = self._get(url, params=params)
104
+ return self._handle_response(response)
105
+
106
+ def batch_get_values(
107
+ self, spreadsheet_id: str, ranges: list[str] | None = None
108
+ ) -> dict[str, Any]:
109
+ """
110
+ Retrieves multiple ranges of values from a Google Spreadsheet in a single batch request.
111
+
112
+ Args:
113
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to retrieve values from
114
+ ranges: Optional list of A1 notation or R1C1 notation range strings (e.g., ['Sheet1!A1:B2', 'Sheet2!C3:D4']). If None, returns values from the entire spreadsheet
115
+
116
+ Returns:
117
+ A dictionary containing the API response with the requested spreadsheet values and metadata
118
+
119
+ Raises:
120
+ HTTPError: If the API request fails due to invalid spreadsheet_id, insufficient permissions, or invalid range format
121
+ ValueError: If the spreadsheet_id is empty or invalid
122
+
123
+ Tags:
124
+ get, batch, read, spreadsheet, values
125
+ """
126
+ url = f"{self.base_url}/{spreadsheet_id}/values:batchGet"
127
+ params = {}
128
+ if ranges:
129
+ params["ranges"] = ranges
130
+ response = self._get(url, params=params)
131
+ return self._handle_response(response)
132
+
133
+ def insert_dimensions(
134
+ self,
135
+ spreadsheet_id: str,
136
+ sheet_id: int,
137
+ dimension: str,
138
+ start_index: int,
139
+ end_index: int,
140
+ inherit_from_before: bool = True,
141
+ include_spreadsheet_in_response: bool | None = None,
142
+ response_include_grid_data: bool | None = None,
143
+ response_ranges: list[str] | None = None,
144
+ ) -> dict[str, Any]:
145
+ """
146
+ Inserts new rows or columns into a Google Sheet at a specific position within the sheet.
147
+
148
+ This function inserts empty rows or columns at a specified location, shifting existing content.
149
+ Use this when you need to add rows/columns in the middle of your data.
150
+
151
+ Args:
152
+ spreadsheet_id: The ID of the spreadsheet to update. Example: "abc123spreadsheetId"
153
+ sheet_id: The ID of the sheet where the dimensions will be inserted. Example: 0
154
+ dimension: The dimension to insert. Valid values are "ROWS" or "COLUMNS". Example: "ROWS"
155
+ start_index: The start index (0-based) of the dimension range to insert. The inserted dimensions will be placed before this index. Example: 1
156
+ end_index: The end index (0-based, exclusive) of the dimension range to insert. The number of rows/columns to insert is `endIndex - startIndex`. Example: 3
157
+ inherit_from_before: If true, the new dimensions will inherit properties from the dimension before the startIndex. If false (default), they will inherit from the dimension at the startIndex. startIndex must be greater than 0 if inheritFromBefore is true. Example: True
158
+ include_spreadsheet_in_response: True if the updated spreadsheet should be included in the response. Example: True
159
+ response_include_grid_data: True if grid data should be included in the response (if includeSpreadsheetInResponse is true). Example: True
160
+ response_ranges: Limits the ranges of the spreadsheet to include in the response. Example: ["Sheet1!A1:B10"]
161
+
162
+ Returns:
163
+ A dictionary containing the Google Sheets API response with update details
164
+
165
+ Raises:
166
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
167
+ ValueError: When spreadsheet_id is empty or dimension is not "ROWS" or "COLUMNS"
168
+
169
+ Tags:
170
+ insert, modify, spreadsheet, rows, columns, dimensions, important
171
+ """
172
+ if not spreadsheet_id:
173
+ raise ValueError("spreadsheet_id cannot be empty")
174
+
175
+ if dimension not in ["ROWS", "COLUMNS"]:
176
+ raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
177
+
178
+ if start_index < 0 or end_index < 0:
179
+ raise ValueError("start_index and end_index must be non-negative")
180
+
181
+ if start_index >= end_index:
182
+ raise ValueError("end_index must be greater than start_index")
183
+
184
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
185
+
186
+ request_body: dict[str, Any] = {
187
+ "requests": [
188
+ {
189
+ "insertDimension": {
190
+ "inheritFromBefore": inherit_from_before,
191
+ "range": {
192
+ "dimension": dimension,
193
+ "sheetId": sheet_id,
194
+ "startIndex": start_index,
195
+ "endIndex": end_index,
196
+ },
197
+ }
198
+ }
199
+ ]
200
+ }
201
+
202
+ # Add optional parameters if provided
203
+ if include_spreadsheet_in_response is not None:
204
+ request_body["includeSpreadsheetInResponse"] = (
205
+ include_spreadsheet_in_response
206
+ )
207
+
208
+ if response_include_grid_data is not None:
209
+ request_body["responseIncludeGridData"] = response_include_grid_data
210
+
211
+ if response_ranges is not None:
212
+ request_body["responseRanges"] = response_ranges
213
+
214
+ response = self._post(url, data=request_body)
215
+ return self._handle_response(response)
216
+
217
+ def append_dimensions(
218
+ self,
219
+ spreadsheet_id: str,
220
+ sheet_id: int,
221
+ dimension: str,
222
+ length: int,
223
+ ) -> dict[str, Any]:
224
+ """
225
+ Appends empty rows or columns to the end of a Google Sheet.
226
+
227
+ This function adds empty rows or columns to the end of the sheet without affecting existing content.
228
+ Use this when you need to extend the sheet with additional space at the bottom or right.
229
+
230
+ Args:
231
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to modify
232
+ sheet_id: The ID of the sheet within the spreadsheet (0 for first sheet)
233
+ dimension: The type of dimension to append - "ROWS" or "COLUMNS"
234
+ length: The number of rows or columns to append to the end
235
+
236
+ Returns:
237
+ A dictionary containing the Google Sheets API response with update details
238
+
239
+ Raises:
240
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
241
+ ValueError: When spreadsheet_id is empty, dimension is not "ROWS" or "COLUMNS", or length is not positive
242
+
243
+ Tags:
244
+ append, modify, spreadsheet, rows, columns, dimensions, important
245
+ """
246
+ if not spreadsheet_id:
247
+ raise ValueError("spreadsheet_id cannot be empty")
248
+
249
+ if dimension not in ["ROWS", "COLUMNS"]:
250
+ raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
251
+
252
+ if length <= 0:
253
+ raise ValueError("length must be a positive integer")
254
+
255
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
256
+
257
+ request_body = {
258
+ "requests": [
259
+ {
260
+ "appendDimension": {
261
+ "sheetId": sheet_id,
262
+ "dimension": dimension,
263
+ "length": length,
264
+ }
265
+ }
266
+ ]
267
+ }
268
+
269
+ response = self._post(url, data=request_body)
270
+ return self._handle_response(response)
271
+
272
+ def delete_dimensions(
273
+ self,
274
+ spreadsheet_id: str,
275
+ sheet_id: int,
276
+ dimension: str,
277
+ start_index: int,
278
+ end_index: int,
279
+ include_spreadsheet_in_response: bool | None = None,
280
+ response_include_grid_data: bool | None = None,
281
+ response_ranges: list[str] | None = None,
282
+ ) -> dict[str, Any]:
283
+ """
284
+ Tool to delete specified rows or columns from a sheet in a google spreadsheet. use when you need to remove a range of rows or columns.
285
+ or Use this when you need to remove unwanted rows or columns from your data.
286
+
287
+ Args:
288
+ spreadsheet_id: The ID of the spreadsheet. Example: "abc123xyz789"
289
+ sheet_id: The ID of the sheet from which to delete the dimension. Example: 0 for first sheet
290
+ dimension: The dimension to delete. Example: "ROWS"
291
+ start_index: The zero-based start index of the range to delete, inclusive. The start index must be less than the end index. Example: 0
292
+ end_index: The zero-based end index of the range to delete, exclusive. The end index must be greater than the start index. Example: 1
293
+ include_spreadsheet_in_response: Determines if the update response should include the spreadsheet resource. Example: True
294
+ response_include_grid_data: True if grid data should be returned. This parameter is ignored if a field mask was set in the request. Example: True
295
+ response_ranges: Limits the ranges of cells included in the response spreadsheet. Example: ["Sheet1!A1:B2", "Sheet2!C:C"]
296
+
297
+ Returns:
298
+ A dictionary containing the Google Sheets API response with update details
299
+
300
+ Raises:
301
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
302
+ ValueError: When spreadsheet_id is empty, dimension is not "ROWS" or "COLUMNS", or indices are invalid
303
+
304
+ Tags:
305
+ delete, modify, spreadsheet, rows, columns, dimensions, important
306
+ """
307
+ if not spreadsheet_id:
308
+ raise ValueError("spreadsheet_id cannot be empty")
309
+
310
+ if dimension not in ["ROWS", "COLUMNS"]:
311
+ raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
312
+
313
+ if start_index < 0 or end_index < 0:
314
+ raise ValueError("start_index and end_index must be non-negative")
315
+
316
+ if start_index >= end_index:
317
+ raise ValueError("end_index must be greater than start_index")
318
+
319
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
320
+
321
+ request_body: dict[str, Any] = {
322
+ "requests": [
323
+ {
324
+ "deleteDimension": {
325
+ "range": {
326
+ "sheetId": sheet_id,
327
+ "dimension": dimension,
328
+ "startIndex": start_index,
329
+ "endIndex": end_index,
330
+ }
331
+ }
332
+ }
333
+ ]
334
+ }
335
+
336
+ # Add optional response parameters if provided
337
+ if include_spreadsheet_in_response is not None:
338
+ request_body["includeSpreadsheetInResponse"] = (
339
+ include_spreadsheet_in_response
340
+ )
341
+
342
+ if response_include_grid_data is not None:
343
+ request_body["responseIncludeGridData"] = response_include_grid_data
344
+
345
+ if response_ranges is not None:
346
+ request_body["responseRanges"] = response_ranges
347
+
348
+ response = self._post(url, data=request_body)
349
+ return self._handle_response(response)
350
+
351
+ def add_sheet(
352
+ self,
353
+ spreadsheetId: str,
354
+ title: str | None = None,
355
+ sheetId: int | None = None,
356
+ index: int | None = None,
357
+ sheetType: str = "GRID",
358
+ hidden: bool | None = None,
359
+ rightToLeft: bool | None = None,
360
+ tabColorStyle: dict | None = None,
361
+ # Grid properties
362
+ rowCount: int | None = None,
363
+ columnCount: int | None = None,
364
+ frozenRowCount: int | None = None,
365
+ frozenColumnCount: int | None = None,
366
+ hideGridlines: bool | None = None,
367
+ rowGroupControlAfter: bool | None = None,
368
+ columnGroupControlAfter: bool | None = None,
369
+ # Response options
370
+ includeSpreadsheetInResponse: bool = False,
371
+ responseIncludeGridData: bool = False,
372
+ ) -> dict[str, Any]:
373
+ """
374
+ Adds a new sheet (worksheet) to a spreadsheet. use this tool to create a new tab within an existing google sheet, optionally specifying its title, index, size, and other properties.
375
+
376
+ Args:
377
+ spreadsheetId: The ID of the spreadsheet to add the sheet to. This is the long string of characters in the URL of your Google Sheet. Example: "abc123xyz789"
378
+ title: The name of the sheet. Example: "Q3 Report"
379
+ sheetId: The ID of the sheet. If not set, an ID will be randomly generated. Must be non-negative if set.
380
+ index: The zero-based index of the sheet in the spreadsheet. Example: 0 for the first sheet.
381
+ sheetType: The type of sheet. Options: "GRID", "OBJECT", "DATA_SOURCE". Defaults to "GRID"
382
+ hidden: True if the sheet is hidden in the UI, false if it's visible.
383
+ rightToLeft: True if the sheet is an RTL sheet, false if it's LTR.
384
+ tabColorStyle: The color of the sheet tab. Can contain either 'rgbColor' (with red, green, blue, alpha values 0-1) or 'themeColor' (TEXT, BACKGROUND, ACCENT1-6, LINK).
385
+ rowCount: The number of rows in the sheet.
386
+ columnCount: The number of columns in the sheet.
387
+ frozenRowCount: The number of rows that are frozen in the sheet.
388
+ frozenColumnCount: The number of columns that are frozen in the sheet.
389
+ hideGridlines: True if the gridlines are hidden, false if they are shown.
390
+ rowGroupControlAfter: True if the row group control toggle is shown after the group, false if before.
391
+ columnGroupControlAfter: True if the column group control toggle is shown after the group, false if before.
392
+ includeSpreadsheetInResponse: Whether the response should include the entire spreadsheet resource. Defaults to false.
393
+ responseIncludeGridData: True if grid data should be returned. This parameter is ignored if includeSpreadsheetInResponse is false. Defaults to false.
394
+
395
+ Returns:
396
+ A dictionary containing the Google Sheets API response with the new sheet details
397
+
398
+ Raises:
399
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
400
+ ValueError: When spreadsheet_id is empty or invalid parameters are provided
401
+
402
+ Tags:
403
+ add, sheet, spreadsheet, create
404
+ """
405
+ if not spreadsheetId:
406
+ raise ValueError("spreadsheetId cannot be empty")
407
+
408
+ url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
409
+
410
+ # Build the addSheet request with properties
411
+ add_sheet_request = {"properties": {}}
412
+
413
+ if title is not None:
414
+ add_sheet_request["properties"]["title"] = title
415
+
416
+ if sheetId is not None:
417
+ add_sheet_request["properties"]["sheetId"] = sheetId
418
+
419
+ if index is not None:
420
+ add_sheet_request["properties"]["index"] = index
421
+
422
+ if sheetType is not None:
423
+ add_sheet_request["properties"]["sheetType"] = sheetType
424
+
425
+ if hidden is not None:
426
+ add_sheet_request["properties"]["hidden"] = hidden
427
+
428
+ if rightToLeft is not None:
429
+ add_sheet_request["properties"]["rightToLeft"] = rightToLeft
430
+
431
+ if tabColorStyle is not None:
432
+ add_sheet_request["properties"]["tabColorStyle"] = tabColorStyle
433
+
434
+ # Build grid properties if any grid-related parameters are provided
435
+ grid_properties = {}
436
+ if any(
437
+ param is not None
438
+ for param in [
439
+ rowCount,
440
+ columnCount,
441
+ frozenRowCount,
442
+ frozenColumnCount,
443
+ hideGridlines,
444
+ rowGroupControlAfter,
445
+ columnGroupControlAfter,
446
+ ]
447
+ ):
448
+ if rowCount is not None:
449
+ grid_properties["rowCount"] = rowCount
450
+
451
+ if columnCount is not None:
452
+ grid_properties["columnCount"] = columnCount
453
+
454
+ if frozenRowCount is not None:
455
+ grid_properties["frozenRowCount"] = frozenRowCount
456
+
457
+ if frozenColumnCount is not None:
458
+ grid_properties["frozenColumnCount"] = frozenColumnCount
459
+
460
+ if hideGridlines is not None:
461
+ grid_properties["hideGridlines"] = hideGridlines
462
+
463
+ if rowGroupControlAfter is not None:
464
+ grid_properties["rowGroupControlAfter"] = rowGroupControlAfter
465
+
466
+ if columnGroupControlAfter is not None:
467
+ grid_properties["columnGroupControlAfter"] = columnGroupControlAfter
468
+
469
+ add_sheet_request["properties"]["gridProperties"] = grid_properties
470
+
471
+ request_body = {
472
+ "requests": [{"addSheet": add_sheet_request}],
473
+ "includeSpreadsheetInResponse": includeSpreadsheetInResponse,
474
+ "responseIncludeGridData": responseIncludeGridData,
475
+ }
476
+
477
+ response = self._post(url, data=request_body)
478
+ return self._handle_response(response)
479
+
480
+ def add_basic_chart(
481
+ self,
482
+ spreadsheet_id: str,
483
+ source_sheet_id: int,
484
+ chart_title: str,
485
+ chart_type: str,
486
+ domain_range: dict,
487
+ series_ranges: list[dict],
488
+ new_sheet: bool = False,
489
+ chart_position: dict | None = None,
490
+ x_axis_title: str | None = None,
491
+ y_axis_title: str | None = None,
492
+ ) -> dict[str, Any]:
493
+ """
494
+ Adds a basic chart to a Google Spreadsheet like a column chart, bar chart, line chart and area chart.
495
+
496
+ This function creates various types of charts from the specified data ranges and places it in a new sheet or existing sheet.
497
+ Use this when you need to visualize data in different chart formats.
498
+
499
+ Args:
500
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to modify
501
+ source_sheet_id: The ID of the sheet containing the source data
502
+ chart_title: The title for the chart
503
+ chart_type: The type of chart to create. Supported types: "COLUMN", "BAR", "LINE", "AREA", "STEPPED_AREA", "SCATTER", "COMBO"
504
+ domain_range: Dictionary containing domain range info (e.g., {"startRowIndex": 0, "endRowIndex": 7, "startColumnIndex": 0, "endColumnIndex": 1})
505
+ series_ranges: List of dictionaries containing series range info for each data series
506
+ new_sheet: Whether to create the chart in a new sheet (True) or existing sheet (False)
507
+ chart_position: Optional positioning for chart when new_sheet=False. Example: {"overlayPosition": {"anchorCell": {"sheetId": 0, "rowIndex": 10, "columnIndex": 5}, "offsetXPixels": 0, "offsetYPixels": 0, "widthPixels": 600, "heightPixels": 400}}
508
+ x_axis_title: Optional title for the X-axis (bottom axis). If not provided, defaults to "Categories"
509
+ y_axis_title: Optional title for the Y-axis (left axis). If not provided, defaults to "Values"
510
+
511
+ Returns:
512
+ A dictionary containing the Google Sheets API response with the chart details
513
+
514
+ Raises:
515
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
516
+ ValueError: When spreadsheet_id is empty or invalid parameters are provided
517
+
518
+ Tags:
519
+ add, chart, basic-chart, visualization
520
+ """
521
+ if not spreadsheet_id:
522
+ raise ValueError("spreadsheet_id cannot be empty")
523
+
524
+ if not chart_title:
525
+ raise ValueError("chart_title cannot be empty")
526
+
527
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
528
+
529
+ # Build the chart specification
530
+ chart_spec = {
531
+ "title": chart_title,
532
+ "basicChart": {
533
+ "chartType": chart_type,
534
+ "legendPosition": "BOTTOM_LEGEND",
535
+ "axis": [
536
+ {
537
+ "position": "BOTTOM_AXIS",
538
+ "title": x_axis_title if x_axis_title else "Categories",
539
+ },
540
+ {
541
+ "position": "LEFT_AXIS",
542
+ "title": y_axis_title if y_axis_title else "Values",
543
+ },
544
+ ],
545
+ "domains": [
546
+ {
547
+ "domain": {
548
+ "sourceRange": {
549
+ "sources": [
550
+ {
551
+ "sheetId": source_sheet_id,
552
+ "startRowIndex": domain_range.get(
553
+ "startRowIndex", 0
554
+ ),
555
+ "endRowIndex": domain_range.get(
556
+ "endRowIndex", 1
557
+ ),
558
+ "startColumnIndex": domain_range.get(
559
+ "startColumnIndex", 0
560
+ ),
561
+ "endColumnIndex": domain_range.get(
562
+ "endColumnIndex", 1
563
+ ),
564
+ }
565
+ ]
566
+ }
567
+ }
568
+ }
569
+ ],
570
+ "series": [],
571
+ "headerCount": 1,
572
+ },
573
+ }
574
+
575
+ # Add series data
576
+ for series_range in series_ranges:
577
+ series = {
578
+ "series": {
579
+ "sourceRange": {
580
+ "sources": [
581
+ {
582
+ "sheetId": source_sheet_id,
583
+ "startRowIndex": series_range.get("startRowIndex", 0),
584
+ "endRowIndex": series_range.get("endRowIndex", 1),
585
+ "startColumnIndex": series_range.get(
586
+ "startColumnIndex", 0
587
+ ),
588
+ "endColumnIndex": series_range.get("endColumnIndex", 1),
589
+ }
590
+ ]
591
+ }
592
+ },
593
+ "targetAxis": "LEFT_AXIS",
594
+ }
595
+ chart_spec["basicChart"]["series"].append(series)
596
+
597
+ # Build the position specification
598
+ if new_sheet:
599
+ position_spec = {"newSheet": True}
600
+ # For existing sheet, use overlayPosition structure
601
+ elif chart_position:
602
+ position_spec = chart_position
603
+ else:
604
+ # Default positioning when placing in existing sheet
605
+ position_spec = {
606
+ "overlayPosition": {
607
+ "anchorCell": {
608
+ "sheetId": source_sheet_id,
609
+ "rowIndex": 0,
610
+ "columnIndex": 0,
611
+ },
612
+ "offsetXPixels": 0,
613
+ "offsetYPixels": 0,
614
+ "widthPixels": 600,
615
+ "heightPixels": 400,
616
+ }
617
+ }
618
+
619
+ # Build the request body
620
+ request_body = {
621
+ "requests": [
622
+ {"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}
623
+ ]
624
+ }
625
+
626
+ response = self._post(url, data=request_body)
627
+ return self._handle_response(response)
628
+
629
+ def add_pie_chart(
630
+ self,
631
+ spreadsheet_id: str,
632
+ source_sheet_id: int,
633
+ chart_title: str,
634
+ data_range: dict,
635
+ new_sheet: bool = False,
636
+ chart_position: dict | None = None,
637
+ legend_position: str = "BOTTOM_LEGEND",
638
+ pie_hole: float | None = None,
639
+ ) -> dict[str, Any]:
640
+ """
641
+ Adds a pie chart to a Google Spreadsheet.
642
+
643
+ This function creates a pie chart from the specified data range and places it in a new sheet or existing sheet.
644
+ Use this when you need to visualize data as proportions of a whole.
645
+
646
+ Args:
647
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to modify
648
+ source_sheet_id: The ID of the sheet containing the source data
649
+ chart_title: The title for the chart
650
+ data_range: Dictionary containing data range info (e.g., {"startRowIndex": 0, "endRowIndex": 7, "startColumnIndex": 0, "endColumnIndex": 2})
651
+ new_sheet: Whether to create the chart in a new sheet (True) or existing sheet (False)
652
+ chart_position: Optional positioning for chart when new_sheet=False. Example: {"overlayPosition": {"anchorCell": {"sheetId": 0, "rowIndex": 10, "columnIndex": 5}, "offsetXPixels": 0, "offsetYPixels": 0, "widthPixels": 600, "heightPixels": 400}}
653
+ legend_position: Position of the legend. Options: "BOTTOM_LEGEND", "LEFT_LEGEND", "RIGHT_LEGEND", "TOP_LEGEND", "NO_LEGEND"
654
+ pie_hole: Optional hole size for creating a donut chart (0.0 to 1.0). 0.0 = solid pie, 0.5 = 50% hole
655
+
656
+ Returns:
657
+ A dictionary containing the Google Sheets API response with the chart details
658
+
659
+ Raises:
660
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
661
+ ValueError: When spreadsheet_id is empty or invalid parameters are provided
662
+
663
+ Tags:
664
+ add, chart, pie, visualization
665
+ """
666
+ if not spreadsheet_id:
667
+ raise ValueError("spreadsheet_id cannot be empty")
668
+
669
+ if not chart_title:
670
+ raise ValueError("chart_title cannot be empty")
671
+
672
+ if pie_hole is not None and not 0 <= pie_hole <= 1:
673
+ raise ValueError("pie_hole must be between 0.0 and 1.0")
674
+
675
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
676
+
677
+ # Build the pie chart specification
678
+ pie_chart_spec = {
679
+ "legendPosition": legend_position,
680
+ "domain": {
681
+ "sourceRange": {
682
+ "sources": [
683
+ {
684
+ "sheetId": source_sheet_id,
685
+ "startRowIndex": data_range.get("startRowIndex", 0),
686
+ "endRowIndex": data_range.get("endRowIndex", 1),
687
+ "startColumnIndex": data_range.get("startColumnIndex", 0),
688
+ "endColumnIndex": data_range.get("startColumnIndex", 0) + 1,
689
+ }
690
+ ]
691
+ }
692
+ },
693
+ "series": {
694
+ "sourceRange": {
695
+ "sources": [
696
+ {
697
+ "sheetId": source_sheet_id,
698
+ "startRowIndex": data_range.get("startRowIndex", 0),
699
+ "endRowIndex": data_range.get("endRowIndex", 1),
700
+ "startColumnIndex": data_range.get("startColumnIndex", 0)
701
+ + 1,
702
+ "endColumnIndex": data_range.get("endColumnIndex", 2),
703
+ }
704
+ ]
705
+ }
706
+ },
707
+ }
708
+
709
+ # Add pie hole for donut chart if specified
710
+ if pie_hole is not None:
711
+ pie_chart_spec["pieHole"] = pie_hole
712
+
713
+ # Build the chart specification
714
+ chart_spec = {"title": chart_title, "pieChart": pie_chart_spec}
715
+
716
+ # Build the position specification
717
+ if new_sheet:
718
+ position_spec = {"newSheet": True}
719
+ # For existing sheet, use overlayPosition structure
720
+ elif chart_position:
721
+ position_spec = chart_position
722
+ else:
723
+ # Default positioning when placing in existing sheet
724
+ position_spec = {
725
+ "overlayPosition": {
726
+ "anchorCell": {
727
+ "sheetId": source_sheet_id,
728
+ "rowIndex": 0,
729
+ "columnIndex": 0,
730
+ },
731
+ "offsetXPixels": 0,
732
+ "offsetYPixels": 0,
733
+ "widthPixels": 600,
734
+ "heightPixels": 400,
735
+ }
736
+ }
737
+
738
+ # Build the request body
739
+ request_body = {
740
+ "requests": [
741
+ {"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}
742
+ ]
743
+ }
744
+
745
+ response = self._post(url, data=request_body)
746
+ return self._handle_response(response)
747
+
748
+ def add_table(
749
+ self,
750
+ spreadsheet_id: str,
751
+ sheet_id: int,
752
+ table_name: str,
753
+ table_id: str,
754
+ start_row_index: int,
755
+ end_row_index: int,
756
+ start_column_index: int,
757
+ end_column_index: int,
758
+ column_properties: list[dict] | None = None,
759
+ ) -> dict[str, Any]:
760
+ """
761
+ Adds a table to a Google Spreadsheet.
762
+
763
+ This function creates a table with specified properties and column types.
764
+ Use this when you need to create structured data with headers, footers, and column types.
765
+
766
+ Args:
767
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to modify
768
+ sheet_id: The ID of the sheet where the table will be created
769
+ table_name: The name of the table
770
+ table_id: The unique identifier for the table
771
+ start_row_index: The starting row index (0-based)
772
+ end_row_index: The ending row index (exclusive)
773
+ start_column_index: The starting column index (0-based)
774
+ end_column_index: The ending column index (exclusive)
775
+ column_properties: Optional list of column properties with types and validation rules. Valid column types: "TEXT", "PERCENT", "DROPDOWN", "DOUBLE", "CURRENCY", "DATE", "TIME", "DATE_TIME". Example: [{"columnIndex": 0, "columnName": "Model Number", "columnType": "TEXT"}, {"columnIndex": 1, "columnName": "Sales - Jan", "columnType": "DOUBLE"}, {"columnIndex": 2, "columnName": "Price", "columnType": "CURRENCY"}, {"columnIndex": 3, "columnName": "Progress", "columnType": "PERCENT"}, {"columnIndex": 4, "columnName": "Created Date", "columnType": "DATE"}, {"columnIndex": 5, "columnName": "Status", "columnType": "DROPDOWN", "dataValidationRule": {"condition": {"type": "ONE_OF_LIST", "values": [{"userEnteredValue": "Active"}, {"userEnteredValue": "Inactive"}]}}}]
776
+ Returns:
777
+ A dictionary containing the Google Sheets API response with the table details
778
+
779
+ Raises:
780
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
781
+ ValueError: When spreadsheet_id is empty or invalid parameters are provided
782
+
783
+ Tags:
784
+ add, table, structured-data
785
+ """
786
+ if not spreadsheet_id:
787
+ raise ValueError("spreadsheet_id cannot be empty")
788
+
789
+ if not table_name:
790
+ raise ValueError("table_name cannot be empty")
791
+
792
+ if not table_id:
793
+ raise ValueError("table_id cannot be empty")
794
+
795
+ if (
796
+ start_row_index < 0
797
+ or end_row_index < 0
798
+ or start_column_index < 0
799
+ or end_column_index < 0
800
+ ):
801
+ raise ValueError("All indices must be non-negative")
802
+
803
+ if start_row_index >= end_row_index:
804
+ raise ValueError("end_row_index must be greater than start_row_index")
805
+
806
+ if start_column_index >= end_column_index:
807
+ raise ValueError("end_column_index must be greater than start_column_index")
808
+
809
+ # Validate column properties if provided
810
+ if column_properties:
811
+ valid_column_types = [
812
+ "TEXT",
813
+ "PERCENT",
814
+ "DROPDOWN",
815
+ "DOUBLE",
816
+ "CURRENCY",
817
+ "DATE",
818
+ "TIME",
819
+ "DATE_TIME",
820
+ ]
821
+ for i, prop in enumerate(column_properties):
822
+ if (
823
+ "columnType" in prop
824
+ and prop["columnType"] not in valid_column_types
825
+ ):
826
+ raise ValueError(
827
+ f"Invalid column type '{prop['columnType']}' at index {i}. Valid types are: {', '.join(valid_column_types)}"
828
+ )
829
+
830
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
831
+
832
+ # Build the table specification
833
+ table_spec = {
834
+ "name": table_name,
835
+ "tableId": table_id,
836
+ "range": {
837
+ "sheetId": sheet_id,
838
+ "startColumnIndex": start_column_index,
839
+ "endColumnIndex": end_column_index,
840
+ "startRowIndex": start_row_index,
841
+ "endRowIndex": end_row_index,
842
+ },
843
+ }
844
+
845
+ # Add column properties if provided
846
+ if column_properties:
847
+ table_spec["columnProperties"] = column_properties
848
+
849
+ # Build the request body
850
+ request_body = {"requests": [{"addTable": {"table": table_spec}}]}
851
+
852
+ response = self._post(url, data=request_body)
853
+ return self._handle_response(response)
854
+
855
+ def update_table(
856
+ self,
857
+ spreadsheet_id: str,
858
+ table_id: str,
859
+ table_name: str | None = None,
860
+ start_row_index: int | None = None,
861
+ end_row_index: int | None = None,
862
+ start_column_index: int | None = None,
863
+ end_column_index: int | None = None,
864
+ column_properties: list[dict] | None = None,
865
+ ) -> dict[str, Any]:
866
+ """
867
+ Updates an existing table in a Google Spreadsheet.
868
+
869
+ This function modifies table properties such as name, range, and column properties.
870
+ Use this when you need to modify an existing table's structure or properties.
871
+
872
+ Args:
873
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to modify
874
+ table_id: The unique identifier of the table to update
875
+ table_name: Optional new name for the table
876
+ start_row_index: Optional new starting row index (0-based)
877
+ end_row_index: Optional new ending row index (exclusive)
878
+ start_column_index: Optional new starting column index (0-based)
879
+ end_column_index: Optional new ending column index (exclusive)
880
+ column_properties: Optional list of column properties with types and validation rules. Valid column types: "TEXT", "PERCENT", "DROPDOWN", "DOUBLE", "CURRENCY", "DATE", "TIME", "DATE_TIME". Example: [{"columnIndex": 0, "columnName": "Model Number", "columnType": "TEXT"}, {"columnIndex": 1, "columnName": "Sales - Jan", "columnType": "DOUBLE"}, {"columnIndex": 2, "columnName": "Price", "columnType": "CURRENCY"}, {"columnIndex": 3, "columnName": "Progress", "columnType": "PERCENT"}, {"columnIndex": 4, "columnName": "Created Date", "columnType": "DATE"}, {"columnIndex": 5, "columnName": "Status", "columnType": "DROPDOWN", "dataValidationRule": {"condition": {"type": "ONE_OF_LIST", "values": [{"userEnteredValue": "Active"}, {"userEnteredValue": "Inactive"}]}}}]
881
+
882
+ Returns:
883
+ A dictionary containing the Google Sheets API response with the updated table details
884
+
885
+ Raises:
886
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
887
+ ValueError: When spreadsheet_id or table_id is empty or invalid parameters are provided
888
+
889
+ Tags:
890
+ update, table, modify, structured-data
891
+ """
892
+ if not spreadsheet_id:
893
+ raise ValueError("spreadsheet_id cannot be empty")
894
+
895
+ if not table_id:
896
+ raise ValueError("table_id cannot be empty")
897
+
898
+ # Validate indices if provided
899
+ if start_row_index is not None and start_row_index < 0:
900
+ raise ValueError("start_row_index must be non-negative")
901
+
902
+ if end_row_index is not None and end_row_index < 0:
903
+ raise ValueError("end_row_index must be non-negative")
904
+
905
+ if start_column_index is not None and start_column_index < 0:
906
+ raise ValueError("start_column_index must be non-negative")
907
+
908
+ if end_column_index is not None and end_column_index < 0:
909
+ raise ValueError("end_column_index must be non-negative")
910
+
911
+ if (
912
+ start_row_index is not None
913
+ and end_row_index is not None
914
+ and start_row_index >= end_row_index
915
+ ):
916
+ raise ValueError("end_row_index must be greater than start_row_index")
917
+
918
+ if (
919
+ start_column_index is not None
920
+ and end_column_index is not None
921
+ and start_column_index >= end_column_index
922
+ ):
923
+ raise ValueError("end_column_index must be greater than start_column_index")
924
+
925
+ # Validate column properties if provided
926
+ if column_properties:
927
+ valid_column_types = [
928
+ "TEXT",
929
+ "PERCENT",
930
+ "DROPDOWN",
931
+ "DOUBLE",
932
+ "CURRENCY",
933
+ "DATE",
934
+ "TIME",
935
+ "DATE_TIME",
936
+ ]
937
+ for i, prop in enumerate(column_properties):
938
+ if (
939
+ "columnType" in prop
940
+ and prop["columnType"] not in valid_column_types
941
+ ):
942
+ raise ValueError(
943
+ f"Invalid column type '{prop['columnType']}' at index {i}. Valid types are: {', '.join(valid_column_types)}"
944
+ )
945
+
946
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
947
+
948
+ # Build the table specification and track fields to update
949
+ table_spec: dict[str, Any] = {"tableId": table_id}
950
+ fields_to_update = []
951
+
952
+ # Add optional properties if provided
953
+ if table_name is not None:
954
+ table_spec["name"] = table_name
955
+ fields_to_update.append("name")
956
+
957
+ # Build range if any range parameters are provided
958
+ range_params: dict[str, Any] = {}
959
+ if start_row_index is not None:
960
+ range_params["startRowIndex"] = start_row_index
961
+ if end_row_index is not None:
962
+ range_params["endRowIndex"] = end_row_index
963
+ if start_column_index is not None:
964
+ range_params["startColumnIndex"] = start_column_index
965
+ if end_column_index is not None:
966
+ range_params["endColumnIndex"] = end_column_index
967
+
968
+ if range_params:
969
+ table_spec["range"] = range_params
970
+ fields_to_update.append("range")
971
+
972
+ # Add column properties if provided
973
+ if column_properties:
974
+ table_spec["columnProperties"] = column_properties
975
+ fields_to_update.append("columnProperties")
976
+
977
+ # Validate that at least one field is being updated
978
+ if not fields_to_update:
979
+ raise ValueError(
980
+ "At least one field must be provided for update (table_name, range indices, or column_properties)"
981
+ )
982
+
983
+ # Build the request body
984
+ request_body = {
985
+ "requests": [
986
+ {
987
+ "updateTable": {
988
+ "table": table_spec,
989
+ "fields": ",".join(fields_to_update),
990
+ }
991
+ }
992
+ ]
993
+ }
994
+
995
+ response = self._post(url, data=request_body)
996
+ return self._handle_response(response)
997
+
998
+ def clear_values(self, spreadsheet_id: str, range: str) -> dict[str, Any]:
999
+ """
1000
+ Clears all values from a specified range in a Google Spreadsheet while preserving cell formatting and other properties
1001
+
1002
+ Args:
1003
+ spreadsheet_id: The unique identifier of the Google Spreadsheet to modify
1004
+ range: The A1 or R1C1 notation range of cells to clear (e.g., 'Sheet1!A1:B2')
1005
+
1006
+ Returns:
1007
+ A dictionary containing the Google Sheets API response
1008
+
1009
+ Raises:
1010
+ HttpError: When the API request fails due to invalid spreadsheet_id, invalid range format, or insufficient permissions
1011
+ ValueError: When spreadsheet_id is empty or range is in invalid format
1012
+
1013
+ Tags:
1014
+ clear, modify, spreadsheet, api, sheets, data-management, important
1015
+ """
1016
+ url = f"{self.base_url}/{spreadsheet_id}/values/{range}:clear"
1017
+ response = self._post(url, data={})
1018
+ return self._handle_response(response)
1019
+
1020
+ def update_values(
1021
+ self,
1022
+ spreadsheet_id: str,
1023
+ range: str,
1024
+ values: list[list[Any]],
1025
+ value_input_option: str = "RAW",
1026
+ ) -> dict[str, Any]:
1027
+ """
1028
+ Updates cell values in a specified range of a Google Spreadsheet using the Sheets API
1029
+
1030
+ Args:
1031
+ spreadsheet_id: The unique identifier of the target Google Spreadsheet
1032
+ range: The A1 notation range where values will be updated (e.g., 'Sheet1!A1:B2')
1033
+ values: A list of lists containing the data to write, where each inner list represents a row of values
1034
+ value_input_option: Determines how input data should be interpreted: 'RAW' (as-is) or 'USER_ENTERED' (parsed as UI input). Defaults to 'RAW'
1035
+
1036
+ Returns:
1037
+ A dictionary containing the Google Sheets API response with update details
1038
+
1039
+ Raises:
1040
+ RequestError: When the API request fails due to invalid parameters or network issues
1041
+ AuthenticationError: When authentication with the Google Sheets API fails
1042
+
1043
+ Tags:
1044
+ update, write, sheets, api, important, data-modification, google-sheets
1045
+ """
1046
+ url = f"{self.base_url}/{spreadsheet_id}/values/{range}"
1047
+ params = {"valueInputOption": value_input_option}
1048
+ data = {"range": range, "values": values}
1049
+ response = self._put(url, data=data, params=params)
1050
+ return self._handle_response(response)
1051
+
1052
+ def batch_clear_values(
1053
+ self,
1054
+ spreadsheet_id: str,
1055
+ ranges: list[str],
1056
+ ) -> dict[str, Any]:
1057
+ """
1058
+ Tool to clear one or more ranges of values from a spreadsheet. use when you need to remove data from specific cells or ranges while keeping formatting and other properties intact.
1059
+
1060
+ Args:
1061
+ spreadsheet_id: The ID of the spreadsheet to update. Example: "1q2w3e4r5t6y7u8i9o0p"
1062
+ ranges: The ranges to clear, in A1 notation or R1C1 notation. Example: ["Sheet1!A1:B2", "Sheet1!C3:D4"]
1063
+
1064
+ Returns:
1065
+ A dictionary containing the Google Sheets API response with clear details
1066
+
1067
+ Raises:
1068
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1069
+ ValueError: When spreadsheet_id is empty or ranges is empty
1070
+
1071
+ Tags:
1072
+ clear, batch, values, spreadsheet
1073
+ """
1074
+ if not spreadsheet_id:
1075
+ raise ValueError("spreadsheet_id cannot be empty")
1076
+
1077
+ if not ranges or not isinstance(ranges, list) or len(ranges) == 0:
1078
+ raise ValueError("ranges must be a non-empty list")
1079
+
1080
+ url = f"{self.base_url}/{spreadsheet_id}/values:batchClear"
1081
+
1082
+ request_body = {"ranges": ranges}
1083
+
1084
+ response = self._post(url, data=request_body)
1085
+ return self._handle_response(response)
1086
+
1087
+ def batch_get_values_by_data_filter(
1088
+ self,
1089
+ spreadsheet_id: str,
1090
+ data_filters: list[dict],
1091
+ major_dimension: str | None = None,
1092
+ value_render_option: str | None = None,
1093
+ date_time_render_option: str | None = None,
1094
+ ) -> dict[str, Any]:
1095
+ """
1096
+ Tool to return one or more ranges of values from a spreadsheet that match the specified data filters. use when you need to retrieve specific data sets based on filtering criteria rather than entire sheets or fixed ranges.
1097
+
1098
+ Args:
1099
+ spreadsheet_id: The ID of the spreadsheet to retrieve data from. Example: "1q2w3e4r5t6y7u8i9o0p"
1100
+ data_filters: The data filters used to match the ranges of values to retrieve. Ranges that match any of the specified data filters are included in the response. Each filter can contain:
1101
+ - a1Range: Selects data that matches the specified A1 range. Example: "Sheet1!A1:B5"
1102
+ - gridRange: Selects data that matches the specified grid range. Example: {"sheetId": 0, "startRowIndex": 0, "endRowIndex": 5, "startColumnIndex": 0, "endColumnIndex": 2}
1103
+ major_dimension: The major dimension that results should use. For example, if the spreadsheet data is: A1=1,B1=2,A2=3,B2=4, then a request that selects that range and sets majorDimension=ROWS returns [[1,2],[3,4]], whereas a request that sets majorDimension=COLUMNS returns [[1,3],[2,4]]. Options: "ROWS" or "COLUMNS". Example: "ROWS"
1104
+ value_render_option: How values should be represented in the output. The default render option is FORMATTED_VALUE. Options: "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA". Example: "FORMATTED_VALUE"
1105
+ date_time_render_option: How dates, times, and durations should be represented in the output. This is ignored if valueRenderOption is FORMATTED_VALUE. The default dateTime render option is SERIAL_NUMBER. Options: "SERIAL_NUMBER" or "FORMATTED_STRING". Example: "SERIAL_NUMBER"
1106
+
1107
+ Returns:
1108
+ A dictionary containing the filtered values that match the specified data filters
1109
+
1110
+ Raises:
1111
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1112
+ ValueError: When spreadsheet_id is empty or data_filters is empty
1113
+
1114
+ Tags:
1115
+ get, batch, data-filter, values, spreadsheet
1116
+ """
1117
+ if not spreadsheet_id:
1118
+ raise ValueError("spreadsheet_id cannot be empty")
1119
+
1120
+ if (
1121
+ not data_filters
1122
+ or not isinstance(data_filters, list)
1123
+ or len(data_filters) == 0
1124
+ ):
1125
+ raise ValueError("data_filters must be a non-empty list")
1126
+
1127
+ if major_dimension and major_dimension not in ["ROWS", "COLUMNS"]:
1128
+ raise ValueError('major_dimension must be either "ROWS" or "COLUMNS"')
1129
+
1130
+ if value_render_option and value_render_option not in [
1131
+ "FORMATTED_VALUE",
1132
+ "UNFORMATTED_VALUE",
1133
+ "FORMULA",
1134
+ ]:
1135
+ raise ValueError(
1136
+ 'value_render_option must be either "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA"'
1137
+ )
1138
+
1139
+ if date_time_render_option and date_time_render_option not in [
1140
+ "SERIAL_NUMBER",
1141
+ "FORMATTED_STRING",
1142
+ ]:
1143
+ raise ValueError(
1144
+ 'date_time_render_option must be either "SERIAL_NUMBER" or "FORMATTED_STRING"'
1145
+ )
1146
+
1147
+ url = f"{self.base_url}/{spreadsheet_id}/values:batchGetByDataFilter"
1148
+
1149
+ request_body: dict[str, Any] = {"dataFilters": data_filters}
1150
+
1151
+ # Add optional parameters if provided
1152
+ if major_dimension:
1153
+ request_body["majorDimension"] = major_dimension
1154
+
1155
+ if value_render_option:
1156
+ request_body["valueRenderOption"] = value_render_option
1157
+
1158
+ if date_time_render_option:
1159
+ request_body["dateTimeRenderOption"] = date_time_render_option
1160
+
1161
+ response = self._post(url, data=request_body)
1162
+ return self._handle_response(response)
1163
+
1164
+ def copy_to_sheet(
1165
+ self,
1166
+ spreadsheet_id: str,
1167
+ sheet_id: int,
1168
+ destination_spreadsheet_id: str,
1169
+ ) -> dict[str, Any]:
1170
+ """
1171
+ Tool to copy a single sheet from a spreadsheet to another spreadsheet. Use when you need to duplicate a sheet into a different spreadsheet.
1172
+
1173
+
1174
+ Args:
1175
+ spreadsheet_id: The ID of the spreadsheet containing the sheet to copy. Example: "1qZ_..."
1176
+ sheet_id: The ID of the sheet to copy. Example: 0
1177
+ destination_spreadsheet_id: The ID of the spreadsheet to copy the sheet to. Example: "2rY_..."
1178
+
1179
+ Returns:
1180
+ A dictionary containing the Google Sheets API response with copy details
1181
+
1182
+ Raises:
1183
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1184
+ ValueError: When any required parameter is empty or invalid
1185
+
1186
+ Tags:
1187
+ copy, sheet, spreadsheet, duplicate
1188
+ """
1189
+ if not spreadsheet_id:
1190
+ raise ValueError("spreadsheet_id cannot be empty")
1191
+
1192
+ if sheet_id is None:
1193
+ raise ValueError("sheet_id cannot be empty")
1194
+
1195
+ if not destination_spreadsheet_id:
1196
+ raise ValueError("destination_spreadsheet_id cannot be empty")
1197
+
1198
+ url = f"{self.base_url}/{spreadsheet_id}/sheets/{sheet_id}:copyTo"
1199
+
1200
+ request_body = {"destinationSpreadsheetId": destination_spreadsheet_id}
1201
+
1202
+ response = self._post(url, data=request_body)
1203
+ return self._handle_response(response)
1204
+
1205
+ def batch_update(
1206
+ self,
1207
+ spreadsheet_id: str,
1208
+ sheet_name: str,
1209
+ values: list[list[Any]],
1210
+ first_cell_location: str | None = None,
1211
+ value_input_option: str = "USER_ENTERED",
1212
+ include_values_in_response: bool = False,
1213
+ ) -> dict[str, Any]:
1214
+ """
1215
+ Updates a specified range in a google sheet with given values, or appends them as new rows if `first cell location` is omitted; ensure the target sheet exists and the spreadsheet contains at least one worksheet.
1216
+ Use this tool for basic updates/append. Overwrites existing data when appending.
1217
+
1218
+ Args:
1219
+ spreadsheet_id: The unique identifier of the Google Sheets spreadsheet to be updated. Example: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
1220
+ sheet_name: The name of the specific sheet within the spreadsheet to update. Example: "Sheet1"
1221
+ values: A 2D list of cell values. Each inner list represents a row. Values can be strings, numbers, or booleans. Ensure columns are properly aligned across rows. Example: [['Item', 'Cost', 'Stocked', 'Ship Date'], ['Wheel', 20.5, True, '2020-06-01'], ['Screw', 0.5, True, '2020-06-03'], ['Nut', 0.25, False, '2020-06-02']]
1222
+ first_cell_location: The starting cell for the update range, specified in A1 notation (e.g., 'A1', 'B2'). The update will extend from this cell to the right and down, based on the provided values. If omitted, values are appended to the end of the sheet. Example: "A1"
1223
+ value_input_option: How input data is interpreted. 'USER_ENTERED': Values parsed as if typed by a user (e.g., strings may become numbers/dates, formulas are calculated); recommended for formulas. 'RAW': Values stored as-is without parsing (e.g., '123' stays string, '=SUM(A1:B1)' stays string). Defaults to 'USER_ENTERED'. Example: "USER_ENTERED"
1224
+ include_values_in_response: If set to True, the response will include the updated values from the spreadsheet. Defaults to False. Example: True
1225
+
1226
+ Returns:
1227
+ A dictionary containing the Google Sheets API response with update details
1228
+
1229
+ Raises:
1230
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1231
+ ValueError: When spreadsheet_id is empty, sheet_name is empty, or values is empty
1232
+
1233
+ Tags:
1234
+ batch, update, write, sheets, api, important, data-modification, google-sheets
1235
+ """
1236
+ if not spreadsheet_id:
1237
+ raise ValueError("spreadsheet_id cannot be empty")
1238
+
1239
+ if not sheet_name:
1240
+ raise ValueError("sheet_name cannot be empty")
1241
+
1242
+ if not values or not isinstance(values, list) or len(values) == 0:
1243
+ raise ValueError("values must be a non-empty 2D list")
1244
+
1245
+ if value_input_option not in ["RAW", "USER_ENTERED"]:
1246
+ raise ValueError(
1247
+ 'value_input_option must be either "RAW" or "USER_ENTERED"'
1248
+ )
1249
+
1250
+ # Determine the range based on first_cell_location
1251
+ if first_cell_location:
1252
+ # Update specific range starting from first_cell_location
1253
+ range_str = f"{sheet_name}!{first_cell_location}"
1254
+ else:
1255
+ # Append to the sheet (no specific range)
1256
+ range_str = f"{sheet_name}"
1257
+
1258
+ url = f"{self.base_url}/{spreadsheet_id}/values/{range_str}"
1259
+
1260
+ params = {
1261
+ "valueInputOption": value_input_option,
1262
+ "includeValuesInResponse": include_values_in_response,
1263
+ }
1264
+
1265
+ data = {"values": values}
1266
+
1267
+ response = self._put(url, data=data, params=params)
1268
+ return self._handle_response(response)
1269
+
1270
+ def append_values(
1271
+ self,
1272
+ spreadsheet_id: str,
1273
+ range: str,
1274
+ value_input_option: str,
1275
+ values: list[list[Any]],
1276
+ insert_data_option: str | None = None,
1277
+ include_values_in_response: bool | None = None,
1278
+ response_value_render_option: str | None = None,
1279
+ response_date_time_render_option: str | None = None,
1280
+ ) -> dict[str, Any]:
1281
+ """
1282
+ Tool to append values to a spreadsheet. use when you need to add new data to the end of an existing table in a google sheet.
1283
+ Use it for Insert new rows (INSERT_ROWS), specific range append, advanced options
1284
+
1285
+ Args:
1286
+ spreadsheet_id: The ID of the spreadsheet to update. Example: "1q0gLhLdGXYZblahblahblah"
1287
+ range: The A1 notation of a range to search for a logical table of data. Values are appended after the last row of the table. Example: "Sheet1!A1:B2"
1288
+ value_input_option: How the input data should be interpreted. Required. Options: "RAW" or "USER_ENTERED". Example: "USER_ENTERED"
1289
+ values: The data to be written. This is an array of arrays, the outer array representing all the data and each inner array representing a major dimension. Each item in the inner array corresponds with one cell. Example: [["A1_val1", "A1_val2"], ["A2_val1", "A2_val2"]]
1290
+ insert_data_option: How the input data should be inserted. Options: "OVERWRITE" or "INSERT_ROWS". Use "INSERT_ROWS" to add new rows instead of overwriting existing data. Example: "INSERT_ROWS"
1291
+ include_values_in_response: Determines if the update response should include the values of the cells that were appended. By default, responses do not include the updated values. Example: True
1292
+ response_value_render_option: Determines how values in the response should be rendered. The default render option is FORMATTED_VALUE. Options: "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA". Example: "FORMATTED_VALUE"
1293
+ response_date_time_render_option: Determines how dates, times, and durations in the response should be rendered. This is ignored if responseValueRenderOption is FORMATTED_VALUE. The default dateTime render option is SERIAL_NUMBER. Options: "SERIAL_NUMBER" or "FORMATTED_STRING". Example: "SERIAL_NUMBER"
1294
+
1295
+ Returns:
1296
+ A dictionary containing the Google Sheets API response with append details
1297
+
1298
+ Raises:
1299
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1300
+ ValueError: When required parameters are empty or invalid
1301
+
1302
+ Tags:
1303
+ append, values, spreadsheet, data, important
1304
+ """
1305
+ if not spreadsheet_id:
1306
+ raise ValueError("spreadsheet_id cannot be empty")
1307
+
1308
+ if not range:
1309
+ raise ValueError("range cannot be empty")
1310
+
1311
+ if not value_input_option:
1312
+ raise ValueError("value_input_option cannot be empty")
1313
+
1314
+ if value_input_option not in ["RAW", "USER_ENTERED"]:
1315
+ raise ValueError(
1316
+ 'value_input_option must be either "RAW" or "USER_ENTERED"'
1317
+ )
1318
+
1319
+ if not values or not isinstance(values, list) or len(values) == 0:
1320
+ raise ValueError("values must be a non-empty 2D list")
1321
+
1322
+ if insert_data_option and insert_data_option not in [
1323
+ "OVERWRITE",
1324
+ "INSERT_ROWS",
1325
+ ]:
1326
+ raise ValueError(
1327
+ 'insert_data_option must be either "OVERWRITE" or "INSERT_ROWS"'
1328
+ )
1329
+
1330
+ if response_value_render_option and response_value_render_option not in [
1331
+ "FORMATTED_VALUE",
1332
+ "UNFORMATTED_VALUE",
1333
+ "FORMULA",
1334
+ ]:
1335
+ raise ValueError(
1336
+ 'response_value_render_option must be either "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA"'
1337
+ )
1338
+
1339
+ if (
1340
+ response_date_time_render_option
1341
+ and response_date_time_render_option
1342
+ not in ["SERIAL_NUMBER", "FORMATTED_STRING"]
1343
+ ):
1344
+ raise ValueError(
1345
+ 'response_date_time_render_option must be either "SERIAL_NUMBER" or "FORMATTED_STRING"'
1346
+ )
1347
+
1348
+ url = f"{self.base_url}/{spreadsheet_id}/values/{range}:append"
1349
+
1350
+ params: dict[str, Any] = {"valueInputOption": value_input_option}
1351
+
1352
+ # Add optional parameters if provided
1353
+ if insert_data_option:
1354
+ params["insertDataOption"] = insert_data_option
1355
+
1356
+ if include_values_in_response is not None:
1357
+ params["includeValuesInResponse"] = include_values_in_response
1358
+
1359
+ if response_value_render_option:
1360
+ params["responseValueRenderOption"] = response_value_render_option
1361
+
1362
+ if response_date_time_render_option:
1363
+ params["responseDateTimeRenderOption"] = response_date_time_render_option
1364
+
1365
+ data = {"values": values}
1366
+
1367
+ response = self._post(url, data=data, params=params)
1368
+ return self._handle_response(response)
1369
+
1370
+ def clear_basic_filter(
1371
+ self,
1372
+ spreadsheet_id: str,
1373
+ sheet_id: int,
1374
+ ) -> dict[str, Any]:
1375
+ """
1376
+ Tool to clear the basic filter from a sheet. use when you need to remove an existing basic filter from a specific sheet within a google spreadsheet.
1377
+
1378
+ Args:
1379
+ spreadsheet_id: The ID of the spreadsheet. Example: "abc123xyz789"
1380
+ sheet_id: The ID of the sheet on which the basic filter should be cleared. Example: 0
1381
+
1382
+ Returns:
1383
+ A dictionary containing the Google Sheets API response with update details
1384
+
1385
+ Raises:
1386
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1387
+ ValueError: When spreadsheet_id is empty or sheet_id is negative
1388
+
1389
+ Tags:
1390
+ clear, filter, basic-filter, spreadsheet
1391
+ """
1392
+ if not spreadsheet_id:
1393
+ raise ValueError("spreadsheet_id cannot be empty")
1394
+
1395
+ if sheet_id < 0:
1396
+ raise ValueError("sheet_id must be non-negative")
1397
+
1398
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
1399
+
1400
+ request_body = {"requests": [{"clearBasicFilter": {"sheetId": sheet_id}}]}
1401
+
1402
+ response = self._post(url, data=request_body)
1403
+ return self._handle_response(response)
1404
+
1405
+ def delete_sheet(
1406
+ self,
1407
+ spreadsheet_id: str,
1408
+ sheet_id: int,
1409
+ ) -> dict[str, Any]:
1410
+ """
1411
+ Tool to delete a sheet (worksheet) from a spreadsheet. use when you need to remove a specific sheet from a google sheet document.
1412
+
1413
+ Args:
1414
+ spreadsheet_id: The ID of the spreadsheet from which to delete the sheet. Example: "abc123xyz789"
1415
+ sheet_id: The ID of the sheet to delete. If the sheet is of DATA_SOURCE type, the associated DataSource is also deleted. Example: 123456789
1416
+
1417
+ Returns:
1418
+ A dictionary containing the Google Sheets API response with update details
1419
+
1420
+ Raises:
1421
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1422
+ ValueError: When spreadsheet_id is empty or sheet_id is negative
1423
+
1424
+ Tags:
1425
+ delete, sheet, spreadsheet, worksheet
1426
+ """
1427
+ if not spreadsheet_id:
1428
+ raise ValueError("spreadsheet_id cannot be empty")
1429
+
1430
+ if sheet_id < 0:
1431
+ raise ValueError("sheet_id must be non-negative")
1432
+
1433
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
1434
+
1435
+ request_body = {"requests": [{"deleteSheet": {"sheetId": sheet_id}}]}
1436
+
1437
+ response = self._post(url, data=request_body)
1438
+ return self._handle_response(response)
1439
+
1440
+ def list_tables(
1441
+ self,
1442
+ spreadsheet_id: str,
1443
+ min_rows: int = 2,
1444
+ min_columns: int = 1,
1445
+ min_confidence: float = 0.5,
1446
+ ) -> dict[str, Any]:
1447
+ """
1448
+ This action is used to list all tables in a google spreadsheet, call this action to get the list of tables in a spreadsheet. discover all tables in a google spreadsheet by analyzing sheet structure and detecting data patterns. uses heuristic analysis to find header rows, data boundaries, and table structures.
1449
+
1450
+ Args:
1451
+ spreadsheet_id: Google Sheets ID from the URL (e.g., '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'). Example: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
1452
+ min_rows: Minimum number of data rows to consider a valid table. Example: 2
1453
+ min_columns: Minimum number of columns to consider a valid table. Example: 1
1454
+ min_confidence: Minimum confidence score (0.0-1.0) to consider a valid table. Example: 0.5
1455
+
1456
+ Returns:
1457
+ A dictionary containing the list of discovered tables with their properties
1458
+
1459
+ Raises:
1460
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1461
+ ValueError: When spreadsheet_id is empty or parameters are invalid
1462
+
1463
+ Tags:
1464
+ list, tables, discover, analyze, spreadsheet, important
1465
+ """
1466
+ if not spreadsheet_id:
1467
+ raise ValueError("spreadsheet_id cannot be empty")
1468
+
1469
+ if min_rows < 1:
1470
+ raise ValueError("min_rows must be at least 1")
1471
+
1472
+ if min_columns < 1:
1473
+ raise ValueError("min_columns must be at least 1")
1474
+
1475
+ if not 0 <= min_confidence <= 1:
1476
+ raise ValueError("min_confidence must be between 0.0 and 1.0")
1477
+
1478
+ # Get spreadsheet structure
1479
+ spreadsheet = self.get_spreadsheet(spreadsheet_id)
1480
+
1481
+ tables = []
1482
+
1483
+ for sheet in spreadsheet.get("sheets", []):
1484
+ sheet_properties = sheet.get("properties", {})
1485
+ sheet_id = sheet_properties.get("sheetId")
1486
+ sheet_title = sheet_properties.get("title", "Sheet1")
1487
+
1488
+ # Analyze sheet for tables using helper function
1489
+ sheet_tables = analyze_sheet_for_tables(
1490
+ self.get_values, # Pass the get_values method as a function
1491
+ spreadsheet_id,
1492
+ sheet_id,
1493
+ sheet_title,
1494
+ min_rows,
1495
+ min_columns,
1496
+ min_confidence,
1497
+ )
1498
+
1499
+ tables.extend(sheet_tables)
1500
+
1501
+ return {
1502
+ "spreadsheet_id": spreadsheet_id,
1503
+ "total_tables": len(tables),
1504
+ "tables": tables,
1505
+ "analysis_parameters": {
1506
+ "min_rows": min_rows,
1507
+ "min_columns": min_columns,
1508
+ },
1509
+ }
1510
+
1511
+ def get_table_schema(
1512
+ self,
1513
+ spreadsheet_id: str,
1514
+ table_name: str,
1515
+ sheet_name: str | None = None,
1516
+ sample_size: int = 50,
1517
+ ) -> dict[str, Any]:
1518
+ """
1519
+ Analyzes table structure and infers column names, types, and constraints.
1520
+ Uses statistical analysis of sample data to determine the most likely data type for each column.
1521
+ Call this action after calling the list tables action to get the schema of a table in a spreadsheet.
1522
+
1523
+ Args:
1524
+ spreadsheet_id: Google Sheets ID from the URL (e.g., '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms')
1525
+ table_name: Specific table name from LIST_TABLES response (e.g., 'Sales_Data', 'Employee_List'). Use 'auto' to analyze the largest/most prominent table
1526
+ sheet_name: Sheet/tab name if table_name is ambiguous across multiple sheets
1527
+ sample_size: Number of rows to sample for type inference (1-1000, default 50)
1528
+
1529
+ Returns:
1530
+ A dictionary containing the table schema with column names, types, and constraints
1531
+
1532
+ Raises:
1533
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1534
+ ValueError: When spreadsheet_id is empty, table_name is empty, or sample_size is invalid
1535
+
1536
+ Tags:
1537
+ schema, analyze, table, structure, types, columns
1538
+ """
1539
+ if not spreadsheet_id:
1540
+ raise ValueError("spreadsheet_id cannot be empty")
1541
+
1542
+ if not table_name:
1543
+ raise ValueError("table_name cannot be empty")
1544
+
1545
+ if not 1 <= sample_size <= 1000:
1546
+ raise ValueError("sample_size must be between 1 and 1000")
1547
+
1548
+ # Get spreadsheet structure
1549
+ spreadsheet = self.get_spreadsheet(spreadsheet_id)
1550
+
1551
+ # Find the target table
1552
+ target_table = None
1553
+
1554
+ for sheet in spreadsheet.get("sheets", []):
1555
+ sheet_properties = sheet.get("properties", {})
1556
+ sheet_title = sheet_properties.get("title", "Sheet1")
1557
+
1558
+ # If sheet_name is specified, only look in that sheet
1559
+ if sheet_name and sheet_title != sheet_name:
1560
+ continue
1561
+
1562
+ # Get tables in this sheet
1563
+ sheet_tables = analyze_sheet_for_tables(
1564
+ self.get_values,
1565
+ spreadsheet_id,
1566
+ sheet_properties.get("sheetId", 0),
1567
+ sheet_title,
1568
+ min_rows=2,
1569
+ min_columns=1,
1570
+ min_confidence=0.3,
1571
+ )
1572
+
1573
+ for table in sheet_tables:
1574
+ if table_name == "auto":
1575
+ # For auto mode, select the largest table
1576
+ if target_table is None or (
1577
+ table["rows"] * table["columns"]
1578
+ > target_table["rows"] * target_table["columns"]
1579
+ ):
1580
+ target_table = table
1581
+ elif table["table_name"] == table_name:
1582
+ target_table = table
1583
+ break
1584
+
1585
+ if target_table and table_name != "auto":
1586
+ break
1587
+
1588
+ if not target_table:
1589
+ raise ValueError(f"Table '{table_name}' not found in spreadsheet")
1590
+
1591
+ # Use the helper function to analyze the table schema
1592
+ return analyze_table_schema(
1593
+ self.get_values, spreadsheet_id, target_table, sample_size
1594
+ )
1595
+
1596
+ def set_basic_filter(
1597
+ self,
1598
+ spreadsheet_id: str,
1599
+ filter: dict,
1600
+ ) -> dict[str, Any]:
1601
+ """
1602
+ Tool to set a basic filter on a sheet in a google spreadsheet. use when you need to filter or sort data within a specific range on a sheet.
1603
+
1604
+ Args:
1605
+ spreadsheet_id: The ID of the spreadsheet. Example: "abc123xyz789"
1606
+ filter: The filter to set. This parameter is required. Contains:
1607
+ - range: The range the filter covers (required)
1608
+ - sheetId: The sheet this range is on (required)
1609
+ - startRowIndex: The start row (inclusive) of the range (optional)
1610
+ - endRowIndex: The end row (exclusive) of the range (optional)
1611
+ - startColumnIndex: The start column (inclusive) of the range (optional)
1612
+ - endColumnIndex: The end column (exclusive) of the range (optional)
1613
+ - sortSpecs: The sort specifications for the filter (optional)
1614
+ - dimensionIndex: The dimension the sort should be applied to
1615
+ - sortOrder: The order data should be sorted ("ASCENDING", "DESCENDING", "SORT_ORDER_UNSPECIFIED")
1616
+
1617
+ Returns:
1618
+ A dictionary containing the Google Sheets API response with filter details
1619
+
1620
+ Raises:
1621
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1622
+ ValueError: When spreadsheet_id is empty or filter is missing required fields
1623
+
1624
+ Tags:
1625
+ filter, basic-filter, spreadsheet, sort, important
1626
+ """
1627
+ if not spreadsheet_id:
1628
+ raise ValueError("spreadsheet_id cannot be empty")
1629
+
1630
+ if not filter:
1631
+ raise ValueError("filter cannot be empty")
1632
+
1633
+ # Validate required filter fields
1634
+ if "range" not in filter:
1635
+ raise ValueError("filter must contain 'range' field")
1636
+
1637
+ # Validate required filter fields using Google API naming convention
1638
+ range_data = filter["range"]
1639
+ if "sheetId" not in range_data:
1640
+ raise ValueError("filter range must contain 'sheetId' field")
1641
+
1642
+ url = f"{self.base_url}/{spreadsheet_id}:batchUpdate"
1643
+
1644
+ request_body = {"requests": [{"setBasicFilter": {"filter": filter}}]}
1645
+
1646
+ response = self._post(url, data=request_body)
1647
+ return self._handle_response(response)
1648
+
1649
+ def format_cells(
1650
+ self,
1651
+ spreadsheetId: str,
1652
+ worksheetId: int,
1653
+ startRowIndex: int,
1654
+ startColumnIndex: int,
1655
+ endRowIndex: int,
1656
+ endColumnIndex: int,
1657
+ # Text formatting
1658
+ bold: bool | None = None,
1659
+ italic: bool | None = None,
1660
+ underline: bool | None = None,
1661
+ strikethrough: bool | None = None,
1662
+ fontSize: int | None = None,
1663
+ fontFamily: str | None = None,
1664
+ # Colors
1665
+ backgroundRed: float | None = None,
1666
+ backgroundGreen: float | None = None,
1667
+ backgroundBlue: float | None = None,
1668
+ textRed: float | None = None,
1669
+ textGreen: float | None = None,
1670
+ textBlue: float | None = None,
1671
+ # Alignment
1672
+ horizontalAlignment: str | None = None, # "LEFT", "CENTER", "RIGHT"
1673
+ verticalAlignment: str | None = None, # "TOP", "MIDDLE", "BOTTOM"
1674
+ # Text wrapping
1675
+ wrapStrategy: str
1676
+ | None = None, # "OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", "WRAP"
1677
+ # Number format
1678
+ numberFormat: str | None = None,
1679
+ # Borders
1680
+ borderTop: dict | None = None,
1681
+ borderBottom: dict | None = None,
1682
+ borderLeft: dict | None = None,
1683
+ borderRight: dict | None = None,
1684
+ # Merge cells
1685
+ mergeCells: bool = False,
1686
+ ) -> dict[str, Any]:
1687
+ """
1688
+ Applies comprehensive cell formatting to a specified range in a Google Sheets worksheet.
1689
+ Supports background colors, text colors, borders, text formatting, font properties,
1690
+ alignment, number formats, text wrapping, and cell merging.
1691
+
1692
+ Args:
1693
+ spreadsheetId: Identifier of the Google Sheets spreadsheet. Example: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
1694
+ worksheetId: ID (sheetId) of the worksheet. Use `get_spreadsheet` to find this ID. Example: 123456789
1695
+ startRowIndex: 0-based index of the first row in the range. Example: 0
1696
+ startColumnIndex: 0-based index of the first column in the range. Example: 0
1697
+ endRowIndex: 0-based index of the row *after* the last row in the range (exclusive). Example: 1
1698
+ endColumnIndex: 0-based index of the column *after* the last column in the range (exclusive). Example: 2
1699
+
1700
+
1701
+ bold: Apply bold formatting. Example: True
1702
+ italic: Apply italic formatting. Example: False
1703
+ underline: Apply underline formatting. Example: False
1704
+ strikethrough: Apply strikethrough formatting. Example: False
1705
+ fontSize: Font size in points. Example: 12
1706
+ fontFamily: Font family name. Example: "Arial", "Times New Roman"
1707
+
1708
+ backgroundRed: Red component of background color. Example: 0.9
1709
+ backgroundGreen: Green component of background color. Example: 0.9
1710
+ backgroundBlue: Blue component of background color. Example: 0.9
1711
+
1712
+ textRed: Red component of text color. Example: 0.0
1713
+ textGreen: Green component of text color. Example: 0.0
1714
+ textBlue: Blue component of text color. Example: 0.0
1715
+
1716
+ horizontalAlignment: "LEFT", "CENTER", or "RIGHT". Example: "CENTER"
1717
+ verticalAlignment: "TOP", "MIDDLE", or "BOTTOM". Example: "MIDDLE"
1718
+
1719
+ wrapStrategy: "OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", or "WRAP". Example: "WRAP"
1720
+
1721
+ numberFormat: Number format string. Example: "#,##0.00", "0.00%", "$#,##0.00"
1722
+
1723
+ borderTop: Top border settings. Example: {"style": "SOLID", "color": {"red": 0, "green": 0, "blue": 0}}
1724
+ borderBottom: Bottom border settings. Example: {"style": "SOLID", "color": {"red": 0, "green": 0, "blue": 0}}
1725
+ borderLeft: Left border settings. Example: {"style": "SOLID", "color": {"red": 0, "green": 0, "blue": 0}}
1726
+ borderRight: Right border settings. Example: {"style": "SOLID", "color": {"red": 0, "green": 0, "blue": 0}}
1727
+
1728
+ mergeCells: Whether to merge the specified range into a single cell. Example: True
1729
+
1730
+ Returns:
1731
+ A dictionary containing the Google Sheets API response with formatting details
1732
+
1733
+ Raises:
1734
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1735
+ ValueError: When spreadsheet_id is empty, indices are invalid, or color values are out of range
1736
+
1737
+ Tags:
1738
+ format, cells, styling, text-formatting, background-color, borders, alignment, merge
1739
+ """
1740
+ if not spreadsheetId:
1741
+ raise ValueError("spreadsheetId cannot be empty")
1742
+
1743
+ if worksheetId < 0:
1744
+ raise ValueError("worksheetId must be non-negative")
1745
+
1746
+ if (
1747
+ startRowIndex < 0
1748
+ or startColumnIndex < 0
1749
+ or endRowIndex < 0
1750
+ or endColumnIndex < 0
1751
+ ):
1752
+ raise ValueError("All indices must be non-negative")
1753
+
1754
+ if startRowIndex >= endRowIndex:
1755
+ raise ValueError("endRowIndex must be greater than startRowIndex")
1756
+
1757
+ if startColumnIndex >= endColumnIndex:
1758
+ raise ValueError("endColumnIndex must be greater than startColumnIndex")
1759
+
1760
+ # Validate color values if provided
1761
+ for color_name, color_value in [
1762
+ ("backgroundRed", backgroundRed),
1763
+ ("backgroundGreen", backgroundGreen),
1764
+ ("backgroundBlue", backgroundBlue),
1765
+ ("textRed", textRed),
1766
+ ("textGreen", textGreen),
1767
+ ("textBlue", textBlue),
1768
+ ]:
1769
+ if color_value is not None and not 0 <= color_value <= 1:
1770
+ raise ValueError(f"{color_name} must be between 0.0 and 1.0")
1771
+
1772
+ if fontSize is not None and fontSize <= 0:
1773
+ raise ValueError("fontSize must be positive")
1774
+
1775
+ if horizontalAlignment and horizontalAlignment not in [
1776
+ "LEFT",
1777
+ "CENTER",
1778
+ "RIGHT",
1779
+ ]:
1780
+ raise ValueError('horizontalAlignment must be "LEFT", "CENTER", or "RIGHT"')
1781
+
1782
+ if verticalAlignment and verticalAlignment not in ["TOP", "MIDDLE", "BOTTOM"]:
1783
+ raise ValueError('verticalAlignment must be "TOP", "MIDDLE", or "BOTTOM"')
1784
+
1785
+ if wrapStrategy and wrapStrategy not in [
1786
+ "OVERFLOW_CELL",
1787
+ "LEGACY_WRAP",
1788
+ "CLIP",
1789
+ "WRAP",
1790
+ ]:
1791
+ raise ValueError(
1792
+ 'wrapStrategy must be "OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", or "WRAP"'
1793
+ )
1794
+
1795
+ url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1796
+
1797
+ requests = []
1798
+
1799
+ # Handle cell merging first if requested
1800
+ if mergeCells:
1801
+ requests.append(
1802
+ {
1803
+ "mergeCells": {
1804
+ "range": {
1805
+ "sheetId": worksheetId,
1806
+ "startRowIndex": startRowIndex,
1807
+ "endRowIndex": endRowIndex,
1808
+ "startColumnIndex": startColumnIndex,
1809
+ "endColumnIndex": endColumnIndex,
1810
+ },
1811
+ "mergeType": "MERGE_ALL",
1812
+ }
1813
+ }
1814
+ )
1815
+
1816
+ # Build the cell format request
1817
+ cell_format = {}
1818
+
1819
+ # Text format
1820
+ text_format = {}
1821
+ if bold is not None:
1822
+ text_format["bold"] = bold
1823
+ if italic is not None:
1824
+ text_format["italic"] = italic
1825
+ if underline is not None:
1826
+ text_format["underline"] = underline
1827
+ if strikethrough is not None:
1828
+ text_format["strikethrough"] = strikethrough
1829
+ if fontSize is not None:
1830
+ text_format["fontSize"] = fontSize
1831
+ if fontFamily is not None:
1832
+ text_format["fontFamily"] = fontFamily
1833
+
1834
+ # Text color
1835
+ if any(color is not None for color in [textRed, textGreen, textBlue]):
1836
+ text_color = {}
1837
+ if textRed is not None:
1838
+ text_color["red"] = textRed
1839
+ if textGreen is not None:
1840
+ text_color["green"] = textGreen
1841
+ if textBlue is not None:
1842
+ text_color["blue"] = textBlue
1843
+ if text_color:
1844
+ text_format["foregroundColor"] = {"rgbColor": text_color}
1845
+
1846
+ if text_format:
1847
+ cell_format["textFormat"] = text_format
1848
+
1849
+ # Background color
1850
+ if any(
1851
+ color is not None
1852
+ for color in [backgroundRed, backgroundGreen, backgroundBlue]
1853
+ ):
1854
+ background_color = {}
1855
+ if backgroundRed is not None:
1856
+ background_color["red"] = backgroundRed
1857
+ if backgroundGreen is not None:
1858
+ background_color["green"] = backgroundGreen
1859
+ if backgroundBlue is not None:
1860
+ background_color["blue"] = backgroundBlue
1861
+ if background_color:
1862
+ cell_format["backgroundColorStyle"] = {"rgbColor": background_color}
1863
+
1864
+ # Alignment
1865
+ if horizontalAlignment or verticalAlignment:
1866
+ cell_format["horizontalAlignment"] = horizontalAlignment
1867
+ cell_format["verticalAlignment"] = verticalAlignment
1868
+
1869
+ # Text wrapping
1870
+ if wrapStrategy:
1871
+ cell_format["wrapStrategy"] = wrapStrategy
1872
+
1873
+ # Number format
1874
+ if numberFormat:
1875
+ cell_format["numberFormat"] = {"type": "TEXT", "pattern": numberFormat}
1876
+
1877
+ # Borders
1878
+ borders = {}
1879
+ for border_side, border_config in [
1880
+ ("top", borderTop),
1881
+ ("bottom", borderBottom),
1882
+ ("left", borderLeft),
1883
+ ("right", borderRight),
1884
+ ]:
1885
+ if border_config:
1886
+ borders[border_side] = border_config
1887
+
1888
+ if borders:
1889
+ cell_format["borders"] = borders
1890
+
1891
+ # Add cell formatting request if any formatting is specified
1892
+ if cell_format:
1893
+ requests.append(
1894
+ {
1895
+ "repeatCell": {
1896
+ "range": {
1897
+ "sheetId": worksheetId,
1898
+ "startRowIndex": startRowIndex,
1899
+ "endRowIndex": endRowIndex,
1900
+ "startColumnIndex": startColumnIndex,
1901
+ "endColumnIndex": endColumnIndex,
1902
+ },
1903
+ "cell": {"userEnteredFormat": cell_format},
1904
+ "fields": "userEnteredFormat",
1905
+ }
1906
+ }
1907
+ )
1908
+
1909
+ request_body = {"requests": requests}
1910
+ response = self._post(url, data=request_body)
1911
+ return self._handle_response(response)
1912
+
1913
+ def list_tools(self):
1914
+ """Returns a list of methods exposed as tools."""
1915
+ return [
1916
+ self.create_spreadsheet,
1917
+ self.get_spreadsheet,
1918
+ self.batch_get_values,
1919
+ self.insert_dimensions,
1920
+ self.append_dimensions,
1921
+ self.delete_dimensions,
1922
+ self.add_sheet,
1923
+ self.delete_sheet,
1924
+ self.add_basic_chart,
1925
+ self.add_pie_chart,
1926
+ self.add_table,
1927
+ self.update_table,
1928
+ self.clear_values,
1929
+ self.update_values,
1930
+ self.batch_update,
1931
+ self.clear_basic_filter,
1932
+ self.list_tables,
1933
+ self.get_values,
1934
+ self.get_table_schema,
1935
+ self.set_basic_filter,
1936
+ self.copy_to_sheet,
1937
+ self.append_values,
1938
+ self.batch_clear_values,
1939
+ self.batch_get_values_by_data_filter,
1940
+ self.format_cells,
1941
+ ]