universal-mcp-applications 0.1.32__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 +85 -189
  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 +100 -581
  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 +240 -181
  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 +50 -109
  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 +33 -115
  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.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
  104. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
  105. {universal_mcp_applications-0.1.32.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,7 +14,7 @@ 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
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
 
@@ -40,12 +35,10 @@ class GoogleSheetApp(APIApplication):
40
35
  spreadsheet_data = {"properties": {"title": title}}
41
36
  response = self._post(url, data=spreadsheet_data)
42
37
  payload = self._handle_response(response)
43
- payload["Note"] = (
44
- "You must load and call other google_sheet content functions (like `google_sheet__write_values_to_sheet`)"
45
- )
38
+ payload["Note"] = "You must load and call other google_sheet content functions (like `google_sheet__write_values_to_sheet`)"
46
39
  return payload
47
40
 
48
- def get_spreadsheet_metadata(self, spreadsheetId: str) -> dict[str, Any]:
41
+ async def get_spreadsheet_metadata(self, spreadsheetId: str) -> dict[str, Any]:
49
42
  """
50
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.
51
44
 
@@ -67,7 +60,7 @@ class GoogleSheetApp(APIApplication):
67
60
  response = self._get(url)
68
61
  return self._handle_response(response)
69
62
 
70
- def get_values(
63
+ async def get_values(
71
64
  self,
72
65
  spreadsheetId: str,
73
66
  range: str,
@@ -97,20 +90,16 @@ class GoogleSheetApp(APIApplication):
97
90
  """
98
91
  url = f"{self.base_url}/{spreadsheetId}/values/{range}"
99
92
  params = {}
100
-
101
93
  if majorDimension:
102
94
  params["majorDimension"] = majorDimension
103
95
  if valueRenderOption:
104
96
  params["valueRenderOption"] = valueRenderOption
105
97
  if dateTimeRenderOption:
106
98
  params["dateTimeRenderOption"] = dateTimeRenderOption
107
-
108
99
  response = self._get(url, params=params)
109
100
  return self._handle_response(response)
110
101
 
111
- def batch_get_values_by_range(
112
- self, spreadsheetId: str, ranges: list[str] | None = None
113
- ) -> dict[str, Any]:
102
+ async def batch_get_values_by_range(self, spreadsheetId: str, ranges: list[str] | None = None) -> dict[str, Any]:
114
103
  """
115
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.
116
105
 
@@ -135,7 +124,7 @@ class GoogleSheetApp(APIApplication):
135
124
  response = self._get(url, params=params)
136
125
  return self._handle_response(response)
137
126
 
138
- def insert_dimensions(
127
+ async def insert_dimensions(
139
128
  self,
140
129
  spreadsheetId: str,
141
130
  sheet_id: int,
@@ -176,56 +165,33 @@ class GoogleSheetApp(APIApplication):
176
165
  """
177
166
  if not spreadsheetId:
178
167
  raise ValueError("spreadsheetId cannot be empty")
179
-
180
168
  if dimension not in ["ROWS", "COLUMNS"]:
181
169
  raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
182
-
183
170
  if start_index < 0 or end_index < 0:
184
171
  raise ValueError("start_index and end_index must be non-negative")
185
-
186
172
  if start_index >= end_index:
187
173
  raise ValueError("end_index must be greater than start_index")
188
-
189
174
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
190
-
191
175
  request_body: dict[str, Any] = {
192
176
  "requests": [
193
177
  {
194
178
  "insertDimension": {
195
179
  "inheritFromBefore": inherit_from_before,
196
- "range": {
197
- "dimension": dimension,
198
- "sheetId": sheet_id,
199
- "startIndex": start_index,
200
- "endIndex": end_index,
201
- },
180
+ "range": {"dimension": dimension, "sheetId": sheet_id, "startIndex": start_index, "endIndex": end_index},
202
181
  }
203
182
  }
204
183
  ]
205
184
  }
206
-
207
- # Add optional parameters if provided
208
185
  if include_spreadsheet_in_response is not None:
209
- request_body["includeSpreadsheetInResponse"] = (
210
- include_spreadsheet_in_response
211
- )
212
-
186
+ request_body["includeSpreadsheetInResponse"] = include_spreadsheet_in_response
213
187
  if response_include_grid_data is not None:
214
188
  request_body["responseIncludeGridData"] = response_include_grid_data
215
-
216
189
  if response_ranges is not None:
217
190
  request_body["responseRanges"] = response_ranges
218
-
219
191
  response = self._post(url, data=request_body)
220
192
  return self._handle_response(response)
221
193
 
222
- def append_dimensions(
223
- self,
224
- spreadsheetId: str,
225
- sheet_id: int,
226
- dimension: str,
227
- length: int,
228
- ) -> dict[str, Any]:
194
+ async def append_dimensions(self, spreadsheetId: str, sheet_id: int, dimension: str, length: int) -> dict[str, Any]:
229
195
  """
230
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.
231
197
 
@@ -250,31 +216,16 @@ class GoogleSheetApp(APIApplication):
250
216
  """
251
217
  if not spreadsheetId:
252
218
  raise ValueError("spreadsheetId cannot be empty")
253
-
254
219
  if dimension not in ["ROWS", "COLUMNS"]:
255
220
  raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
256
-
257
221
  if length <= 0:
258
222
  raise ValueError("length must be a positive integer")
259
-
260
223
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
261
-
262
- request_body = {
263
- "requests": [
264
- {
265
- "appendDimension": {
266
- "sheetId": sheet_id,
267
- "dimension": dimension,
268
- "length": length,
269
- }
270
- }
271
- ]
272
- }
273
-
224
+ request_body = {"requests": [{"appendDimension": {"sheetId": sheet_id, "dimension": dimension, "length": length}}]}
274
225
  response = self._post(url, data=request_body)
275
226
  return self._handle_response(response)
276
227
 
277
- def delete_dimensions(
228
+ async def delete_dimensions(
278
229
  self,
279
230
  spreadsheetId: str,
280
231
  sheet_id: int,
@@ -310,49 +261,32 @@ class GoogleSheetApp(APIApplication):
310
261
  """
311
262
  if not spreadsheetId:
312
263
  raise ValueError("spreadsheetId cannot be empty")
313
-
314
264
  if dimension not in ["ROWS", "COLUMNS"]:
315
265
  raise ValueError('dimension must be either "ROWS" or "COLUMNS"')
316
-
317
266
  if start_index < 0 or end_index < 0:
318
267
  raise ValueError("start_index and end_index must be non-negative")
319
-
320
268
  if start_index >= end_index:
321
269
  raise ValueError("end_index must be greater than start_index")
322
-
323
270
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
324
-
325
271
  request_body: dict[str, Any] = {
326
272
  "requests": [
327
273
  {
328
274
  "deleteDimension": {
329
- "range": {
330
- "sheetId": sheet_id,
331
- "dimension": dimension,
332
- "startIndex": start_index,
333
- "endIndex": end_index,
334
- }
275
+ "range": {"sheetId": sheet_id, "dimension": dimension, "startIndex": start_index, "endIndex": end_index}
335
276
  }
336
277
  }
337
278
  ]
338
279
  }
339
-
340
- # Add optional response parameters if provided
341
280
  if include_spreadsheet_in_response is not None:
342
- request_body["includeSpreadsheetInResponse"] = (
343
- include_spreadsheet_in_response
344
- )
345
-
281
+ request_body["includeSpreadsheetInResponse"] = include_spreadsheet_in_response
346
282
  if response_include_grid_data is not None:
347
283
  request_body["responseIncludeGridData"] = response_include_grid_data
348
-
349
284
  if response_ranges is not None:
350
285
  request_body["responseRanges"] = response_ranges
351
-
352
286
  response = self._post(url, data=request_body)
353
287
  return self._handle_response(response)
354
288
 
355
- def add_sheet(
289
+ async def add_sheet(
356
290
  self,
357
291
  spreadsheetId: str,
358
292
  title: str | None = None,
@@ -362,7 +296,6 @@ class GoogleSheetApp(APIApplication):
362
296
  hidden: bool | None = None,
363
297
  rightToLeft: bool | None = None,
364
298
  tabColorStyle: dict | None = None,
365
- # Grid properties
366
299
  rowCount: int | None = None,
367
300
  columnCount: int | None = None,
368
301
  frozenRowCount: int | None = None,
@@ -370,7 +303,6 @@ class GoogleSheetApp(APIApplication):
370
303
  hideGridlines: bool | None = None,
371
304
  rowGroupControlAfter: bool | None = None,
372
305
  columnGroupControlAfter: bool | None = None,
373
- # Response options
374
306
  includeSpreadsheetInResponse: bool = False,
375
307
  responseIncludeGridData: bool = False,
376
308
  ) -> dict[str, Any]:
@@ -408,80 +340,61 @@ class GoogleSheetApp(APIApplication):
408
340
  """
409
341
  if not spreadsheetId:
410
342
  raise ValueError("spreadsheetId cannot be empty")
411
-
412
343
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
413
-
414
- # Build the addSheet request with properties
415
344
  add_sheet_request = {"properties": {}}
416
-
417
345
  if title is not None:
418
346
  add_sheet_request["properties"]["title"] = title
419
-
420
347
  if sheetId is not None:
421
348
  add_sheet_request["properties"]["sheetId"] = sheetId
422
-
423
349
  if index is not None:
424
350
  add_sheet_request["properties"]["index"] = index
425
-
426
351
  if sheetType is not None:
427
352
  add_sheet_request["properties"]["sheetType"] = sheetType
428
-
429
353
  if hidden is not None:
430
354
  add_sheet_request["properties"]["hidden"] = hidden
431
-
432
355
  if rightToLeft is not None:
433
356
  add_sheet_request["properties"]["rightToLeft"] = rightToLeft
434
-
435
357
  if tabColorStyle is not None:
436
358
  add_sheet_request["properties"]["tabColorStyle"] = tabColorStyle
437
-
438
- # Build grid properties if any grid-related parameters are provided
439
359
  grid_properties = {}
440
360
  if any(
441
- param is not None
442
- for param in [
443
- rowCount,
444
- columnCount,
445
- frozenRowCount,
446
- frozenColumnCount,
447
- hideGridlines,
448
- rowGroupControlAfter,
449
- columnGroupControlAfter,
450
- ]
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
+ )
451
373
  ):
452
374
  if rowCount is not None:
453
375
  grid_properties["rowCount"] = rowCount
454
-
455
376
  if columnCount is not None:
456
377
  grid_properties["columnCount"] = columnCount
457
-
458
378
  if frozenRowCount is not None:
459
379
  grid_properties["frozenRowCount"] = frozenRowCount
460
-
461
380
  if frozenColumnCount is not None:
462
381
  grid_properties["frozenColumnCount"] = frozenColumnCount
463
-
464
382
  if hideGridlines is not None:
465
383
  grid_properties["hideGridlines"] = hideGridlines
466
-
467
384
  if rowGroupControlAfter is not None:
468
385
  grid_properties["rowGroupControlAfter"] = rowGroupControlAfter
469
-
470
386
  if columnGroupControlAfter is not None:
471
387
  grid_properties["columnGroupControlAfter"] = columnGroupControlAfter
472
-
473
388
  add_sheet_request["properties"]["gridProperties"] = grid_properties
474
-
475
389
  request_body = {
476
390
  "requests": [{"addSheet": add_sheet_request}],
477
391
  "includeSpreadsheetInResponse": includeSpreadsheetInResponse,
478
392
  "responseIncludeGridData": responseIncludeGridData,
479
393
  }
480
-
481
394
  response = self._post(url, data=request_body)
482
395
  return self._handle_response(response)
483
396
 
484
- def add_basic_chart(
397
+ async def add_basic_chart(
485
398
  self,
486
399
  spreadsheetId: str,
487
400
  source_sheet_id: int,
@@ -524,27 +437,17 @@ class GoogleSheetApp(APIApplication):
524
437
  """
525
438
  if not spreadsheetId:
526
439
  raise ValueError("spreadsheetId cannot be empty")
527
-
528
440
  if not chart_title:
529
441
  raise ValueError("chart_title cannot be empty")
530
-
531
442
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
532
-
533
- # Build the chart specification
534
443
  chart_spec = {
535
444
  "title": chart_title,
536
445
  "basicChart": {
537
446
  "chartType": chart_type,
538
447
  "legendPosition": "BOTTOM_LEGEND",
539
448
  "axis": [
540
- {
541
- "position": "BOTTOM_AXIS",
542
- "title": x_axis_title if x_axis_title else "Categories",
543
- },
544
- {
545
- "position": "LEFT_AXIS",
546
- "title": y_axis_title if y_axis_title else "Values",
547
- },
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"},
548
451
  ],
549
452
  "domains": [
550
453
  {
@@ -553,18 +456,10 @@ class GoogleSheetApp(APIApplication):
553
456
  "sources": [
554
457
  {
555
458
  "sheetId": source_sheet_id,
556
- "startRowIndex": domain_range.get(
557
- "startRowIndex", 0
558
- ),
559
- "endRowIndex": domain_range.get(
560
- "endRowIndex", 1
561
- ),
562
- "startColumnIndex": domain_range.get(
563
- "startColumnIndex", 0
564
- ),
565
- "endColumnIndex": domain_range.get(
566
- "endColumnIndex", 1
567
- ),
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),
568
463
  }
569
464
  ]
570
465
  }
@@ -575,8 +470,6 @@ class GoogleSheetApp(APIApplication):
575
470
  "headerCount": 1,
576
471
  },
577
472
  }
578
-
579
- # Add series data
580
473
  for series_range in series_ranges:
581
474
  series = {
582
475
  "series": {
@@ -586,9 +479,7 @@ class GoogleSheetApp(APIApplication):
586
479
  "sheetId": source_sheet_id,
587
480
  "startRowIndex": series_range.get("startRowIndex", 0),
588
481
  "endRowIndex": series_range.get("endRowIndex", 1),
589
- "startColumnIndex": series_range.get(
590
- "startColumnIndex", 0
591
- ),
482
+ "startColumnIndex": series_range.get("startColumnIndex", 0),
592
483
  "endColumnIndex": series_range.get("endColumnIndex", 1),
593
484
  }
594
485
  ]
@@ -597,40 +488,25 @@ class GoogleSheetApp(APIApplication):
597
488
  "targetAxis": "LEFT_AXIS",
598
489
  }
599
490
  chart_spec["basicChart"]["series"].append(series)
600
-
601
- # Build the position specification
602
491
  if new_sheet:
603
492
  position_spec = {"newSheet": True}
604
- # For existing sheet, use overlayPosition structure
605
493
  elif chart_position:
606
494
  position_spec = chart_position
607
495
  else:
608
- # Default positioning when placing in existing sheet
609
496
  position_spec = {
610
497
  "overlayPosition": {
611
- "anchorCell": {
612
- "sheetId": source_sheet_id,
613
- "rowIndex": 0,
614
- "columnIndex": 0,
615
- },
498
+ "anchorCell": {"sheetId": source_sheet_id, "rowIndex": 0, "columnIndex": 0},
616
499
  "offsetXPixels": 0,
617
500
  "offsetYPixels": 0,
618
501
  "widthPixels": 600,
619
502
  "heightPixels": 400,
620
503
  }
621
504
  }
622
-
623
- # Build the request body
624
- request_body = {
625
- "requests": [
626
- {"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}
627
- ]
628
- }
629
-
505
+ request_body = {"requests": [{"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}]}
630
506
  response = self._post(url, data=request_body)
631
507
  return self._handle_response(response)
632
508
 
633
- def add_pie_chart(
509
+ async def add_pie_chart(
634
510
  self,
635
511
  spreadsheetId: str,
636
512
  source_sheet_id: int,
@@ -669,16 +545,11 @@ class GoogleSheetApp(APIApplication):
669
545
  """
670
546
  if not spreadsheetId:
671
547
  raise ValueError("spreadsheetId cannot be empty")
672
-
673
548
  if not chart_title:
674
549
  raise ValueError("chart_title cannot be empty")
675
-
676
- 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):
677
551
  raise ValueError("pie_hole must be between 0.0 and 1.0")
678
-
679
552
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
680
-
681
- # Build the pie chart specification
682
553
  pie_chart_spec = {
683
554
  "legendPosition": legend_position,
684
555
  "domain": {
@@ -701,55 +572,35 @@ class GoogleSheetApp(APIApplication):
701
572
  "sheetId": source_sheet_id,
702
573
  "startRowIndex": data_range.get("startRowIndex", 0),
703
574
  "endRowIndex": data_range.get("endRowIndex", 1),
704
- "startColumnIndex": data_range.get("startColumnIndex", 0)
705
- + 1,
575
+ "startColumnIndex": data_range.get("startColumnIndex", 0) + 1,
706
576
  "endColumnIndex": data_range.get("endColumnIndex", 2),
707
577
  }
708
578
  ]
709
579
  }
710
580
  },
711
581
  }
712
-
713
- # Add pie hole for donut chart if specified
714
582
  if pie_hole is not None:
715
583
  pie_chart_spec["pieHole"] = pie_hole
716
-
717
- # Build the chart specification
718
584
  chart_spec = {"title": chart_title, "pieChart": pie_chart_spec}
719
-
720
- # Build the position specification
721
585
  if new_sheet:
722
586
  position_spec = {"newSheet": True}
723
- # For existing sheet, use overlayPosition structure
724
587
  elif chart_position:
725
588
  position_spec = chart_position
726
589
  else:
727
- # Default positioning when placing in existing sheet
728
590
  position_spec = {
729
591
  "overlayPosition": {
730
- "anchorCell": {
731
- "sheetId": source_sheet_id,
732
- "rowIndex": 0,
733
- "columnIndex": 0,
734
- },
592
+ "anchorCell": {"sheetId": source_sheet_id, "rowIndex": 0, "columnIndex": 0},
735
593
  "offsetXPixels": 0,
736
594
  "offsetYPixels": 0,
737
595
  "widthPixels": 600,
738
596
  "heightPixels": 400,
739
597
  }
740
598
  }
741
-
742
- # Build the request body
743
- request_body = {
744
- "requests": [
745
- {"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}
746
- ]
747
- }
748
-
599
+ request_body = {"requests": [{"addChart": {"chart": {"spec": chart_spec, "position": position_spec}}}]}
749
600
  response = self._post(url, data=request_body)
750
601
  return self._handle_response(response)
751
602
 
752
- def add_table(
603
+ async def add_table(
753
604
  self,
754
605
  spreadsheetId: str,
755
606
  sheet_id: int,
@@ -789,51 +640,24 @@ class GoogleSheetApp(APIApplication):
789
640
  """
790
641
  if not spreadsheetId:
791
642
  raise ValueError("spreadsheetId cannot be empty")
792
-
793
643
  if not table_name:
794
644
  raise ValueError("table_name cannot be empty")
795
-
796
645
  if not table_id:
797
646
  raise ValueError("table_id cannot be empty")
798
-
799
- if (
800
- start_row_index < 0
801
- or end_row_index < 0
802
- or start_column_index < 0
803
- or end_column_index < 0
804
- ):
647
+ if start_row_index < 0 or end_row_index < 0 or start_column_index < 0 or (end_column_index < 0):
805
648
  raise ValueError("All indices must be non-negative")
806
-
807
649
  if start_row_index >= end_row_index:
808
650
  raise ValueError("end_row_index must be greater than start_row_index")
809
-
810
651
  if start_column_index >= end_column_index:
811
652
  raise ValueError("end_column_index must be greater than start_column_index")
812
-
813
- # Validate column properties if provided
814
653
  if column_properties:
815
- valid_column_types = [
816
- "TEXT",
817
- "PERCENT",
818
- "DROPDOWN",
819
- "DOUBLE",
820
- "CURRENCY",
821
- "DATE",
822
- "TIME",
823
- "DATE_TIME",
824
- ]
654
+ valid_column_types = ["TEXT", "PERCENT", "DROPDOWN", "DOUBLE", "CURRENCY", "DATE", "TIME", "DATE_TIME"]
825
655
  for i, prop in enumerate(column_properties):
826
- if (
827
- "columnType" in prop
828
- and prop["columnType"] not in valid_column_types
829
- ):
656
+ if "columnType" in prop and prop["columnType"] not in valid_column_types:
830
657
  raise ValueError(
831
658
  f"Invalid column type '{prop['columnType']}' at index {i}. Valid types are: {', '.join(valid_column_types)}"
832
659
  )
833
-
834
660
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
835
-
836
- # Build the table specification
837
661
  table_spec = {
838
662
  "name": table_name,
839
663
  "tableId": table_id,
@@ -845,18 +669,13 @@ class GoogleSheetApp(APIApplication):
845
669
  "endRowIndex": end_row_index,
846
670
  },
847
671
  }
848
-
849
- # Add column properties if provided
850
672
  if column_properties:
851
673
  table_spec["columnProperties"] = column_properties
852
-
853
- # Build the request body
854
674
  request_body = {"requests": [{"addTable": {"table": table_spec}}]}
855
-
856
675
  response = self._post(url, data=request_body)
857
676
  return self._handle_response(response)
858
677
 
859
- def update_table(
678
+ async def update_table(
860
679
  self,
861
680
  spreadsheetId: str,
862
681
  table_id: str,
@@ -895,70 +714,33 @@ class GoogleSheetApp(APIApplication):
895
714
  """
896
715
  if not spreadsheetId:
897
716
  raise ValueError("spreadsheetId cannot be empty")
898
-
899
717
  if not table_id:
900
718
  raise ValueError("table_id cannot be empty")
901
-
902
- # Validate indices if provided
903
719
  if start_row_index is not None and start_row_index < 0:
904
720
  raise ValueError("start_row_index must be non-negative")
905
-
906
721
  if end_row_index is not None and end_row_index < 0:
907
722
  raise ValueError("end_row_index must be non-negative")
908
-
909
723
  if start_column_index is not None and start_column_index < 0:
910
724
  raise ValueError("start_column_index must be non-negative")
911
-
912
725
  if end_column_index is not None and end_column_index < 0:
913
726
  raise ValueError("end_column_index must be non-negative")
914
-
915
- if (
916
- start_row_index is not None
917
- and end_row_index is not None
918
- and start_row_index >= end_row_index
919
- ):
727
+ if start_row_index is not None and end_row_index is not None and (start_row_index >= end_row_index):
920
728
  raise ValueError("end_row_index must be greater than start_row_index")
921
-
922
- if (
923
- start_column_index is not None
924
- and end_column_index is not None
925
- and start_column_index >= end_column_index
926
- ):
729
+ if start_column_index is not None and end_column_index is not None and (start_column_index >= end_column_index):
927
730
  raise ValueError("end_column_index must be greater than start_column_index")
928
-
929
- # Validate column properties if provided
930
731
  if column_properties:
931
- valid_column_types = [
932
- "TEXT",
933
- "PERCENT",
934
- "DROPDOWN",
935
- "DOUBLE",
936
- "CURRENCY",
937
- "DATE",
938
- "TIME",
939
- "DATE_TIME",
940
- ]
732
+ valid_column_types = ["TEXT", "PERCENT", "DROPDOWN", "DOUBLE", "CURRENCY", "DATE", "TIME", "DATE_TIME"]
941
733
  for i, prop in enumerate(column_properties):
942
- if (
943
- "columnType" in prop
944
- and prop["columnType"] not in valid_column_types
945
- ):
734
+ if "columnType" in prop and prop["columnType"] not in valid_column_types:
946
735
  raise ValueError(
947
736
  f"Invalid column type '{prop['columnType']}' at index {i}. Valid types are: {', '.join(valid_column_types)}"
948
737
  )
949
-
950
738
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
951
-
952
- # Build the table specification and track fields to update
953
739
  table_spec: dict[str, Any] = {"tableId": table_id}
954
740
  fields_to_update = []
955
-
956
- # Add optional properties if provided
957
741
  if table_name is not None:
958
742
  table_spec["name"] = table_name
959
743
  fields_to_update.append("name")
960
-
961
- # Build range if any range parameters are provided
962
744
  range_params: dict[str, Any] = {}
963
745
  if start_row_index is not None:
964
746
  range_params["startRowIndex"] = start_row_index
@@ -968,38 +750,19 @@ class GoogleSheetApp(APIApplication):
968
750
  range_params["startColumnIndex"] = start_column_index
969
751
  if end_column_index is not None:
970
752
  range_params["endColumnIndex"] = end_column_index
971
-
972
753
  if range_params:
973
754
  table_spec["range"] = range_params
974
755
  fields_to_update.append("range")
975
-
976
- # Add column properties if provided
977
756
  if column_properties:
978
757
  table_spec["columnProperties"] = column_properties
979
758
  fields_to_update.append("columnProperties")
980
-
981
- # Validate that at least one field is being updated
982
759
  if not fields_to_update:
983
- raise ValueError(
984
- "At least one field must be provided for update (table_name, range indices, or column_properties)"
985
- )
986
-
987
- # Build the request body
988
- request_body = {
989
- "requests": [
990
- {
991
- "updateTable": {
992
- "table": table_spec,
993
- "fields": ",".join(fields_to_update),
994
- }
995
- }
996
- ]
997
- }
998
-
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)}}]}
999
762
  response = self._post(url, data=request_body)
1000
763
  return self._handle_response(response)
1001
764
 
1002
- def clear_values(self, spreadsheetId: str, range: str) -> dict[str, Any]:
765
+ async def clear_values(self, spreadsheetId: str, range: str) -> dict[str, Any]:
1003
766
  """
1004
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.
1005
768
 
@@ -1021,12 +784,8 @@ class GoogleSheetApp(APIApplication):
1021
784
  response = self._post(url, data={})
1022
785
  return self._handle_response(response)
1023
786
 
1024
- def update_values(
1025
- self,
1026
- spreadsheetId: str,
1027
- range: str,
1028
- values: list[list[Any]],
1029
- 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"
1030
789
  ) -> dict[str, Any]:
1031
790
  """
1032
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.
@@ -1053,11 +812,7 @@ class GoogleSheetApp(APIApplication):
1053
812
  response = self._put(url, data=data, params=params)
1054
813
  return self._handle_response(response)
1055
814
 
1056
- def batch_clear_values(
1057
- self,
1058
- spreadsheetId: str,
1059
- ranges: list[str],
1060
- ) -> dict[str, Any]:
815
+ async def batch_clear_values(self, spreadsheetId: str, ranges: list[str]) -> dict[str, Any]:
1061
816
  """
1062
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.
1063
818
 
@@ -1077,18 +832,14 @@ class GoogleSheetApp(APIApplication):
1077
832
  """
1078
833
  if not spreadsheetId:
1079
834
  raise ValueError("spreadsheetId cannot be empty")
1080
-
1081
835
  if not ranges or not isinstance(ranges, list) or len(ranges) == 0:
1082
836
  raise ValueError("ranges must be a non-empty list")
1083
-
1084
837
  url = f"{self.base_url}/{spreadsheetId}/values:batchClear"
1085
-
1086
838
  request_body = {"ranges": ranges}
1087
-
1088
839
  response = self._post(url, data=request_body)
1089
840
  return self._handle_response(response)
1090
841
 
1091
- def batch_get_values_by_data_filter(
842
+ async def batch_get_values_by_data_filter(
1092
843
  self,
1093
844
  spreadsheetId: str,
1094
845
  data_filters: list[dict],
@@ -1120,57 +871,26 @@ class GoogleSheetApp(APIApplication):
1120
871
  """
1121
872
  if not spreadsheetId:
1122
873
  raise ValueError("spreadsheetId cannot be empty")
1123
-
1124
- if (
1125
- not data_filters
1126
- or not isinstance(data_filters, list)
1127
- or len(data_filters) == 0
1128
- ):
874
+ if not data_filters or not isinstance(data_filters, list) or len(data_filters) == 0:
1129
875
  raise ValueError("data_filters must be a non-empty list")
1130
-
1131
876
  if major_dimension and major_dimension not in ["ROWS", "COLUMNS"]:
1132
877
  raise ValueError('major_dimension must be either "ROWS" or "COLUMNS"')
1133
-
1134
- if value_render_option and value_render_option not in [
1135
- "FORMATTED_VALUE",
1136
- "UNFORMATTED_VALUE",
1137
- "FORMULA",
1138
- ]:
1139
- raise ValueError(
1140
- 'value_render_option must be either "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA"'
1141
- )
1142
-
1143
- if date_time_render_option and date_time_render_option not in [
1144
- "SERIAL_NUMBER",
1145
- "FORMATTED_STRING",
1146
- ]:
1147
- raise ValueError(
1148
- 'date_time_render_option must be either "SERIAL_NUMBER" or "FORMATTED_STRING"'
1149
- )
1150
-
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"')
1151
882
  url = f"{self.base_url}/{spreadsheetId}/values:batchGetByDataFilter"
1152
-
1153
883
  request_body: dict[str, Any] = {"dataFilters": data_filters}
1154
-
1155
- # Add optional parameters if provided
1156
884
  if major_dimension:
1157
885
  request_body["majorDimension"] = major_dimension
1158
-
1159
886
  if value_render_option:
1160
887
  request_body["valueRenderOption"] = value_render_option
1161
-
1162
888
  if date_time_render_option:
1163
889
  request_body["dateTimeRenderOption"] = date_time_render_option
1164
-
1165
890
  response = self._post(url, data=request_body)
1166
891
  return self._handle_response(response)
1167
892
 
1168
- def copy_sheet_to_spreadsheet(
1169
- self,
1170
- spreadsheetId: str,
1171
- sheet_id: int,
1172
- destination_spreadsheetId: str,
1173
- ) -> dict[str, Any]:
893
+ async def copy_sheet_to_spreadsheet(self, spreadsheetId: str, sheet_id: int, destination_spreadsheetId: str) -> dict[str, Any]:
1174
894
  """
1175
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.
1176
896
 
@@ -1192,21 +912,16 @@ class GoogleSheetApp(APIApplication):
1192
912
  """
1193
913
  if not spreadsheetId:
1194
914
  raise ValueError("spreadsheetId cannot be empty")
1195
-
1196
915
  if sheet_id is None:
1197
916
  raise ValueError("sheet_id cannot be empty")
1198
-
1199
917
  if not destination_spreadsheetId:
1200
918
  raise ValueError("destination_spreadsheetId cannot be empty")
1201
-
1202
919
  url = f"{self.base_url}/{spreadsheetId}/sheets/{sheet_id}:copyTo"
1203
-
1204
920
  request_body = {"destinationSpreadsheetId": destination_spreadsheetId}
1205
-
1206
921
  response = self._post(url, data=request_body)
1207
922
  return self._handle_response(response)
1208
923
 
1209
- def write_values_to_sheet(
924
+ async def write_values_to_sheet(
1210
925
  self,
1211
926
  spreadsheetId: str,
1212
927
  sheet_name: str,
@@ -1238,39 +953,23 @@ class GoogleSheetApp(APIApplication):
1238
953
  """
1239
954
  if not spreadsheetId:
1240
955
  raise ValueError("spreadsheetId cannot be empty")
1241
-
1242
956
  if not sheet_name:
1243
957
  raise ValueError("sheet_name cannot be empty")
1244
-
1245
958
  if not values or not isinstance(values, list) or len(values) == 0:
1246
959
  raise ValueError("values must be a non-empty 2D list")
1247
-
1248
960
  if value_input_option not in ["RAW", "USER_ENTERED"]:
1249
- raise ValueError(
1250
- 'value_input_option must be either "RAW" or "USER_ENTERED"'
1251
- )
1252
-
1253
- # Determine the range based on first_cell_location
961
+ raise ValueError('value_input_option must be either "RAW" or "USER_ENTERED"')
1254
962
  if first_cell_location:
1255
- # Update specific range starting from first_cell_location
1256
963
  range_str = f"{sheet_name}!{first_cell_location}"
1257
964
  else:
1258
- # Append to the sheet (no specific range)
1259
965
  range_str = f"{sheet_name}"
1260
-
1261
966
  url = f"{self.base_url}/{spreadsheetId}/values/{range_str}"
1262
-
1263
- params = {
1264
- "valueInputOption": value_input_option,
1265
- "includeValuesInResponse": include_values_in_response,
1266
- }
1267
-
967
+ params = {"valueInputOption": value_input_option, "includeValuesInResponse": include_values_in_response}
1268
968
  data = {"values": values}
1269
-
1270
969
  response = self._put(url, data=data, params=params)
1271
970
  return self._handle_response(response)
1272
971
 
1273
- def append_values(
972
+ async def append_values(
1274
973
  self,
1275
974
  spreadsheetId: str,
1276
975
  range: str,
@@ -1306,74 +1005,35 @@ class GoogleSheetApp(APIApplication):
1306
1005
  """
1307
1006
  if not spreadsheetId:
1308
1007
  raise ValueError("spreadsheetId cannot be empty")
1309
-
1310
1008
  if not range:
1311
1009
  raise ValueError("range cannot be empty")
1312
-
1313
1010
  if not value_input_option:
1314
1011
  raise ValueError("value_input_option cannot be empty")
1315
-
1316
1012
  if value_input_option not in ["RAW", "USER_ENTERED"]:
1317
- raise ValueError(
1318
- 'value_input_option must be either "RAW" or "USER_ENTERED"'
1319
- )
1320
-
1013
+ raise ValueError('value_input_option must be either "RAW" or "USER_ENTERED"')
1321
1014
  if not values or not isinstance(values, list) or len(values) == 0:
1322
1015
  raise ValueError("values must be a non-empty 2D list")
1323
-
1324
- if insert_data_option and insert_data_option not in [
1325
- "OVERWRITE",
1326
- "INSERT_ROWS",
1327
- ]:
1328
- raise ValueError(
1329
- 'insert_data_option must be either "OVERWRITE" or "INSERT_ROWS"'
1330
- )
1331
-
1332
- if response_value_render_option and response_value_render_option not in [
1333
- "FORMATTED_VALUE",
1334
- "UNFORMATTED_VALUE",
1335
- "FORMULA",
1336
- ]:
1337
- raise ValueError(
1338
- 'response_value_render_option must be either "FORMATTED_VALUE", "UNFORMATTED_VALUE", or "FORMULA"'
1339
- )
1340
-
1341
- if (
1342
- response_date_time_render_option
1343
- and response_date_time_render_option
1344
- not in ["SERIAL_NUMBER", "FORMATTED_STRING"]
1345
- ):
1346
- raise ValueError(
1347
- 'response_date_time_render_option must be either "SERIAL_NUMBER" or "FORMATTED_STRING"'
1348
- )
1349
-
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"')
1350
1022
  url = f"{self.base_url}/{spreadsheetId}/values/{range}:append"
1351
-
1352
1023
  params: dict[str, Any] = {"valueInputOption": value_input_option}
1353
-
1354
- # Add optional parameters if provided
1355
1024
  if insert_data_option:
1356
1025
  params["insertDataOption"] = insert_data_option
1357
-
1358
1026
  if include_values_in_response is not None:
1359
1027
  params["includeValuesInResponse"] = include_values_in_response
1360
-
1361
1028
  if response_value_render_option:
1362
1029
  params["responseValueRenderOption"] = response_value_render_option
1363
-
1364
1030
  if response_date_time_render_option:
1365
1031
  params["responseDateTimeRenderOption"] = response_date_time_render_option
1366
-
1367
1032
  data = {"values": values}
1368
-
1369
1033
  response = self._post(url, data=data, params=params)
1370
1034
  return self._handle_response(response)
1371
1035
 
1372
- def clear_basic_filter(
1373
- self,
1374
- spreadsheetId: str,
1375
- sheet_id: int,
1376
- ) -> dict[str, Any]:
1036
+ async def clear_basic_filter(self, spreadsheetId: str, sheet_id: int) -> dict[str, Any]:
1377
1037
  """
1378
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.
1379
1039
 
@@ -1393,22 +1053,14 @@ class GoogleSheetApp(APIApplication):
1393
1053
  """
1394
1054
  if not spreadsheetId:
1395
1055
  raise ValueError("spreadsheetId cannot be empty")
1396
-
1397
1056
  if sheet_id < 0:
1398
1057
  raise ValueError("sheet_id must be non-negative")
1399
-
1400
1058
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1401
-
1402
1059
  request_body = {"requests": [{"clearBasicFilter": {"sheetId": sheet_id}}]}
1403
-
1404
1060
  response = self._post(url, data=request_body)
1405
1061
  return self._handle_response(response)
1406
1062
 
1407
- def delete_sheet(
1408
- self,
1409
- spreadsheetId: str,
1410
- sheet_id: int,
1411
- ) -> dict[str, Any]:
1063
+ async def delete_sheet(self, spreadsheetId: str, sheet_id: int) -> dict[str, Any]:
1412
1064
  """
1413
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.
1414
1066
 
@@ -1428,23 +1080,15 @@ class GoogleSheetApp(APIApplication):
1428
1080
  """
1429
1081
  if not spreadsheetId:
1430
1082
  raise ValueError("spreadsheetId cannot be empty")
1431
-
1432
1083
  if sheet_id < 0:
1433
1084
  raise ValueError("sheet_id must be non-negative")
1434
-
1435
1085
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1436
-
1437
1086
  request_body = {"requests": [{"deleteSheet": {"sheetId": sheet_id}}]}
1438
-
1439
1087
  response = self._post(url, data=request_body)
1440
1088
  return self._handle_response(response)
1441
1089
 
1442
- def discover_tables(
1443
- self,
1444
- spreadsheetId: str,
1445
- min_rows: int = 2,
1446
- min_columns: int = 1,
1447
- 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
1448
1092
  ) -> dict[str, Any]:
1449
1093
  """
1450
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.
@@ -1467,55 +1111,31 @@ class GoogleSheetApp(APIApplication):
1467
1111
  """
1468
1112
  if not spreadsheetId:
1469
1113
  raise ValueError("spreadsheetId cannot be empty")
1470
-
1471
1114
  if min_rows < 1:
1472
1115
  raise ValueError("min_rows must be at least 1")
1473
-
1474
1116
  if min_columns < 1:
1475
1117
  raise ValueError("min_columns must be at least 1")
1476
-
1477
1118
  if not 0 <= min_confidence <= 1:
1478
1119
  raise ValueError("min_confidence must be between 0.0 and 1.0")
1479
-
1480
- # Get spreadsheet structure
1481
- spreadsheet = self.get_spreadsheet_metadata(spreadsheetId)
1482
-
1120
+ spreadsheet = await self.get_spreadsheet_metadata(spreadsheetId)
1483
1121
  tables = []
1484
-
1485
1122
  for sheet in spreadsheet.get("sheets", []):
1486
1123
  sheet_properties = sheet.get("properties", {})
1487
1124
  sheet_id = sheet_properties.get("sheetId")
1488
1125
  sheet_title = sheet_properties.get("title", "Sheet1")
1489
-
1490
- # Analyze sheet for tables using helper function
1491
1126
  sheet_tables = analyze_sheet_for_tables(
1492
- self.get_values, # Pass the get_values method as a function
1493
- spreadsheetId,
1494
- sheet_id,
1495
- sheet_title,
1496
- min_rows,
1497
- min_columns,
1498
- min_confidence,
1127
+ self.get_values, spreadsheetId, sheet_id, sheet_title, min_rows, min_columns, min_confidence
1499
1128
  )
1500
-
1501
1129
  tables.extend(sheet_tables)
1502
-
1503
1130
  return {
1504
1131
  "spreadsheetId": spreadsheetId,
1505
1132
  "total_tables": len(tables),
1506
1133
  "tables": tables,
1507
- "analysis_parameters": {
1508
- "min_rows": min_rows,
1509
- "min_columns": min_columns,
1510
- },
1134
+ "analysis_parameters": {"min_rows": min_rows, "min_columns": min_columns},
1511
1135
  }
1512
1136
 
1513
- def analyze_table_schema(
1514
- self,
1515
- spreadsheetId: str,
1516
- table_name: str,
1517
- sheet_name: str | None = None,
1518
- 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
1519
1139
  ) -> dict[str, Any]:
1520
1140
  """
1521
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.
@@ -1538,28 +1158,17 @@ class GoogleSheetApp(APIApplication):
1538
1158
  """
1539
1159
  if not spreadsheetId:
1540
1160
  raise ValueError("spreadsheetId cannot be empty")
1541
-
1542
1161
  if not table_name:
1543
1162
  raise ValueError("table_name cannot be empty")
1544
-
1545
1163
  if not 1 <= sample_size <= 1000:
1546
1164
  raise ValueError("sample_size must be between 1 and 1000")
1547
-
1548
- # Get spreadsheet structure
1549
- spreadsheet = self.get_spreadsheet_metadata(spreadsheetId)
1550
-
1551
- # Find the target table
1165
+ spreadsheet = await self.get_spreadsheet_metadata(spreadsheetId)
1552
1166
  target_table = None
1553
-
1554
1167
  for sheet in spreadsheet.get("sheets", []):
1555
1168
  sheet_properties = sheet.get("properties", {})
1556
1169
  sheet_title = sheet_properties.get("title", "Sheet1")
1557
-
1558
- # If sheet_name is specified, only look in that sheet
1559
1170
  if sheet_name and sheet_title != sheet_name:
1560
1171
  continue
1561
-
1562
- # Get tables in this sheet
1563
1172
  sheet_tables = analyze_sheet_for_tables(
1564
1173
  self.get_values,
1565
1174
  spreadsheetId,
@@ -1569,35 +1178,20 @@ class GoogleSheetApp(APIApplication):
1569
1178
  min_columns=1,
1570
1179
  min_confidence=0.3,
1571
1180
  )
1572
-
1573
1181
  for table in sheet_tables:
1574
1182
  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
- ):
1183
+ if target_table is None or table["rows"] * table["columns"] > target_table["rows"] * target_table["columns"]:
1580
1184
  target_table = table
1581
1185
  elif table["table_name"] == table_name:
1582
1186
  target_table = table
1583
1187
  break
1584
-
1585
1188
  if target_table and table_name != "auto":
1586
1189
  break
1587
-
1588
1190
  if not target_table:
1589
1191
  raise ValueError(f"Table '{table_name}' not found in spreadsheet")
1192
+ return analyze_table_schema(self.get_values, spreadsheetId, target_table, sample_size)
1590
1193
 
1591
- # Use the helper function to analyze the table schema
1592
- return analyze_table_schema(
1593
- self.get_values, spreadsheetId, target_table, sample_size
1594
- )
1595
-
1596
- def set_basic_filter(
1597
- self,
1598
- spreadsheetId: str,
1599
- filter: dict,
1600
- ) -> dict[str, Any]:
1194
+ async def set_basic_filter(self, spreadsheetId: str, filter: dict) -> dict[str, Any]:
1601
1195
  """
1602
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.
1603
1197
 
@@ -1626,27 +1220,19 @@ class GoogleSheetApp(APIApplication):
1626
1220
  """
1627
1221
  if not spreadsheetId:
1628
1222
  raise ValueError("spreadsheetId cannot be empty")
1629
-
1630
1223
  if not filter:
1631
1224
  raise ValueError("filter cannot be empty")
1632
-
1633
- # Validate required filter fields
1634
1225
  if "range" not in filter:
1635
1226
  raise ValueError("filter must contain 'range' field")
1636
-
1637
- # Validate required filter fields using Google API naming convention
1638
1227
  range_data = filter["range"]
1639
1228
  if "sheetId" not in range_data:
1640
1229
  raise ValueError("filter range must contain 'sheetId' field")
1641
-
1642
1230
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1643
-
1644
1231
  request_body = {"requests": [{"setBasicFilter": {"filter": filter}}]}
1645
-
1646
1232
  response = self._post(url, data=request_body)
1647
1233
  return self._handle_response(response)
1648
1234
 
1649
- def format_cells(
1235
+ async def format_cells(
1650
1236
  self,
1651
1237
  spreadsheetId: str,
1652
1238
  worksheetId: int,
@@ -1654,34 +1240,26 @@ class GoogleSheetApp(APIApplication):
1654
1240
  startColumnIndex: int,
1655
1241
  endRowIndex: int,
1656
1242
  endColumnIndex: int,
1657
- # Text formatting
1658
1243
  bold: bool | None = None,
1659
1244
  italic: bool | None = None,
1660
1245
  underline: bool | None = None,
1661
1246
  strikethrough: bool | None = None,
1662
1247
  fontSize: int | None = None,
1663
1248
  fontFamily: str | None = None,
1664
- # Colors
1665
1249
  backgroundRed: float | None = None,
1666
1250
  backgroundGreen: float | None = None,
1667
1251
  backgroundBlue: float | None = None,
1668
1252
  textRed: float | None = None,
1669
1253
  textGreen: float | None = None,
1670
1254
  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
1255
+ horizontalAlignment: str | None = None,
1256
+ verticalAlignment: str | None = None,
1257
+ wrapStrategy: str | None = None,
1678
1258
  numberFormat: str | None = None,
1679
- # Borders
1680
1259
  borderTop: dict | None = None,
1681
1260
  borderBottom: dict | None = None,
1682
1261
  borderLeft: dict | None = None,
1683
1262
  borderRight: dict | None = None,
1684
- # Merge cells
1685
1263
  mergeCells: bool = False,
1686
1264
  ) -> dict[str, Any]:
1687
1265
  """
@@ -1737,25 +1315,14 @@ class GoogleSheetApp(APIApplication):
1737
1315
  """
1738
1316
  if not spreadsheetId:
1739
1317
  raise ValueError("spreadsheetId cannot be empty")
1740
-
1741
1318
  if worksheetId < 0:
1742
1319
  raise ValueError("worksheetId must be non-negative")
1743
-
1744
- if (
1745
- startRowIndex < 0
1746
- or startColumnIndex < 0
1747
- or endRowIndex < 0
1748
- or endColumnIndex < 0
1749
- ):
1320
+ if startRowIndex < 0 or startColumnIndex < 0 or endRowIndex < 0 or (endColumnIndex < 0):
1750
1321
  raise ValueError("All indices must be non-negative")
1751
-
1752
1322
  if startRowIndex >= endRowIndex:
1753
1323
  raise ValueError("endRowIndex must be greater than startRowIndex")
1754
-
1755
1324
  if startColumnIndex >= endColumnIndex:
1756
1325
  raise ValueError("endColumnIndex must be greater than startColumnIndex")
1757
-
1758
- # Validate color values if provided
1759
1326
  for color_name, color_value in [
1760
1327
  ("backgroundRed", backgroundRed),
1761
1328
  ("backgroundGreen", backgroundGreen),
@@ -1764,37 +1331,18 @@ class GoogleSheetApp(APIApplication):
1764
1331
  ("textGreen", textGreen),
1765
1332
  ("textBlue", textBlue),
1766
1333
  ]:
1767
- 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):
1768
1335
  raise ValueError(f"{color_name} must be between 0.0 and 1.0")
1769
-
1770
1336
  if fontSize is not None and fontSize <= 0:
1771
1337
  raise ValueError("fontSize must be positive")
1772
-
1773
- if horizontalAlignment and horizontalAlignment not in [
1774
- "LEFT",
1775
- "CENTER",
1776
- "RIGHT",
1777
- ]:
1338
+ if horizontalAlignment and horizontalAlignment not in ["LEFT", "CENTER", "RIGHT"]:
1778
1339
  raise ValueError('horizontalAlignment must be "LEFT", "CENTER", or "RIGHT"')
1779
-
1780
1340
  if verticalAlignment and verticalAlignment not in ["TOP", "MIDDLE", "BOTTOM"]:
1781
1341
  raise ValueError('verticalAlignment must be "TOP", "MIDDLE", or "BOTTOM"')
1782
-
1783
- if wrapStrategy and wrapStrategy not in [
1784
- "OVERFLOW_CELL",
1785
- "LEGACY_WRAP",
1786
- "CLIP",
1787
- "WRAP",
1788
- ]:
1789
- raise ValueError(
1790
- 'wrapStrategy must be "OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", or "WRAP"'
1791
- )
1792
-
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"')
1793
1344
  url = f"{self.base_url}/{spreadsheetId}:batchUpdate"
1794
-
1795
1345
  requests = []
1796
-
1797
- # Handle cell merging first if requested
1798
1346
  if mergeCells:
1799
1347
  requests.append(
1800
1348
  {
@@ -1810,11 +1358,7 @@ class GoogleSheetApp(APIApplication):
1810
1358
  }
1811
1359
  }
1812
1360
  )
1813
-
1814
- # Build the cell format request
1815
1361
  cell_format = {}
1816
-
1817
- # Text format
1818
1362
  text_format = {}
1819
1363
  if bold is not None:
1820
1364
  text_format["bold"] = bold
@@ -1828,9 +1372,7 @@ class GoogleSheetApp(APIApplication):
1828
1372
  text_format["fontSize"] = fontSize
1829
1373
  if fontFamily is not None:
1830
1374
  text_format["fontFamily"] = fontFamily
1831
-
1832
- # Text color
1833
- 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])):
1834
1376
  text_color = {}
1835
1377
  if textRed is not None:
1836
1378
  text_color["red"] = textRed
@@ -1840,15 +1382,9 @@ class GoogleSheetApp(APIApplication):
1840
1382
  text_color["blue"] = textBlue
1841
1383
  if text_color:
1842
1384
  text_format["foregroundColor"] = {"rgbColor": text_color}
1843
-
1844
1385
  if text_format:
1845
1386
  cell_format["textFormat"] = text_format
1846
-
1847
- # Background color
1848
- if any(
1849
- color is not None
1850
- for color in [backgroundRed, backgroundGreen, backgroundBlue]
1851
- ):
1387
+ if any((color is not None for color in [backgroundRed, backgroundGreen, backgroundBlue])):
1852
1388
  background_color = {}
1853
1389
  if backgroundRed is not None:
1854
1390
  background_color["red"] = backgroundRed
@@ -1858,35 +1394,19 @@ class GoogleSheetApp(APIApplication):
1858
1394
  background_color["blue"] = backgroundBlue
1859
1395
  if background_color:
1860
1396
  cell_format["backgroundColorStyle"] = {"rgbColor": background_color}
1861
-
1862
- # Alignment
1863
1397
  if horizontalAlignment or verticalAlignment:
1864
1398
  cell_format["horizontalAlignment"] = horizontalAlignment
1865
1399
  cell_format["verticalAlignment"] = verticalAlignment
1866
-
1867
- # Text wrapping
1868
1400
  if wrapStrategy:
1869
1401
  cell_format["wrapStrategy"] = wrapStrategy
1870
-
1871
- # Number format
1872
1402
  if numberFormat:
1873
1403
  cell_format["numberFormat"] = {"type": "TEXT", "pattern": numberFormat}
1874
-
1875
- # Borders
1876
1404
  borders = {}
1877
- for border_side, border_config in [
1878
- ("top", borderTop),
1879
- ("bottom", borderBottom),
1880
- ("left", borderLeft),
1881
- ("right", borderRight),
1882
- ]:
1405
+ for border_side, border_config in [("top", borderTop), ("bottom", borderBottom), ("left", borderLeft), ("right", borderRight)]:
1883
1406
  if border_config:
1884
1407
  borders[border_side] = border_config
1885
-
1886
1408
  if borders:
1887
1409
  cell_format["borders"] = borders
1888
-
1889
- # Add cell formatting request if any formatting is specified
1890
1410
  if cell_format:
1891
1411
  requests.append(
1892
1412
  {
@@ -1903,7 +1423,6 @@ class GoogleSheetApp(APIApplication):
1903
1423
  }
1904
1424
  }
1905
1425
  )
1906
-
1907
1426
  request_body = {"requests": requests}
1908
1427
  response = self._post(url, data=request_body)
1909
1428
  return self._handle_response(response)