universal-mcp-applications 0.1.30rc2__py3-none-any.whl → 0.1.36rc2__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 (105) hide show
  1. universal_mcp/applications/ahrefs/app.py +52 -198
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +111 -464
  4. universal_mcp/applications/asana/app.py +417 -1567
  5. universal_mcp/applications/aws_s3/app.py +36 -103
  6. universal_mcp/applications/bill/app.py +546 -1957
  7. universal_mcp/applications/box/app.py +1068 -3981
  8. universal_mcp/applications/braze/app.py +364 -1430
  9. universal_mcp/applications/browser_use/app.py +2 -8
  10. universal_mcp/applications/cal_com_v2/app.py +207 -625
  11. universal_mcp/applications/calendly/app.py +61 -200
  12. universal_mcp/applications/canva/app.py +45 -110
  13. universal_mcp/applications/clickup/app.py +207 -674
  14. universal_mcp/applications/coda/app.py +146 -426
  15. universal_mcp/applications/confluence/app.py +310 -1098
  16. universal_mcp/applications/contentful/app.py +36 -151
  17. universal_mcp/applications/crustdata/app.py +28 -107
  18. universal_mcp/applications/dialpad/app.py +283 -756
  19. universal_mcp/applications/digitalocean/app.py +1766 -5777
  20. universal_mcp/applications/domain_checker/app.py +3 -54
  21. universal_mcp/applications/e2b/app.py +14 -64
  22. universal_mcp/applications/elevenlabs/app.py +9 -47
  23. universal_mcp/applications/exa/app.py +6 -17
  24. universal_mcp/applications/falai/app.py +24 -101
  25. universal_mcp/applications/figma/app.py +53 -137
  26. universal_mcp/applications/file_system/app.py +2 -13
  27. universal_mcp/applications/firecrawl/app.py +51 -152
  28. universal_mcp/applications/fireflies/app.py +59 -281
  29. universal_mcp/applications/fpl/app.py +91 -528
  30. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  31. universal_mcp/applications/fpl/utils/helper.py +25 -89
  32. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  33. universal_mcp/applications/ghost_content/app.py +52 -161
  34. universal_mcp/applications/github/app.py +19 -56
  35. universal_mcp/applications/gong/app.py +88 -248
  36. universal_mcp/applications/google_calendar/app.py +16 -68
  37. universal_mcp/applications/google_docs/app.py +88 -188
  38. universal_mcp/applications/google_drive/app.py +141 -463
  39. universal_mcp/applications/google_gemini/app.py +12 -64
  40. universal_mcp/applications/google_mail/app.py +28 -157
  41. universal_mcp/applications/google_searchconsole/app.py +15 -48
  42. universal_mcp/applications/google_sheet/app.py +103 -580
  43. universal_mcp/applications/google_sheet/helper.py +10 -37
  44. universal_mcp/applications/hashnode/app.py +57 -269
  45. universal_mcp/applications/heygen/app.py +44 -122
  46. universal_mcp/applications/http_tools/app.py +10 -32
  47. universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
  48. universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
  49. universal_mcp/applications/hubspot/app.py +23 -87
  50. universal_mcp/applications/jira/app.py +2071 -7986
  51. universal_mcp/applications/klaviyo/app.py +494 -1376
  52. universal_mcp/applications/linkedin/README.md +9 -2
  53. universal_mcp/applications/linkedin/app.py +392 -212
  54. universal_mcp/applications/mailchimp/app.py +450 -1605
  55. universal_mcp/applications/markitdown/app.py +8 -20
  56. universal_mcp/applications/miro/app.py +217 -699
  57. universal_mcp/applications/ms_teams/app.py +64 -186
  58. universal_mcp/applications/neon/app.py +86 -192
  59. universal_mcp/applications/notion/app.py +21 -36
  60. universal_mcp/applications/onedrive/app.py +16 -38
  61. universal_mcp/applications/openai/app.py +42 -165
  62. universal_mcp/applications/outlook/app.py +24 -84
  63. universal_mcp/applications/perplexity/app.py +4 -19
  64. universal_mcp/applications/pipedrive/app.py +832 -3142
  65. universal_mcp/applications/posthog/app.py +163 -432
  66. universal_mcp/applications/reddit/app.py +40 -139
  67. universal_mcp/applications/resend/app.py +41 -107
  68. universal_mcp/applications/retell/app.py +14 -41
  69. universal_mcp/applications/rocketlane/app.py +221 -934
  70. universal_mcp/applications/scraper/README.md +7 -4
  71. universal_mcp/applications/scraper/app.py +216 -102
  72. universal_mcp/applications/semanticscholar/app.py +22 -64
  73. universal_mcp/applications/semrush/app.py +43 -77
  74. universal_mcp/applications/sendgrid/app.py +512 -1262
  75. universal_mcp/applications/sentry/app.py +271 -906
  76. universal_mcp/applications/serpapi/app.py +40 -143
  77. universal_mcp/applications/sharepoint/app.py +17 -39
  78. universal_mcp/applications/shopify/app.py +1551 -4287
  79. universal_mcp/applications/shortcut/app.py +155 -417
  80. universal_mcp/applications/slack/app.py +50 -101
  81. universal_mcp/applications/spotify/app.py +126 -325
  82. universal_mcp/applications/supabase/app.py +104 -213
  83. universal_mcp/applications/tavily/app.py +1 -1
  84. universal_mcp/applications/trello/app.py +693 -2656
  85. universal_mcp/applications/twilio/app.py +14 -50
  86. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  87. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  88. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  89. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  90. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  91. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  92. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  93. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  94. universal_mcp/applications/whatsapp/app.py +35 -186
  95. universal_mcp/applications/whatsapp/audio.py +2 -6
  96. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  97. universal_mcp/applications/whatsapp_business/app.py +70 -283
  98. universal_mcp/applications/wrike/app.py +45 -118
  99. universal_mcp/applications/yahoo_finance/app.py +19 -65
  100. universal_mcp/applications/youtube/app.py +75 -261
  101. universal_mcp/applications/zenquotes/app.py +2 -2
  102. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
  104. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
  105. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,7 @@
1
1
  from typing import Any
2
-
3
2
  from universal_mcp.applications.application import APIApplication
4
3
  from universal_mcp.integrations import Integration
5
-
6
- from universal_mcp.applications.google_sheet.helper import (
7
- analyze_sheet_for_tables,
8
- analyze_table_schema,
9
- )
4
+ from universal_mcp.applications.google_sheet.helper import analyze_sheet_for_tables, analyze_table_schema
10
5
 
11
6
 
12
7
  class GoogleSheetApp(APIApplication):
@@ -19,9 +14,9 @@ class GoogleSheetApp(APIApplication):
19
14
  super().__init__(name="google_sheet", integration=integration)
20
15
  self.base_url = "https://sheets.googleapis.com/v4/spreadsheets"
21
16
 
22
- def create_spreadsheet(self, title: str) -> dict[str, Any]:
17
+ async def create_spreadsheet(self, title: str) -> dict[str, Any]:
23
18
  """
24
- Creates a new, blank Google Spreadsheet file with a specified title. This function generates a completely new document, unlike `add_sheet` which adds a worksheet (tab) to an existing spreadsheet. It returns the API response containing the new spreadsheet's metadata.
19
+ Creates a new, blank Google Spreadsheet file with a specified title. This function generates a completely new document, unlike `add_sheet` which adds a worksheet (tab) to an existing spreadsheet. It returns the API response containing the new spreadsheet's metadata. Note that you need to call other google_sheet functions (e.g. `google_sheet__write_values_to_sheet`) to actually add content after creating the spreadsheet.
25
20
 
26
21
  Args:
27
22
  title: String representing the desired title for the new spreadsheet
@@ -39,9 +34,11 @@ class GoogleSheetApp(APIApplication):
39
34
  url = self.base_url
40
35
  spreadsheet_data = {"properties": {"title": title}}
41
36
  response = self._post(url, data=spreadsheet_data)
42
- return self._handle_response(response)
37
+ payload = self._handle_response(response)
38
+ payload["Note"] = "You must load and call other google_sheet content functions (like `google_sheet__write_values_to_sheet`)"
39
+ return payload
43
40
 
44
- def get_spreadsheet_metadata(self, spreadsheetId: str) -> dict[str, Any]:
41
+ async def get_spreadsheet_metadata(self, spreadsheetId: str) -> dict[str, Any]:
45
42
  """
46
43
  Retrieves a spreadsheet's metadata and structural properties, such as sheet names, IDs, and named ranges, using its unique ID. This function intentionally excludes cell data, distinguishing it from `get_values` which fetches the actual content within cells.
47
44
 
@@ -63,7 +60,7 @@ class GoogleSheetApp(APIApplication):
63
60
  response = self._get(url)
64
61
  return self._handle_response(response)
65
62
 
66
- def get_values(
63
+ async def get_values(
67
64
  self,
68
65
  spreadsheetId: str,
69
66
  range: str,
@@ -93,20 +90,16 @@ class GoogleSheetApp(APIApplication):
93
90
  """
94
91
  url = f"{self.base_url}/{spreadsheetId}/values/{range}"
95
92
  params = {}
96
-
97
93
  if majorDimension:
98
94
  params["majorDimension"] = majorDimension
99
95
  if valueRenderOption:
100
96
  params["valueRenderOption"] = valueRenderOption
101
97
  if dateTimeRenderOption:
102
98
  params["dateTimeRenderOption"] = dateTimeRenderOption
103
-
104
99
  response = self._get(url, params=params)
105
100
  return self._handle_response(response)
106
101
 
107
- def batch_get_values_by_range(
108
- self, spreadsheetId: str, ranges: list[str] | None = None
109
- ) -> dict[str, Any]:
102
+ async def batch_get_values_by_range(self, spreadsheetId: str, ranges: list[str] | None = None) -> dict[str, Any]:
110
103
  """
111
104
  Efficiently retrieves values from multiple predefined A1 notation ranges in a single API request. Unlike `get_values`, which fetches a single range, or `batch_get_values_by_data_filter`, which uses dynamic filtering criteria, this function operates on a simple list of range strings for bulk data retrieval.
112
105
 
@@ -131,7 +124,7 @@ class GoogleSheetApp(APIApplication):
131
124
  response = self._get(url, params=params)
132
125
  return self._handle_response(response)
133
126
 
134
- def insert_dimensions(
127
+ async def insert_dimensions(
135
128
  self,
136
129
  spreadsheetId: str,
137
130
  sheet_id: int,
@@ -172,56 +165,33 @@ class GoogleSheetApp(APIApplication):
172
165
  """
173
166
  if not spreadsheetId:
174
167
  raise ValueError("spreadsheetId cannot be empty")
175
-
176
168
  if dimension not in ["ROWS", "COLUMNS"]:
177
169
  raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
178
-
179
170
  if start_index < 0 or end_index < 0:
180
171
  raise ValueError("start_index and end_index must be non-negative")
181
-
182
172
  if start_index >= end_index:
183
173
  raise ValueError("end_index must be greater than start_index")
184
-
185
174
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
186
-
187
175
  request_body: dict[str, Any] = {
188
176
  "requests": [
189
177
  {
190
178
  "insertDimension": {
191
179
  "inheritFromBefore": inherit_from_before,
192
- "range": {
193
- "dimension": dimension,
194
- "sheetId": sheet_id,
195
- "startIndex": start_index,
196
- "endIndex": end_index,
197
- },
180
+ "range": {"dimension": dimension, "sheetId": sheet_id, "startIndex": start_index, "endIndex": end_index},
198
181
  }
199
182
  }
200
183
  ]
201
184
  }
202
-
203
- # Add optional parameters if provided
204
185
  if include_spreadsheet_in_response is not None:
205
- request_body["includeSpreadsheetInResponse"] = (
206
- include_spreadsheet_in_response
207
- )
208
-
186
+ request_body["includeSpreadsheetInResponse"] = include_spreadsheet_in_response
209
187
  if response_include_grid_data is not None:
210
188
  request_body["responseIncludeGridData"] = response_include_grid_data
211
-
212
189
  if response_ranges is not None:
213
190
  request_body["responseRanges"] = response_ranges
214
-
215
191
  response = self._post(url, data=request_body)
216
192
  return self._handle_response(response)
217
193
 
218
- def append_dimensions(
219
- self,
220
- spreadsheetId: str,
221
- sheet_id: int,
222
- dimension: str,
223
- length: int,
224
- ) -> dict[str, Any]:
194
+ async def append_dimensions(self, spreadsheetId: str, sheet_id: int, dimension: str, length: int) -> dict[str, Any]:
225
195
  """
226
196
  Adds a specified number of empty rows or columns to the end of a designated sheet. Unlike `insert_dimensions`, which adds space at a specific index, this function exclusively extends the sheet's boundaries at the bottom or to the right without affecting existing content.
227
197
 
@@ -246,31 +216,16 @@ class GoogleSheetApp(APIApplication):
246
216
  """
247
217
  if not spreadsheetId:
248
218
  raise ValueError("spreadsheetId cannot be empty")
249
-
250
219
  if dimension not in ["ROWS", "COLUMNS"]:
251
220
  raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
252
-
253
221
  if length <= 0:
254
222
  raise ValueError("length must be a positive integer")
255
-
256
223
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
257
-
258
- request_body = {
259
- "requests": [
260
- {
261
- "appendDimension": {
262
- "sheetId": sheet_id,
263
- "dimension": dimension,
264
- "length": length,
265
- }
266
- }
267
- ]
268
- }
269
-
224
+ request_body = {"requests": [{"appendDimension": {"sheetId": sheet_id, "dimension": dimension, "length": length}}]}
270
225
  response = self._post(url, data=request_body)
271
226
  return self._handle_response(response)
272
227
 
273
- def delete_dimensions(
228
+ async def delete_dimensions(
274
229
  self,
275
230
  spreadsheetId: str,
276
231
  sheet_id: int,
@@ -306,49 +261,32 @@ class GoogleSheetApp(APIApplication):
306
261
  """
307
262
  if not spreadsheetId:
308
263
  raise ValueError("spreadsheetId cannot be empty")
309
-
310
264
  if dimension not in ["ROWS", "COLUMNS"]:
311
265
  raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
312
-
313
266
  if start_index < 0 or end_index < 0:
314
267
  raise ValueError("start_index and end_index must be non-negative")
315
-
316
268
  if start_index >= end_index:
317
269
  raise ValueError("end_index must be greater than start_index")
318
-
319
270
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
320
-
321
271
  request_body: dict[str, Any] = {
322
272
  "requests": [
323
273
  {
324
274
  "deleteDimension": {
325
- "range": {
326
- "sheetId": sheet_id,
327
- "dimension": dimension,
328
- "startIndex": start_index,
329
- "endIndex": end_index,
330
- }
275
+ "range": {"sheetId": sheet_id, "dimension": dimension, "startIndex": start_index, "endIndex": end_index}
331
276
  }
332
277
  }
333
278
  ]
334
279
  }
335
-
336
- # Add optional response parameters if provided
337
280
  if include_spreadsheet_in_response is not None:
338
- request_body["includeSpreadsheetInResponse"] = (
339
- include_spreadsheet_in_response
340
- )
341
-
281
+ request_body["includeSpreadsheetInResponse"] = include_spreadsheet_in_response
342
282
  if response_include_grid_data is not None:
343
283
  request_body["responseIncludeGridData"] = response_include_grid_data
344
-
345
284
  if response_ranges is not None:
346
285
  request_body["responseRanges"] = response_ranges
347
-
348
286
  response = self._post(url, data=request_body)
349
287
  return self._handle_response(response)
350
288
 
351
- def add_sheet(
289
+ async def add_sheet(
352
290
  self,
353
291
  spreadsheetId: str,
354
292
  title: str | None = None,
@@ -358,7 +296,6 @@ class GoogleSheetApp(APIApplication):
358
296
  hidden: bool | None = None,
359
297
  rightToLeft: bool | None = None,
360
298
  tabColorStyle: dict | None = None,
361
- # Grid properties
362
299
  rowCount: int | None = None,
363
300
  columnCount: int | None = None,
364
301
  frozenRowCount: int | None = None,
@@ -366,7 +303,6 @@ class GoogleSheetApp(APIApplication):
366
303
  hideGridlines: bool | None = None,
367
304
  rowGroupControlAfter: bool | None = None,
368
305
  columnGroupControlAfter: bool | None = None,
369
- # Response options
370
306
  includeSpreadsheetInResponse: bool = False,
371
307
  responseIncludeGridData: bool = False,
372
308
  ) -> dict[str, Any]:
@@ -404,80 +340,61 @@ class GoogleSheetApp(APIApplication):
404
340
  """
405
341
  if not spreadsheetId:
406
342
  raise ValueError("spreadsheetId cannot be empty")
407
-
408
343
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
409
-
410
- # Build the addSheet request with properties
411
344
  add_sheet_request = {"properties": {}}
412
-
413
345
  if title is not None:
414
346
  add_sheet_request["properties"]["title"] = title
415
-
416
347
  if sheetId is not None:
417
348
  add_sheet_request["properties"]["sheetId"] = sheetId
418
-
419
349
  if index is not None:
420
350
  add_sheet_request["properties"]["index"] = index
421
-
422
351
  if sheetType is not None:
423
352
  add_sheet_request["properties"]["sheetType"] = sheetType
424
-
425
353
  if hidden is not None:
426
354
  add_sheet_request["properties"]["hidden"] = hidden
427
-
428
355
  if rightToLeft is not None:
429
356
  add_sheet_request["properties"]["rightToLeft"] = rightToLeft
430
-
431
357
  if tabColorStyle is not None:
432
358
  add_sheet_request["properties"]["tabColorStyle"] = tabColorStyle
433
-
434
- # Build grid properties if any grid-related parameters are provided
435
359
  grid_properties = {}
436
360
  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
- ]
361
+ (
362
+ param is not None
363
+ for param in [
364
+ rowCount,
365
+ columnCount,
366
+ frozenRowCount,
367
+ frozenColumnCount,
368
+ hideGridlines,
369
+ rowGroupControlAfter,
370
+ columnGroupControlAfter,
371
+ ]
372
+ )
447
373
  ):
448
374
  if rowCount is not None:
449
375
  grid_properties["rowCount"] = rowCount
450
-
451
376
  if columnCount is not None:
452
377
  grid_properties["columnCount"] = columnCount
453
-
454
378
  if frozenRowCount is not None:
455
379
  grid_properties["frozenRowCount"] = frozenRowCount
456
-
457
380
  if frozenColumnCount is not None:
458
381
  grid_properties["frozenColumnCount"] = frozenColumnCount
459
-
460
382
  if hideGridlines is not None:
461
383
  grid_properties["hideGridlines"] = hideGridlines
462
-
463
384
  if rowGroupControlAfter is not None:
464
385
  grid_properties["rowGroupControlAfter"] = rowGroupControlAfter
465
-
466
386
  if columnGroupControlAfter is not None:
467
387
  grid_properties["columnGroupControlAfter"] = columnGroupControlAfter
468
-
469
388
  add_sheet_request["properties"]["gridProperties"] = grid_properties
470
-
471
389
  request_body = {
472
390
  "requests": [{"addSheet": add_sheet_request}],
473
391
  "includeSpreadsheetInResponse": includeSpreadsheetInResponse,
474
392
  "responseIncludeGridData": responseIncludeGridData,
475
393
  }
476
-
477
394
  response = self._post(url, data=request_body)
478
395
  return self._handle_response(response)
479
396
 
480
- def add_basic_chart(
397
+ async def add_basic_chart(
481
398
  self,
482
399
  spreadsheetId: str,
483
400
  source_sheet_id: int,
@@ -520,27 +437,17 @@ class GoogleSheetApp(APIApplication):
520
437
  """
521
438
  if not spreadsheetId:
522
439
  raise ValueError("spreadsheetId cannot be empty")
523
-
524
440
  if not chart_title:
525
441
  raise ValueError("chart_title cannot be empty")
526
-
527
442
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
528
-
529
- # Build the chart specification
530
443
  chart_spec = {
531
444
  "title": chart_title,
532
445
  "basicChart": {
533
446
  "chartType": chart_type,
534
447
  "legendPosition": "BOTTOM_LEGEND",
535
448
  "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
- },
449
+ {"position": "BOTTOM_AXIS", "title": x_axis_title if x_axis_title else "Categories"},
450
+ {"position": "LEFT_AXIS", "title": y_axis_title if y_axis_title else "Values"},
544
451
  ],
545
452
  "domains": [
546
453
  {
@@ -549,18 +456,10 @@ class GoogleSheetApp(APIApplication):
549
456
  "sources": [
550
457
  {
551
458
  "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
- ),
459
+ "startRowIndex": domain_range.get("startRowIndex", 0),
460
+ "endRowIndex": domain_range.get("endRowIndex", 1),
461
+ "startColumnIndex": domain_range.get("startColumnIndex", 0),
462
+ "endColumnIndex": domain_range.get("endColumnIndex", 1),
564
463
  }
565
464
  ]
566
465
  }
@@ -571,8 +470,6 @@ class GoogleSheetApp(APIApplication):
571
470
  "headerCount": 1,
572
471
  },
573
472
  }
574
-
575
- # Add series data
576
473
  for series_range in series_ranges:
577
474
  series = {
578
475
  "series": {
@@ -582,9 +479,7 @@ class GoogleSheetApp(APIApplication):
582
479
  "sheetId": source_sheet_id,
583
480
  "startRowIndex": series_range.get("startRowIndex", 0),
584
481
  "endRowIndex": series_range.get("endRowIndex", 1),
585
- "startColumnIndex": series_range.get(
586
- "startColumnIndex", 0
587
- ),
482
+ "startColumnIndex": series_range.get("startColumnIndex", 0),
588
483
  "endColumnIndex": series_range.get("endColumnIndex", 1),
589
484
  }
590
485
  ]
@@ -593,40 +488,25 @@ class GoogleSheetApp(APIApplication):
593
488
  "targetAxis": "LEFT_AXIS",
594
489
  }
595
490
  chart_spec["basicChart"]["series"].append(series)
596
-
597
- # Build the position specification
598
491
  if new_sheet:
599
492
  position_spec = {"newSheet": True}
600
- # For existing sheet, use overlayPosition structure
601
493
  elif chart_position:
602
494
  position_spec = chart_position
603
495
  else:
604
- # Default positioning when placing in existing sheet
605
496
  position_spec = {
606
497
  "overlayPosition": {
607
- "anchorCell": {
608
- "sheetId": source_sheet_id,
609
- "rowIndex": 0,
610
- "columnIndex": 0,
611
- },
498
+ "anchorCell": {"sheetId": source_sheet_id, "rowIndex": 0, "columnIndex": 0},
612
499
  "offsetXPixels": 0,
613
500
  "offsetYPixels": 0,
614
501
  "widthPixels": 600,
615
502
  "heightPixels": 400,
616
503
  }
617
504
  }
618
-
619
- # Build the request body
620
- request_body = {
621
- "requests": [
622
- {"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}
623
- ]
624
- }
625
-
505
+ request_body = {"requests": [{"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}]}
626
506
  response = self._post(url, data=request_body)
627
507
  return self._handle_response(response)
628
508
 
629
- def add_pie_chart(
509
+ async def add_pie_chart(
630
510
  self,
631
511
  spreadsheetId: str,
632
512
  source_sheet_id: int,
@@ -665,16 +545,11 @@ class GoogleSheetApp(APIApplication):
665
545
  """
666
546
  if not spreadsheetId:
667
547
  raise ValueError("spreadsheetId cannot be empty")
668
-
669
548
  if not chart_title:
670
549
  raise ValueError("chart_title cannot be empty")
671
-
672
- if pie_hole is not None and not 0 <= pie_hole <= 1:
550
+ if pie_hole is not None and (not 0 <= pie_hole <= 1):
673
551
  raise ValueError("pie_hole must be between 0.0 and 1.0")
674
-
675
552
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
676
-
677
- # Build the pie chart specification
678
553
  pie_chart_spec = {
679
554
  "legendPosition": legend_position,
680
555
  "domain": {
@@ -697,55 +572,35 @@ class GoogleSheetApp(APIApplication):
697
572
  "sheetId": source_sheet_id,
698
573
  "startRowIndex": data_range.get("startRowIndex", 0),
699
574
  "endRowIndex": data_range.get("endRowIndex", 1),
700
- "startColumnIndex": data_range.get("startColumnIndex", 0)
701
- + 1,
575
+ "startColumnIndex": data_range.get("startColumnIndex", 0) + 1,
702
576
  "endColumnIndex": data_range.get("endColumnIndex", 2),
703
577
  }
704
578
  ]
705
579
  }
706
580
  },
707
581
  }
708
-
709
- # Add pie hole for donut chart if specified
710
582
  if pie_hole is not None:
711
583
  pie_chart_spec["pieHole"] = pie_hole
712
-
713
- # Build the chart specification
714
584
  chart_spec = {"title": chart_title, "pieChart": pie_chart_spec}
715
-
716
- # Build the position specification
717
585
  if new_sheet:
718
586
  position_spec = {"newSheet": True}
719
- # For existing sheet, use overlayPosition structure
720
587
  elif chart_position:
721
588
  position_spec = chart_position
722
589
  else:
723
- # Default positioning when placing in existing sheet
724
590
  position_spec = {
725
591
  "overlayPosition": {
726
- "anchorCell": {
727
- "sheetId": source_sheet_id,
728
- "rowIndex": 0,
729
- "columnIndex": 0,
730
- },
592
+ "anchorCell": {"sheetId": source_sheet_id, "rowIndex": 0, "columnIndex": 0},
731
593
  "offsetXPixels": 0,
732
594
  "offsetYPixels": 0,
733
595
  "widthPixels": 600,
734
596
  "heightPixels": 400,
735
597
  }
736
598
  }
737
-
738
- # Build the request body
739
- request_body = {
740
- "requests": [
741
- {"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}
742
- ]
743
- }
744
-
599
+ request_body = {"requests": [{"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}]}
745
600
  response = self._post(url, data=request_body)
746
601
  return self._handle_response(response)
747
602
 
748
- def add_table(
603
+ async def add_table(
749
604
  self,
750
605
  spreadsheetId: str,
751
606
  sheet_id: int,
@@ -785,51 +640,24 @@ class GoogleSheetApp(APIApplication):
785
640
  """
786
641
  if not spreadsheetId:
787
642
  raise ValueError("spreadsheetId cannot be empty")
788
-
789
643
  if not table_name:
790
644
  raise ValueError("table_name cannot be empty")
791
-
792
645
  if not table_id:
793
646
  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
- ):
647
+ if start_row_index < 0 or end_row_index < 0 or start_column_index < 0 or (end_column_index < 0):
801
648
  raise ValueError("All indices must be non-negative")
802
-
803
649
  if start_row_index >= end_row_index:
804
650
  raise ValueError("end_row_index must be greater than start_row_index")
805
-
806
651
  if start_column_index >= end_column_index:
807
652
  raise ValueError("end_column_index must be greater than start_column_index")
808
-
809
- # Validate column properties if provided
810
653
  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
- ]
654
+ valid_column_types = ["TEXT", "PERCENT", "DROPDOWN", "DOUBLE", "CURRENCY", "DATE", "TIME", "DATE_TIME"]
821
655
  for i, prop in enumerate(column_properties):
822
- if (
823
- "columnType" in prop
824
- and prop["columnType"] not in valid_column_types
825
- ):
656
+ if "columnType" in prop and prop["columnType"] not in valid_column_types:
826
657
  raise ValueError(
827
658
  f"Invalid column type '{prop['columnType']}' at index {i}. Valid types are: {', '.join(valid_column_types)}"
828
659
  )
829
-
830
660
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
831
-
832
- # Build the table specification
833
661
  table_spec = {
834
662
  "name": table_name,
835
663
  "tableId": table_id,
@@ -841,18 +669,13 @@ class GoogleSheetApp(APIApplication):
841
669
  "endRowIndex": end_row_index,
842
670
  },
843
671
  }
844
-
845
- # Add column properties if provided
846
672
  if column_properties:
847
673
  table_spec["columnProperties"] = column_properties
848
-
849
- # Build the request body
850
674
  request_body = {"requests": [{"addTable": {"table": table_spec}}]}
851
-
852
675
  response = self._post(url, data=request_body)
853
676
  return self._handle_response(response)
854
677
 
855
- def update_table(
678
+ async def update_table(
856
679
  self,
857
680
  spreadsheetId: str,
858
681
  table_id: str,
@@ -891,70 +714,33 @@ class GoogleSheetApp(APIApplication):
891
714
  """
892
715
  if not spreadsheetId:
893
716
  raise ValueError("spreadsheetId cannot be empty")
894
-
895
717
  if not table_id:
896
718
  raise ValueError("table_id cannot be empty")
897
-
898
- # Validate indices if provided
899
719
  if start_row_index is not None and start_row_index < 0:
900
720
  raise ValueError("start_row_index must be non-negative")
901
-
902
721
  if end_row_index is not None and end_row_index < 0:
903
722
  raise ValueError("end_row_index must be non-negative")
904
-
905
723
  if start_column_index is not None and start_column_index < 0:
906
724
  raise ValueError("start_column_index must be non-negative")
907
-
908
725
  if end_column_index is not None and end_column_index < 0:
909
726
  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
- ):
727
+ if start_row_index is not None and end_row_index is not None and (start_row_index >= end_row_index):
916
728
  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
- ):
729
+ if start_column_index is not None and end_column_index is not None and (start_column_index >= end_column_index):
923
730
  raise ValueError("end_column_index must be greater than start_column_index")
924
-
925
- # Validate column properties if provided
926
731
  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
- ]
732
+ valid_column_types = ["TEXT", "PERCENT", "DROPDOWN", "DOUBLE", "CURRENCY", "DATE", "TIME", "DATE_TIME"]
937
733
  for i, prop in enumerate(column_properties):
938
- if (
939
- "columnType" in prop
940
- and prop["columnType"] not in valid_column_types
941
- ):
734
+ if "columnType" in prop and prop["columnType"] not in valid_column_types:
942
735
  raise ValueError(
943
736
  f"Invalid column type '{prop['columnType']}' at index {i}. Valid types are: {', '.join(valid_column_types)}"
944
737
  )
945
-
946
738
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
947
-
948
- # Build the table specification and track fields to update
949
739
  table_spec: dict[str, Any] = {"tableId": table_id}
950
740
  fields_to_update = []
951
-
952
- # Add optional properties if provided
953
741
  if table_name is not None:
954
742
  table_spec["name"] = table_name
955
743
  fields_to_update.append("name")
956
-
957
- # Build range if any range parameters are provided
958
744
  range_params: dict[str, Any] = {}
959
745
  if start_row_index is not None:
960
746
  range_params["startRowIndex"] = start_row_index
@@ -964,38 +750,19 @@ class GoogleSheetApp(APIApplication):
964
750
  range_params["startColumnIndex"] = start_column_index
965
751
  if end_column_index is not None:
966
752
  range_params["endColumnIndex"] = end_column_index
967
-
968
753
  if range_params:
969
754
  table_spec["range"] = range_params
970
755
  fields_to_update.append("range")
971
-
972
- # Add column properties if provided
973
756
  if column_properties:
974
757
  table_spec["columnProperties"] = column_properties
975
758
  fields_to_update.append("columnProperties")
976
-
977
- # Validate that at least one field is being updated
978
759
  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
-
760
+ raise ValueError("At least one field must be provided for update (table_name, range indices, or column_properties)")
761
+ request_body = {"requests": [{"updateTable": {"table": table_spec, "fields": ",".join(fields_to_update)}}]}
995
762
  response = self._post(url, data=request_body)
996
763
  return self._handle_response(response)
997
764
 
998
- def clear_values(self, spreadsheetId: str, range: str) -> dict[str, Any]:
765
+ async def clear_values(self, spreadsheetId: str, range: str) -> dict[str, Any]:
999
766
  """
1000
767
  Clears data from a single, specified cell range while preserving all formatting. Unlike `delete_dimensions`, it only removes content, not the cells themselves. For clearing multiple ranges simultaneously, use the `batch_clear_values` function.
1001
768
 
@@ -1017,12 +784,8 @@ class GoogleSheetApp(APIApplication):
1017
784
  response = self._post(url, data={})
1018
785
  return self._handle_response(response)
1019
786
 
1020
- def update_values(
1021
- self,
1022
- spreadsheetId: str,
1023
- range: str,
1024
- values: list[list[Any]],
1025
- value_input_option: str = "RAW",
787
+ async def update_values(
788
+ self, spreadsheetId: str, range: str, values: list[list[Any]], value_input_option: str = "RAW"
1026
789
  ) -> dict[str, Any]:
1027
790
  """
1028
791
  Overwrites cell values within a specific A1 notation range using a provided 2D list. This function replaces existing data in a predefined area, distinguishing it from `append_values`, which adds new rows after a table instead of overwriting a specific block of cells.
@@ -1049,11 +812,7 @@ class GoogleSheetApp(APIApplication):
1049
812
  response = self._put(url, data=data, params=params)
1050
813
  return self._handle_response(response)
1051
814
 
1052
- def batch_clear_values(
1053
- self,
1054
- spreadsheetId: str,
1055
- ranges: list[str],
1056
- ) -> dict[str, Any]:
815
+ async def batch_clear_values(self, spreadsheetId: str, ranges: list[str]) -> dict[str, Any]:
1057
816
  """
1058
817
  Clears cell values from multiple specified ranges in a single batch operation, preserving existing formatting. Unlike `clear_values`, which handles a single range, this method efficiently processes a list of ranges at once, removing only the content and not the cells themselves.
1059
818
 
@@ -1073,18 +832,14 @@ class GoogleSheetApp(APIApplication):
1073
832
  """
1074
833
  if not spreadsheetId:
1075
834
  raise ValueError("spreadsheetId cannot be empty")
1076
-
1077
835
  if not ranges or not isinstance(ranges, list) or len(ranges) == 0:
1078
836
  raise ValueError("ranges must be a non-empty list")
1079
-
1080
837
  url = f"{self.base_url}/{spreadsheetId}/values:batchClear"
1081
-
1082
838
  request_body = {"ranges": ranges}
1083
-
1084
839
  response = self._post(url, data=request_body)
1085
840
  return self._handle_response(response)
1086
841
 
1087
- def batch_get_values_by_data_filter(
842
+ async def batch_get_values_by_data_filter(
1088
843
  self,
1089
844
  spreadsheetId: str,
1090
845
  data_filters: list[dict],
@@ -1116,57 +871,26 @@ class GoogleSheetApp(APIApplication):
1116
871
  """
1117
872
  if not spreadsheetId:
1118
873
  raise ValueError("spreadsheetId cannot be empty")
1119
-
1120
- if (
1121
- not data_filters
1122
- or not isinstance(data_filters, list)
1123
- or len(data_filters) == 0
1124
- ):
874
+ if not data_filters or not isinstance(data_filters, list) or len(data_filters) == 0:
1125
875
  raise ValueError("data_filters must be a non-empty list")
1126
-
1127
876
  if major_dimension and major_dimension not in ["ROWS", "COLUMNS"]:
1128
877
  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
-
878
+ if value_render_option and value_render_option not in ["FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA"]:
879
+ raise ValueError('value_render_option must be either "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA"')
880
+ if date_time_render_option and date_time_render_option not in ["SERIAL_NUMBER", "FORMATTED_STRING"]:
881
+ raise ValueError('date_time_render_option must be either "SERIAL_NUMBER" or "FORMATTED_STRING"')
1147
882
  url = f"{self.base_url}/{spreadsheetId}/values:batchGetByDataFilter"
1148
-
1149
883
  request_body: dict[str, Any] = {"dataFilters": data_filters}
1150
-
1151
- # Add optional parameters if provided
1152
884
  if major_dimension:
1153
885
  request_body["majorDimension"] = major_dimension
1154
-
1155
886
  if value_render_option:
1156
887
  request_body["valueRenderOption"] = value_render_option
1157
-
1158
888
  if date_time_render_option:
1159
889
  request_body["dateTimeRenderOption"] = date_time_render_option
1160
-
1161
890
  response = self._post(url, data=request_body)
1162
891
  return self._handle_response(response)
1163
892
 
1164
- def copy_sheet_to_spreadsheet(
1165
- self,
1166
- spreadsheetId: str,
1167
- sheet_id: int,
1168
- destination_spreadsheetId: str,
1169
- ) -> dict[str, Any]:
893
+ async def copy_sheet_to_spreadsheet(self, spreadsheetId: str, sheet_id: int, destination_spreadsheetId: str) -> dict[str, Any]:
1170
894
  """
1171
895
  Copies a specific sheet, including all its data and formatting, from a source spreadsheet to a different destination spreadsheet. This action duplicates an entire worksheet into another workbook, returning properties of the newly created sheet.
1172
896
 
@@ -1188,21 +912,16 @@ class GoogleSheetApp(APIApplication):
1188
912
  """
1189
913
  if not spreadsheetId:
1190
914
  raise ValueError("spreadsheetId cannot be empty")
1191
-
1192
915
  if sheet_id is None:
1193
916
  raise ValueError("sheet_id cannot be empty")
1194
-
1195
917
  if not destination_spreadsheetId:
1196
918
  raise ValueError("destination_spreadsheetId cannot be empty")
1197
-
1198
919
  url = f"{self.base_url}/{spreadsheetId}/sheets/{sheet_id}:copyTo"
1199
-
1200
920
  request_body = {"destinationSpreadsheetId": destination_spreadsheetId}
1201
-
1202
921
  response = self._post(url, data=request_body)
1203
922
  return self._handle_response(response)
1204
923
 
1205
- def write_values_to_sheet(
924
+ async def write_values_to_sheet(
1206
925
  self,
1207
926
  spreadsheetId: str,
1208
927
  sheet_name: str,
@@ -1234,39 +953,23 @@ class GoogleSheetApp(APIApplication):
1234
953
  """
1235
954
  if not spreadsheetId:
1236
955
  raise ValueError("spreadsheetId cannot be empty")
1237
-
1238
956
  if not sheet_name:
1239
957
  raise ValueError("sheet_name cannot be empty")
1240
-
1241
958
  if not values or not isinstance(values, list) or len(values) == 0:
1242
959
  raise ValueError("values must be a non-empty 2D list")
1243
-
1244
960
  if value_input_option not in ["RAW", "USER_ENTERED"]:
1245
- raise ValueError(
1246
- 'value_input_option must be either "RAW" or "USER_ENTERED"'
1247
- )
1248
-
1249
- # Determine the range based on first_cell_location
961
+ raise ValueError('value_input_option must be either "RAW" or "USER_ENTERED"')
1250
962
  if first_cell_location:
1251
- # Update specific range starting from first_cell_location
1252
963
  range_str = f"{sheet_name}!{first_cell_location}"
1253
964
  else:
1254
- # Append to the sheet (no specific range)
1255
965
  range_str = f"{sheet_name}"
1256
-
1257
966
  url = f"{self.base_url}/{spreadsheetId}/values/{range_str}"
1258
-
1259
- params = {
1260
- "valueInputOption": value_input_option,
1261
- "includeValuesInResponse": include_values_in_response,
1262
- }
1263
-
967
+ params = {"valueInputOption": value_input_option, "includeValuesInResponse": include_values_in_response}
1264
968
  data = {"values": values}
1265
-
1266
969
  response = self._put(url, data=data, params=params)
1267
970
  return self._handle_response(response)
1268
971
 
1269
- def append_values(
972
+ async def append_values(
1270
973
  self,
1271
974
  spreadsheetId: str,
1272
975
  range: str,
@@ -1302,74 +1005,35 @@ class GoogleSheetApp(APIApplication):
1302
1005
  """
1303
1006
  if not spreadsheetId:
1304
1007
  raise ValueError("spreadsheetId cannot be empty")
1305
-
1306
1008
  if not range:
1307
1009
  raise ValueError("range cannot be empty")
1308
-
1309
1010
  if not value_input_option:
1310
1011
  raise ValueError("value_input_option cannot be empty")
1311
-
1312
1012
  if value_input_option not in ["RAW", "USER_ENTERED"]:
1313
- raise ValueError(
1314
- 'value_input_option must be either "RAW" or "USER_ENTERED"'
1315
- )
1316
-
1013
+ raise ValueError('value_input_option must be either "RAW" or "USER_ENTERED"')
1317
1014
  if not values or not isinstance(values, list) or len(values) == 0:
1318
1015
  raise ValueError("values must be a non-empty 2D list")
1319
-
1320
- if insert_data_option and insert_data_option not in [
1321
- "OVERWRITE",
1322
- "INSERT_ROWS",
1323
- ]:
1324
- raise ValueError(
1325
- 'insert_data_option must be either "OVERWRITE" or "INSERT_ROWS"'
1326
- )
1327
-
1328
- if response_value_render_option and response_value_render_option not in [
1329
- "FORMATTED_VALUE",
1330
- "UNFORMATTED_VALUE",
1331
- "FORMULA",
1332
- ]:
1333
- raise ValueError(
1334
- 'response_value_render_option must be either "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA"'
1335
- )
1336
-
1337
- if (
1338
- response_date_time_render_option
1339
- and response_date_time_render_option
1340
- not in ["SERIAL_NUMBER", "FORMATTED_STRING"]
1341
- ):
1342
- raise ValueError(
1343
- 'response_date_time_render_option must be either "SERIAL_NUMBER" or "FORMATTED_STRING"'
1344
- )
1345
-
1016
+ if insert_data_option and insert_data_option not in ["OVERWRITE", "INSERT_ROWS"]:
1017
+ raise ValueError('insert_data_option must be either "OVERWRITE" or "INSERT_ROWS"')
1018
+ if response_value_render_option and response_value_render_option not in ["FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA"]:
1019
+ raise ValueError('response_value_render_option must be either "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA"')
1020
+ if response_date_time_render_option and response_date_time_render_option not in ["SERIAL_NUMBER", "FORMATTED_STRING"]:
1021
+ raise ValueError('response_date_time_render_option must be either "SERIAL_NUMBER" or "FORMATTED_STRING"')
1346
1022
  url = f"{self.base_url}/{spreadsheetId}/values/{range}:append"
1347
-
1348
1023
  params: dict[str, Any] = {"valueInputOption": value_input_option}
1349
-
1350
- # Add optional parameters if provided
1351
1024
  if insert_data_option:
1352
1025
  params["insertDataOption"] = insert_data_option
1353
-
1354
1026
  if include_values_in_response is not None:
1355
1027
  params["includeValuesInResponse"] = include_values_in_response
1356
-
1357
1028
  if response_value_render_option:
1358
1029
  params["responseValueRenderOption"] = response_value_render_option
1359
-
1360
1030
  if response_date_time_render_option:
1361
1031
  params["responseDateTimeRenderOption"] = response_date_time_render_option
1362
-
1363
1032
  data = {"values": values}
1364
-
1365
1033
  response = self._post(url, data=data, params=params)
1366
1034
  return self._handle_response(response)
1367
1035
 
1368
- def clear_basic_filter(
1369
- self,
1370
- spreadsheetId: str,
1371
- sheet_id: int,
1372
- ) -> dict[str, Any]:
1036
+ async def clear_basic_filter(self, spreadsheetId: str, sheet_id: int) -> dict[str, Any]:
1373
1037
  """
1374
1038
  Removes the basic filter from a specified sheet, clearing active sorting and filtering criteria to restore the default data view. As the direct counterpart to `set_basic_filter`, this function removes the entire filter object, not just the cell content.
1375
1039
 
@@ -1389,22 +1053,14 @@ class GoogleSheetApp(APIApplication):
1389
1053
  """
1390
1054
  if not spreadsheetId:
1391
1055
  raise ValueError("spreadsheetId cannot be empty")
1392
-
1393
1056
  if sheet_id < 0:
1394
1057
  raise ValueError("sheet_id must be non-negative")
1395
-
1396
1058
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1397
-
1398
1059
  request_body = {"requests": [{"clearBasicFilter": {"sheetId": sheet_id}}]}
1399
-
1400
1060
  response = self._post(url, data=request_body)
1401
1061
  return self._handle_response(response)
1402
1062
 
1403
- def delete_sheet(
1404
- self,
1405
- spreadsheetId: str,
1406
- sheet_id: int,
1407
- ) -> dict[str, Any]:
1063
+ async def delete_sheet(self, spreadsheetId: str, sheet_id: int) -> dict[str, Any]:
1408
1064
  """
1409
1065
  Permanently deletes a specific sheet (worksheet) from a Google Spreadsheet using its sheet ID. This operation removes the target sheet and all its contents, acting as the direct counterpart to the `add_sheet` function which creates new sheets within a spreadsheet.
1410
1066
 
@@ -1424,23 +1080,15 @@ class GoogleSheetApp(APIApplication):
1424
1080
  """
1425
1081
  if not spreadsheetId:
1426
1082
  raise ValueError("spreadsheetId cannot be empty")
1427
-
1428
1083
  if sheet_id < 0:
1429
1084
  raise ValueError("sheet_id must be non-negative")
1430
-
1431
1085
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1432
-
1433
1086
  request_body = {"requests": [{"deleteSheet": {"sheetId": sheet_id}}]}
1434
-
1435
1087
  response = self._post(url, data=request_body)
1436
1088
  return self._handle_response(response)
1437
1089
 
1438
- def discover_tables(
1439
- self,
1440
- spreadsheetId: str,
1441
- min_rows: int = 2,
1442
- min_columns: int = 1,
1443
- min_confidence: float = 0.5,
1090
+ async def discover_tables(
1091
+ self, spreadsheetId: str, min_rows: int = 2, min_columns: int = 1, min_confidence: float = 0.5
1444
1092
  ) -> dict[str, Any]:
1445
1093
  """
1446
1094
  Heuristically analyzes a spreadsheet to discover and list all table-like data structures, identifying headers and data boundaries. It returns informal data blocks meeting specified size criteria, distinguishing it from functions like `add_table` that manage formally defined tables.
@@ -1463,55 +1111,31 @@ class GoogleSheetApp(APIApplication):
1463
1111
  """
1464
1112
  if not spreadsheetId:
1465
1113
  raise ValueError("spreadsheetId cannot be empty")
1466
-
1467
1114
  if min_rows < 1:
1468
1115
  raise ValueError("min_rows must be at least 1")
1469
-
1470
1116
  if min_columns < 1:
1471
1117
  raise ValueError("min_columns must be at least 1")
1472
-
1473
1118
  if not 0 <= min_confidence <= 1:
1474
1119
  raise ValueError("min_confidence must be between 0.0 and 1.0")
1475
-
1476
- # Get spreadsheet structure
1477
- spreadsheet = self.get_spreadsheet_metadata(spreadsheetId)
1478
-
1120
+ spreadsheet = await self.get_spreadsheet_metadata(spreadsheetId)
1479
1121
  tables = []
1480
-
1481
1122
  for sheet in spreadsheet.get("sheets", []):
1482
1123
  sheet_properties = sheet.get("properties", {})
1483
1124
  sheet_id = sheet_properties.get("sheetId")
1484
1125
  sheet_title = sheet_properties.get("title", "Sheet1")
1485
-
1486
- # Analyze sheet for tables using helper function
1487
1126
  sheet_tables = analyze_sheet_for_tables(
1488
- self.get_values, # Pass the get_values method as a function
1489
- spreadsheetId,
1490
- sheet_id,
1491
- sheet_title,
1492
- min_rows,
1493
- min_columns,
1494
- min_confidence,
1127
+ self.get_values, spreadsheetId, sheet_id, sheet_title, min_rows, min_columns, min_confidence
1495
1128
  )
1496
-
1497
1129
  tables.extend(sheet_tables)
1498
-
1499
1130
  return {
1500
1131
  "spreadsheetId": spreadsheetId,
1501
1132
  "total_tables": len(tables),
1502
1133
  "tables": tables,
1503
- "analysis_parameters": {
1504
- "min_rows": min_rows,
1505
- "min_columns": min_columns,
1506
- },
1134
+ "analysis_parameters": {"min_rows": min_rows, "min_columns": min_columns},
1507
1135
  }
1508
1136
 
1509
- def analyze_table_schema(
1510
- self,
1511
- spreadsheetId: str,
1512
- table_name: str,
1513
- sheet_name: str | None = None,
1514
- sample_size: int = 50,
1137
+ async def analyze_table_schema(
1138
+ self, spreadsheetId: str, table_name: str, sheet_name: str | None = None, sample_size: int = 50
1515
1139
  ) -> dict[str, Any]:
1516
1140
  """
1517
1141
  Infers a specified table's schema by analyzing a data sample. After locating the table by name (a value discovered via `discover_tables`), this function determines the most likely data type and properties for each column, providing a detailed structural breakdown of its content.
@@ -1534,28 +1158,17 @@ class GoogleSheetApp(APIApplication):
1534
1158
  """
1535
1159
  if not spreadsheetId:
1536
1160
  raise ValueError("spreadsheetId cannot be empty")
1537
-
1538
1161
  if not table_name:
1539
1162
  raise ValueError("table_name cannot be empty")
1540
-
1541
1163
  if not 1 <= sample_size <= 1000:
1542
1164
  raise ValueError("sample_size must be between 1 and 1000")
1543
-
1544
- # Get spreadsheet structure
1545
- spreadsheet = self.get_spreadsheet_metadata(spreadsheetId)
1546
-
1547
- # Find the target table
1165
+ spreadsheet = await self.get_spreadsheet_metadata(spreadsheetId)
1548
1166
  target_table = None
1549
-
1550
1167
  for sheet in spreadsheet.get("sheets", []):
1551
1168
  sheet_properties = sheet.get("properties", {})
1552
1169
  sheet_title = sheet_properties.get("title", "Sheet1")
1553
-
1554
- # If sheet_name is specified, only look in that sheet
1555
1170
  if sheet_name and sheet_title != sheet_name:
1556
1171
  continue
1557
-
1558
- # Get tables in this sheet
1559
1172
  sheet_tables = analyze_sheet_for_tables(
1560
1173
  self.get_values,
1561
1174
  spreadsheetId,
@@ -1565,35 +1178,20 @@ class GoogleSheetApp(APIApplication):
1565
1178
  min_columns=1,
1566
1179
  min_confidence=0.3,
1567
1180
  )
1568
-
1569
1181
  for table in sheet_tables:
1570
1182
  if table_name == "auto":
1571
- # For auto mode, select the largest table
1572
- if target_table is None or (
1573
- table["rows"] * table["columns"]
1574
- > target_table["rows"] * target_table["columns"]
1575
- ):
1183
+ if target_table is None or table["rows"] * table["columns"] > target_table["rows"] * target_table["columns"]:
1576
1184
  target_table = table
1577
1185
  elif table["table_name"] == table_name:
1578
1186
  target_table = table
1579
1187
  break
1580
-
1581
1188
  if target_table and table_name != "auto":
1582
1189
  break
1583
-
1584
1190
  if not target_table:
1585
1191
  raise ValueError(f"Table '{table_name}' not found in spreadsheet")
1192
+ return analyze_table_schema(self.get_values, spreadsheetId, target_table, sample_size)
1586
1193
 
1587
- # Use the helper function to analyze the table schema
1588
- return analyze_table_schema(
1589
- self.get_values, spreadsheetId, target_table, sample_size
1590
- )
1591
-
1592
- def set_basic_filter(
1593
- self,
1594
- spreadsheetId: str,
1595
- filter: dict,
1596
- ) -> dict[str, Any]:
1194
+ async def set_basic_filter(self, spreadsheetId: str, filter: dict) -> dict[str, Any]:
1597
1195
  """
1598
1196
  Sets or updates a basic filter on a specified range within a sheet, enabling data sorting and filtering. The filter's target range and optional sort specifications are defined in a dictionary argument. It is the counterpart to `clear_basic_filter`, which removes an existing filter.
1599
1197
 
@@ -1622,27 +1220,19 @@ class GoogleSheetApp(APIApplication):
1622
1220
  """
1623
1221
  if not spreadsheetId:
1624
1222
  raise ValueError("spreadsheetId cannot be empty")
1625
-
1626
1223
  if not filter:
1627
1224
  raise ValueError("filter cannot be empty")
1628
-
1629
- # Validate required filter fields
1630
1225
  if "range" not in filter:
1631
1226
  raise ValueError("filter must contain 'range' field")
1632
-
1633
- # Validate required filter fields using Google API naming convention
1634
1227
  range_data = filter["range"]
1635
1228
  if "sheetId" not in range_data:
1636
1229
  raise ValueError("filter range must contain 'sheetId' field")
1637
-
1638
1230
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1639
-
1640
1231
  request_body = {"requests": [{"setBasicFilter": {"filter": filter}}]}
1641
-
1642
1232
  response = self._post(url, data=request_body)
1643
1233
  return self._handle_response(response)
1644
1234
 
1645
- def format_cells(
1235
+ async def format_cells(
1646
1236
  self,
1647
1237
  spreadsheetId: str,
1648
1238
  worksheetId: int,
@@ -1650,34 +1240,26 @@ class GoogleSheetApp(APIApplication):
1650
1240
  startColumnIndex: int,
1651
1241
  endRowIndex: int,
1652
1242
  endColumnIndex: int,
1653
- # Text formatting
1654
1243
  bold: bool | None = None,
1655
1244
  italic: bool | None = None,
1656
1245
  underline: bool | None = None,
1657
1246
  strikethrough: bool | None = None,
1658
1247
  fontSize: int | None = None,
1659
1248
  fontFamily: str | None = None,
1660
- # Colors
1661
1249
  backgroundRed: float | None = None,
1662
1250
  backgroundGreen: float | None = None,
1663
1251
  backgroundBlue: float | None = None,
1664
1252
  textRed: float | None = None,
1665
1253
  textGreen: float | None = None,
1666
1254
  textBlue: float | None = None,
1667
- # Alignment
1668
- horizontalAlignment: str | None = None, # "LEFT", "CENTER", "RIGHT"
1669
- verticalAlignment: str | None = None, # "TOP", "MIDDLE", "BOTTOM"
1670
- # Text wrapping
1671
- wrapStrategy: str
1672
- | None = None, # "OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", "WRAP"
1673
- # Number format
1255
+ horizontalAlignment: str | None = None,
1256
+ verticalAlignment: str | None = None,
1257
+ wrapStrategy: str | None = None,
1674
1258
  numberFormat: str | None = None,
1675
- # Borders
1676
1259
  borderTop: dict | None = None,
1677
1260
  borderBottom: dict | None = None,
1678
1261
  borderLeft: dict | None = None,
1679
1262
  borderRight: dict | None = None,
1680
- # Merge cells
1681
1263
  mergeCells: bool = False,
1682
1264
  ) -> dict[str, Any]:
1683
1265
  """
@@ -1733,25 +1315,14 @@ class GoogleSheetApp(APIApplication):
1733
1315
  """
1734
1316
  if not spreadsheetId:
1735
1317
  raise ValueError("spreadsheetId cannot be empty")
1736
-
1737
1318
  if worksheetId < 0:
1738
1319
  raise ValueError("worksheetId must be non-negative")
1739
-
1740
- if (
1741
- startRowIndex < 0
1742
- or startColumnIndex < 0
1743
- or endRowIndex < 0
1744
- or endColumnIndex < 0
1745
- ):
1320
+ if startRowIndex < 0 or startColumnIndex < 0 or endRowIndex < 0 or (endColumnIndex < 0):
1746
1321
  raise ValueError("All indices must be non-negative")
1747
-
1748
1322
  if startRowIndex >= endRowIndex:
1749
1323
  raise ValueError("endRowIndex must be greater than startRowIndex")
1750
-
1751
1324
  if startColumnIndex >= endColumnIndex:
1752
1325
  raise ValueError("endColumnIndex must be greater than startColumnIndex")
1753
-
1754
- # Validate color values if provided
1755
1326
  for color_name, color_value in [
1756
1327
  ("backgroundRed", backgroundRed),
1757
1328
  ("backgroundGreen", backgroundGreen),
@@ -1760,37 +1331,18 @@ class GoogleSheetApp(APIApplication):
1760
1331
  ("textGreen", textGreen),
1761
1332
  ("textBlue", textBlue),
1762
1333
  ]:
1763
- if color_value is not None and not 0 <= color_value <= 1:
1334
+ if color_value is not None and (not 0 <= color_value <= 1):
1764
1335
  raise ValueError(f"{color_name} must be between 0.0 and 1.0")
1765
-
1766
1336
  if fontSize is not None and fontSize <= 0:
1767
1337
  raise ValueError("fontSize must be positive")
1768
-
1769
- if horizontalAlignment and horizontalAlignment not in [
1770
- "LEFT",
1771
- "CENTER",
1772
- "RIGHT",
1773
- ]:
1338
+ if horizontalAlignment and horizontalAlignment not in ["LEFT", "CENTER", "RIGHT"]:
1774
1339
  raise ValueError('horizontalAlignment must be "LEFT", "CENTER", or "RIGHT"')
1775
-
1776
1340
  if verticalAlignment and verticalAlignment not in ["TOP", "MIDDLE", "BOTTOM"]:
1777
1341
  raise ValueError('verticalAlignment must be "TOP", "MIDDLE", or "BOTTOM"')
1778
-
1779
- if wrapStrategy and wrapStrategy not in [
1780
- "OVERFLOW_CELL",
1781
- "LEGACY_WRAP",
1782
- "CLIP",
1783
- "WRAP",
1784
- ]:
1785
- raise ValueError(
1786
- 'wrapStrategy must be "OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", or "WRAP"'
1787
- )
1788
-
1342
+ if wrapStrategy and wrapStrategy not in ["OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", "WRAP"]:
1343
+ raise ValueError('wrapStrategy must be "OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", or "WRAP"')
1789
1344
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1790
-
1791
1345
  requests = []
1792
-
1793
- # Handle cell merging first if requested
1794
1346
  if mergeCells:
1795
1347
  requests.append(
1796
1348
  {
@@ -1806,11 +1358,7 @@ class GoogleSheetApp(APIApplication):
1806
1358
  }
1807
1359
  }
1808
1360
  )
1809
-
1810
- # Build the cell format request
1811
1361
  cell_format = {}
1812
-
1813
- # Text format
1814
1362
  text_format = {}
1815
1363
  if bold is not None:
1816
1364
  text_format["bold"] = bold
@@ -1824,9 +1372,7 @@ class GoogleSheetApp(APIApplication):
1824
1372
  text_format["fontSize"] = fontSize
1825
1373
  if fontFamily is not None:
1826
1374
  text_format["fontFamily"] = fontFamily
1827
-
1828
- # Text color
1829
- if any(color is not None for color in [textRed, textGreen, textBlue]):
1375
+ if any((color is not None for color in [textRed, textGreen, textBlue])):
1830
1376
  text_color = {}
1831
1377
  if textRed is not None:
1832
1378
  text_color["red"] = textRed
@@ -1836,15 +1382,9 @@ class GoogleSheetApp(APIApplication):
1836
1382
  text_color["blue"] = textBlue
1837
1383
  if text_color:
1838
1384
  text_format["foregroundColor"] = {"rgbColor": text_color}
1839
-
1840
1385
  if text_format:
1841
1386
  cell_format["textFormat"] = text_format
1842
-
1843
- # Background color
1844
- if any(
1845
- color is not None
1846
- for color in [backgroundRed, backgroundGreen, backgroundBlue]
1847
- ):
1387
+ if any((color is not None for color in [backgroundRed, backgroundGreen, backgroundBlue])):
1848
1388
  background_color = {}
1849
1389
  if backgroundRed is not None:
1850
1390
  background_color["red"] = backgroundRed
@@ -1854,35 +1394,19 @@ class GoogleSheetApp(APIApplication):
1854
1394
  background_color["blue"] = backgroundBlue
1855
1395
  if background_color:
1856
1396
  cell_format["backgroundColorStyle"] = {"rgbColor": background_color}
1857
-
1858
- # Alignment
1859
1397
  if horizontalAlignment or verticalAlignment:
1860
1398
  cell_format["horizontalAlignment"] = horizontalAlignment
1861
1399
  cell_format["verticalAlignment"] = verticalAlignment
1862
-
1863
- # Text wrapping
1864
1400
  if wrapStrategy:
1865
1401
  cell_format["wrapStrategy"] = wrapStrategy
1866
-
1867
- # Number format
1868
1402
  if numberFormat:
1869
1403
  cell_format["numberFormat"] = {"type": "TEXT", "pattern": numberFormat}
1870
-
1871
- # Borders
1872
1404
  borders = {}
1873
- for border_side, border_config in [
1874
- ("top", borderTop),
1875
- ("bottom", borderBottom),
1876
- ("left", borderLeft),
1877
- ("right", borderRight),
1878
- ]:
1405
+ for border_side, border_config in [("top", borderTop), ("bottom", borderBottom), ("left", borderLeft), ("right", borderRight)]:
1879
1406
  if border_config:
1880
1407
  borders[border_side] = border_config
1881
-
1882
1408
  if borders:
1883
1409
  cell_format["borders"] = borders
1884
-
1885
- # Add cell formatting request if any formatting is specified
1886
1410
  if cell_format:
1887
1411
  requests.append(
1888
1412
  {
@@ -1899,7 +1423,6 @@ class GoogleSheetApp(APIApplication):
1899
1423
  }
1900
1424
  }
1901
1425
  )
1902
-
1903
1426
  request_body = {"requests": requests}
1904
1427
  response = self._post(url, data=request_body)
1905
1428
  return self._handle_response(response)