arcade-google-sheets 2.0.0rc1__py3-none-any.whl → 3.1.0__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.
@@ -2,17 +2,20 @@ from typing import Annotated
2
2
 
3
3
  from arcade_tdk import ToolContext, tool
4
4
  from arcade_tdk.auth import Google
5
- from arcade_tdk.errors import RetryableToolError
6
5
 
6
+ from arcade_google_sheets.converters import SheetDataInputToValueRangesConverter
7
7
  from arcade_google_sheets.models import (
8
- SheetDataInput,
9
8
  Spreadsheet,
10
9
  SpreadsheetProperties,
11
10
  )
12
11
  from arcade_google_sheets.utils import (
12
+ batch_update,
13
13
  build_sheets_service,
14
+ col_to_index,
14
15
  create_sheet,
16
+ get_sheet_metadata_from_identifier,
15
17
  parse_write_to_cell_response,
18
+ validate_sheet_data_input,
16
19
  validate_write_to_cell_params,
17
20
  )
18
21
 
@@ -40,15 +43,7 @@ def create_spreadsheet(
40
43
  """
41
44
  service = build_sheets_service(context.get_auth_token_or_empty())
42
45
 
43
- try:
44
- sheet_data = SheetDataInput(data=data) # type: ignore[arg-type]
45
- except Exception as e:
46
- msg = "Invalid JSON or unexpected data format for parameter `data`"
47
- raise RetryableToolError(
48
- message=msg,
49
- additional_prompt_content=f"{msg}: {e}",
50
- retry_after_ms=100,
51
- )
46
+ sheet_data = validate_sheet_data_input(data)
52
47
 
53
48
  spreadsheet = Spreadsheet(
54
49
  properties=SpreadsheetProperties(title=title),
@@ -112,3 +107,120 @@ def write_to_cell(
112
107
  )
113
108
 
114
109
  return parse_write_to_cell_response(sheet_properties)
110
+
111
+
112
+ @tool(
113
+ requires_auth=Google(
114
+ scopes=["https://www.googleapis.com/auth/drive.file"],
115
+ )
116
+ )
117
+ def update_cells(
118
+ context: ToolContext,
119
+ spreadsheet_id: Annotated[str, "The id of the spreadsheet to write to"],
120
+ data: Annotated[
121
+ str,
122
+ "The data to write. A JSON string (property names enclosed in double quotes) "
123
+ "representing a dictionary that maps row numbers to dictionaries that map "
124
+ "column letters to cell values. For example, data[23]['C'] is the value for cell C23. "
125
+ "This is the same format accepted by create_spreadsheet. "
126
+ "Type hint: dict[int, dict[str, int | float | str | bool]]",
127
+ ],
128
+ sheet_position: Annotated[
129
+ int | None,
130
+ "The position/tab of the sheet in the spreadsheet to write to. "
131
+ "A value of 1 represents the first (leftmost/Sheet1) sheet. "
132
+ "Defaults to 1.",
133
+ ] = 1,
134
+ sheet_id_or_name: Annotated[
135
+ str | None,
136
+ "The id or name of the sheet to write to. If provided, takes "
137
+ "precedence over sheet_position.",
138
+ ] = None,
139
+ ) -> Annotated[dict, "The status of the operation, including updated ranges and counts"]:
140
+ """
141
+ Write values to a Google Sheet using a flexible data format.
142
+
143
+ sheet_id_or_name takes precedence over sheet_position. If a sheet is not mentioned,
144
+ then always assume the default sheet_position is sufficient.
145
+ """
146
+ service = build_sheets_service(context.get_auth_token_or_empty())
147
+
148
+ sheet_data = validate_sheet_data_input(data)
149
+ sheet_name, sheet_id, sheet_url = get_sheet_metadata_from_identifier(
150
+ service, spreadsheet_id, sheet_position, sheet_id_or_name
151
+ )
152
+ converter = SheetDataInputToValueRangesConverter(sheet_name, sheet_data)
153
+ value_ranges = converter.convert()
154
+
155
+ response = batch_update(service, spreadsheet_id, value_ranges)
156
+
157
+ return {**response, "sheet_url": sheet_url, "sheet_id": sheet_id}
158
+
159
+
160
+ @tool(
161
+ requires_auth=Google(
162
+ scopes=["https://www.googleapis.com/auth/drive.file"],
163
+ )
164
+ )
165
+ def add_note_to_cell(
166
+ context: ToolContext,
167
+ spreadsheet_id: Annotated[str, "The id of the spreadsheet to add a comment to"],
168
+ column: Annotated[str, "The column string to add a note to. For example, 'A', 'F', or 'AZ'"],
169
+ row: Annotated[int, "The row number to add a note to"],
170
+ note_text: Annotated[str, "The text for the note to add"],
171
+ sheet_position: Annotated[
172
+ int | None,
173
+ "The position/tab of the sheet in the spreadsheet to write to. "
174
+ "A value of 1 represents the first (leftmost/Sheet1) sheet. "
175
+ "Defaults to 1.",
176
+ ] = 1,
177
+ sheet_id_or_name: Annotated[
178
+ str | None,
179
+ "The id or name of the sheet to write to. If provided, takes "
180
+ "precedence over sheet_position.",
181
+ ] = None,
182
+ ) -> Annotated[dict, "The status of the operation"]:
183
+ """
184
+ Add a note to a specific cell in a spreadsheet. A note is a small
185
+ piece of text attached to a cell (shown with a black triangle) that
186
+ appears when you hover over the cell.
187
+
188
+ sheet_id_or_name takes precedence over sheet_position. If a sheet is not mentioned,
189
+ then always assume the default sheet_position is sufficient.
190
+ """
191
+ service = build_sheets_service(context.get_auth_token_or_empty())
192
+
193
+ sheet_name, sheet_id, sheet_url = get_sheet_metadata_from_identifier(
194
+ service, spreadsheet_id, sheet_position, sheet_id_or_name
195
+ )
196
+ column_index = col_to_index(column)
197
+
198
+ service.spreadsheets().batchUpdate(
199
+ spreadsheetId=spreadsheet_id,
200
+ body={
201
+ "requests": [
202
+ {
203
+ "repeatCell": {
204
+ "range": {
205
+ "sheetId": sheet_id,
206
+ "startRowIndex": row - 1,
207
+ "endRowIndex": row,
208
+ "startColumnIndex": column_index,
209
+ "endColumnIndex": column_index + 1,
210
+ },
211
+ "cell": {
212
+ "note": note_text,
213
+ },
214
+ "fields": "note",
215
+ },
216
+ }
217
+ ]
218
+ },
219
+ ).execute()
220
+
221
+ return {
222
+ "status": "success",
223
+ "sheet_url": sheet_url,
224
+ "sheet_id": sheet_id,
225
+ "sheet_name": sheet_name,
226
+ }
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import string
2
3
  from typing import Any
3
4
 
4
5
  from arcade_tdk.errors import RetryableToolError, ToolExecutionError
@@ -9,7 +10,12 @@ from arcade_google_sheets.constants import (
9
10
  DEFAULT_SHEET_COLUMN_COUNT,
10
11
  DEFAULT_SHEET_ROW_COUNT,
11
12
  )
12
- from arcade_google_sheets.enums import NumberFormatType
13
+ from arcade_google_sheets.enums import (
14
+ Corpora,
15
+ NumberFormatType,
16
+ OrderBy,
17
+ SheetIdentifierType,
18
+ )
13
19
  from arcade_google_sheets.models import (
14
20
  CellData,
15
21
  CellExtendedValue,
@@ -21,7 +27,10 @@ from arcade_google_sheets.models import (
21
27
  Sheet,
22
28
  SheetDataInput,
23
29
  SheetProperties,
30
+ Spreadsheet,
31
+ ValueRange,
24
32
  )
33
+ from arcade_google_sheets.templates import sheet_url_template
25
34
  from arcade_google_sheets.types import CellValue
26
35
 
27
36
  logging.basicConfig(
@@ -32,6 +41,15 @@ logging.basicConfig(
32
41
  logger = logging.getLogger(__name__)
33
42
 
34
43
 
44
+ def remove_none_values(params: dict) -> dict:
45
+ """
46
+ Remove None values from a dictionary.
47
+ :param params: The dictionary to clean
48
+ :return: A new dictionary with None values removed
49
+ """
50
+ return {k: v for k, v in params.items() if v is not None}
51
+
52
+
35
53
  def build_sheets_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
36
54
  """
37
55
  Build a Sheets service object.
@@ -40,6 +58,14 @@ def build_sheets_service(auth_token: str | None) -> Resource: # type: ignore[no
40
58
  return build("sheets", "v4", credentials=Credentials(auth_token))
41
59
 
42
60
 
61
+ def build_drive_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
62
+ """
63
+ Build a Drive service object.
64
+ """
65
+ auth_token = auth_token or ""
66
+ return build("drive", "v3", credentials=Credentials(auth_token))
67
+
68
+
43
69
  def col_to_index(col: str) -> int:
44
70
  """Convert a sheet's column string to a 0-indexed column index
45
71
 
@@ -459,6 +485,34 @@ def convert_api_grid_data_to_dict(grids: list[dict]) -> dict:
459
485
  return dict(sorted(result.items()))
460
486
 
461
487
 
488
+ def validate_sheet_data_input(data: str | None) -> SheetDataInput:
489
+ """
490
+ Validate and convert data to SheetDataInput, raising RetryableToolError on validation failure.
491
+ `data` is a JSON string representing a dictionary that maps row numbers to dictionaries that map
492
+ column letters to cell values.
493
+
494
+ Args:
495
+ data: The data parameter to validate, a JSON string representing a dictionary that maps
496
+ row numbers to dictionaries that map column letters to cell values.
497
+ Type hint: dict[int, dict[str, int | float | str | bool]]
498
+
499
+ Returns:
500
+ SheetDataInput: The validated sheet data input object
501
+
502
+ Raises:
503
+ RetryableToolError: If the data is invalid JSON or has an unexpected format
504
+ """
505
+ try:
506
+ return SheetDataInput(data=data) # type: ignore[arg-type]
507
+ except Exception as e:
508
+ msg = "Invalid JSON or unexpected data format for parameter `data`"
509
+ raise RetryableToolError(
510
+ message=msg,
511
+ additional_prompt_content=f"{msg}: {e}",
512
+ retry_after_ms=100,
513
+ )
514
+
515
+
462
516
  def validate_write_to_cell_params( # type: ignore[no-any-unimported]
463
517
  service: Resource,
464
518
  spreadsheet_id: str,
@@ -546,3 +600,424 @@ def parse_write_to_cell_response(response: dict) -> dict:
546
600
  "updatedCell": response["updatedData"]["range"].split("!")[1],
547
601
  "value": response["updatedData"]["values"][0][0],
548
602
  }
603
+
604
+
605
+ def calculate_a1_sheet_range(
606
+ sheet_name: str,
607
+ sheet_row_count: int,
608
+ sheet_col_count: int,
609
+ start_row: int,
610
+ start_col: str,
611
+ max_rows: int,
612
+ max_cols: int,
613
+ ) -> str | None:
614
+ """Calculate a single range for a sheet based on start position and limits.
615
+
616
+ Args:
617
+ sheet_name (str): The name of the sheet.
618
+ sheet_row_count (int): The number of rows in the sheet.
619
+ sheet_col_count (int): The number of columns in the sheet.
620
+ start_row (int): The row from which to start fetching data.
621
+ start_col (str): The column letter(s) from which to start fetching data.
622
+ max_rows (int): The maximum number of rows to fetch.
623
+ max_cols (int): The maximum number of columns to fetch.
624
+
625
+ Returns:
626
+ str | None: The A1 range for the sheet, or None if there is no data to fetch.
627
+ """
628
+ start_col_index = col_to_index(start_col)
629
+
630
+ effective_max_rows = min(sheet_row_count, max_rows or sheet_row_count)
631
+ effective_max_cols = min(sheet_col_count, max_cols or sheet_col_count)
632
+
633
+ end_row = min(start_row + effective_max_rows - 1, sheet_row_count)
634
+ end_col_index = min(start_col_index + effective_max_cols - 1, sheet_col_count - 1)
635
+
636
+ # Only create a range if there's actually data to fetch
637
+ if start_row <= end_row and start_col_index <= end_col_index:
638
+ range_start = f"{index_to_col(start_col_index)}{start_row}"
639
+ range_end = f"{index_to_col(end_col_index)}{end_row}"
640
+ return f"'{sheet_name}'!{range_start}:{range_end}"
641
+
642
+ return None
643
+
644
+
645
+ def get_sheet_by_identifier(
646
+ sheets: list[Sheet], sheet_identifier: str, sheet_identifier_type: SheetIdentifierType
647
+ ) -> Sheet | None:
648
+ """
649
+ Find a sheet by identifier (name, sheet ID, or 1-based position index).
650
+
651
+ Args:
652
+ sheets (list): List of Sheet objects from the spreadsheet.
653
+ sheet_identifier (str): The identifier of the sheet to get.
654
+ sheet_identifier_type (SheetIdentifierType): The type of the identifier.
655
+
656
+ Returns:
657
+ Sheet | None: The matching sheet, or None if not found.
658
+ """
659
+ if sheet_identifier_type == SheetIdentifierType.POSITION:
660
+ index = int(sheet_identifier) - 1
661
+ if 0 <= index < len(sheets):
662
+ return sheets[index]
663
+
664
+ if sheet_identifier_type == SheetIdentifierType.ID_OR_NAME:
665
+ for sheet in sheets:
666
+ sheet_title = sheet.properties.title
667
+ sheet_id = sheet.properties.sheetId
668
+ if (
669
+ sheet_title.casefold() == sheet_identifier.casefold()
670
+ or str(sheet_id).casefold() == sheet_identifier.casefold()
671
+ ):
672
+ return sheet
673
+
674
+ return None
675
+
676
+
677
+ def get_spreadsheet_metadata_helper(sheets_service: Resource, spreadsheet_id: str) -> Spreadsheet: # type: ignore[no-any-unimported]
678
+ """Get the spreadsheet metadata to collect the sheet names and dimensions
679
+
680
+ Args:
681
+ sheets_service (Resource): The Google Sheets service.
682
+ spreadsheet_id (str): The ID of the spreadsheet provided to the tool.
683
+
684
+ Returns:
685
+ Spreadsheet: The spreadsheet with only the metadata.
686
+ """
687
+ metadata_response = (
688
+ sheets_service.spreadsheets()
689
+ .get(
690
+ spreadsheetId=spreadsheet_id,
691
+ includeGridData=False,
692
+ fields="spreadsheetId,spreadsheetUrl,properties/title,sheets/properties",
693
+ )
694
+ .execute()
695
+ )
696
+ return Spreadsheet.model_validate(metadata_response)
697
+
698
+
699
+ def batch_update(service: Resource, spreadsheet_id: str, data: list[ValueRange]) -> dict: # type: ignore[no-any-unimported]
700
+ """
701
+ Batch update a spreadsheet with a list of ValueRanges.
702
+
703
+ Args:
704
+ service (Resource): The Google Sheets service.
705
+ spreadsheet_id (str): The ID of the spreadsheet to update.
706
+ data (list[ValueRange]): The data to update the spreadsheet with.
707
+
708
+ Returns:
709
+ dict: The response from the batch update.
710
+ """
711
+ body = {
712
+ "valueInputOption": "USER_ENTERED",
713
+ "data": [value_range.model_dump() for value_range in data],
714
+ }
715
+ response = (
716
+ service.spreadsheets()
717
+ .values()
718
+ .batchUpdate(spreadsheetId=spreadsheet_id, body=body)
719
+ .execute()
720
+ )
721
+ updated_ranges = [
722
+ value_response["updatedRange"] for value_response in response.get("responses", [])
723
+ ]
724
+ return {
725
+ "spreadsheet_id": response["spreadsheetId"],
726
+ "total_updated_rows": response["totalUpdatedRows"],
727
+ "total_updated_columns": response["totalUpdatedColumns"],
728
+ "total_updated_cells": response["totalUpdatedCells"],
729
+ "updated_ranges": updated_ranges,
730
+ }
731
+
732
+
733
+ def get_spreadsheet_with_pagination( # type: ignore[no-any-unimported]
734
+ service: Resource,
735
+ spreadsheet_id: str,
736
+ sheet_identifier: str,
737
+ sheet_identifier_type: SheetIdentifierType,
738
+ start_row: int,
739
+ start_col: str,
740
+ max_rows: int,
741
+ max_cols: int,
742
+ ) -> dict:
743
+ """
744
+ Get spreadsheet data with pagination support for large spreadsheets.
745
+
746
+ Args:
747
+ service (Resource): The Google Sheets service.
748
+ spreadsheet_id (str): The ID of the spreadsheet provided to the tool.
749
+ sheet_position (int | None): The position/tab of the sheet to get.
750
+ sheet_id_or_name (str | None): The id or name of the sheet to get.
751
+ start_row (int): The row from which to start fetching data.
752
+ start_col (str): The column letter(s) from which to start fetching data.
753
+ max_rows (int): The maximum number of rows to fetch.
754
+ max_cols (int): The maximum number of columns to fetch.
755
+
756
+ Returns:
757
+ dict: The spreadsheet data for the specified sheet in the spreadsheet.
758
+
759
+ """
760
+
761
+ # First, only get the spreadsheet metadata to collect the sheet names and dimensions
762
+ spreadsheet_with_only_metadata = get_spreadsheet_metadata_helper(service, spreadsheet_id)
763
+
764
+ target_sheet = get_sheet_by_identifier(
765
+ spreadsheet_with_only_metadata.sheets, sheet_identifier, sheet_identifier_type
766
+ )
767
+ if not target_sheet:
768
+ raise ToolExecutionError(
769
+ message=f"Sheet with identifier '{sheet_identifier}' not found",
770
+ developer_message=(
771
+ "Sheet(s) in the spreadsheet: "
772
+ + ", ".join([
773
+ sheet.model_dump_json() for sheet in spreadsheet_with_only_metadata.sheets
774
+ ])
775
+ ),
776
+ )
777
+
778
+ a1_ranges = []
779
+ sheet_name = target_sheet.properties.title
780
+ grid_props = target_sheet.properties.gridProperties
781
+ if grid_props:
782
+ sheet_row_count = grid_props.rowCount
783
+ sheet_col_count = grid_props.columnCount
784
+
785
+ curr_range = calculate_a1_sheet_range(
786
+ sheet_name,
787
+ sheet_row_count,
788
+ sheet_col_count,
789
+ start_row,
790
+ start_col,
791
+ max_rows,
792
+ max_cols,
793
+ )
794
+ if curr_range:
795
+ a1_ranges.append(curr_range)
796
+
797
+ # Next, get the data for the ranges
798
+ if a1_ranges:
799
+ response = (
800
+ service.spreadsheets()
801
+ .get(
802
+ spreadsheetId=spreadsheet_id,
803
+ includeGridData=True,
804
+ ranges=a1_ranges,
805
+ fields="spreadsheetId,spreadsheetUrl,properties/title,sheets/properties,sheets/data/rowData/values/userEnteredValue,sheets/data/rowData/values/formattedValue,sheets/data/rowData/values/effectiveValue",
806
+ )
807
+ .execute()
808
+ )
809
+ else:
810
+ response = spreadsheet_with_only_metadata.model_dump()
811
+
812
+ return parse_get_spreadsheet_response(response)
813
+
814
+
815
+ def process_get_spreadsheet_params(
816
+ sheet_position: int | None,
817
+ sheet_id_or_name: str | None,
818
+ start_row: int,
819
+ start_col: str,
820
+ max_rows: int,
821
+ max_cols: int,
822
+ ) -> tuple[str, SheetIdentifierType, int, str, int, int]:
823
+ """Process and validate the input parameters for the get_spreadsheet tool.
824
+
825
+ Args:
826
+ sheet_position (int | None): The position/tab of the sheet to get.
827
+ sheet_id_or_name (str | None): The id or name of the sheet to get.
828
+ start_row (int): Processed to be within the range [1, 1000]
829
+ start_col (str): Processed to be alphabetic column representation. e.g., A, Z, QED
830
+ max_rows (int): Processed to be within the range [1, 1000]
831
+ max_cols (int): Processed to be within the range [1, 26]
832
+
833
+ Returns:
834
+ tuple[str, str, int, str, int, int]: The processed parameters.
835
+
836
+ Raises:
837
+ ToolExecutionError:
838
+ If the start_col is not one of alphabetic or numeric
839
+ """
840
+ if sheet_id_or_name:
841
+ sheet_identifier = sheet_id_or_name
842
+ sheet_identifier_type = SheetIdentifierType.ID_OR_NAME
843
+ elif sheet_position:
844
+ sheet_identifier = str(sheet_position)
845
+ sheet_identifier_type = SheetIdentifierType.POSITION
846
+ else:
847
+ raise RetryableToolError("Either sheet_position or sheet_id_or_name must be provided")
848
+
849
+ processed_start_row = max(1, start_row)
850
+ processed_max_rows = max(1, min(max_rows, 1000))
851
+ processed_max_cols = max(1, min(max_cols, 26))
852
+ if not all(c in string.ascii_letters for c in start_col):
853
+ if not start_col.isdigit():
854
+ raise ToolExecutionError("Input 'start_col' must be alphabetic (A-Z) or numeric")
855
+ processed_start_col = index_to_col(int(start_col) - 1)
856
+ else:
857
+ processed_start_col = start_col.upper()
858
+
859
+ return (
860
+ sheet_identifier,
861
+ sheet_identifier_type,
862
+ processed_start_row,
863
+ processed_start_col,
864
+ processed_max_rows,
865
+ processed_max_cols,
866
+ )
867
+
868
+
869
+ def get_sheet_metadata_from_identifier( # type: ignore[no-any-unimported]
870
+ service: Resource,
871
+ spreadsheet_id: str,
872
+ sheet_position: int | None,
873
+ sheet_id_or_name: str | None,
874
+ ) -> tuple[str, int, str]:
875
+ """Get the actual sheet name from position, id, or name identifier.
876
+
877
+ Args:
878
+ service (Resource): The Google Sheets service.
879
+ spreadsheet_id (str): The ID of the spreadsheet.
880
+ sheet_position (int | None): The position/tab of the sheet (1-indexed).
881
+ sheet_id_or_name (str | None): The id or name of the sheet.
882
+
883
+ Returns:
884
+ tuple[str, str, str]: The sheet's title, id, and url.
885
+
886
+ Raises:
887
+ ToolExecutionError: If the sheet is not found.
888
+ """
889
+ # Determine the sheet identifier and type
890
+ if sheet_id_or_name:
891
+ sheet_identifier = sheet_id_or_name
892
+ sheet_identifier_type = SheetIdentifierType.ID_OR_NAME
893
+ elif sheet_position:
894
+ sheet_identifier = str(sheet_position)
895
+ sheet_identifier_type = SheetIdentifierType.POSITION
896
+ else:
897
+ # Default to first sheet
898
+ sheet_identifier = "1"
899
+ sheet_identifier_type = SheetIdentifierType.POSITION
900
+
901
+ spreadsheet = get_spreadsheet_metadata_helper(service, spreadsheet_id)
902
+
903
+ target_sheet = get_sheet_by_identifier(
904
+ spreadsheet.sheets, sheet_identifier, sheet_identifier_type
905
+ )
906
+
907
+ if not target_sheet:
908
+ raise ToolExecutionError(
909
+ message=f"Sheet with {sheet_identifier_type.value} '{sheet_identifier}' not found",
910
+ developer_message=(
911
+ "Sheet(s) in the spreadsheet: "
912
+ + ", ".join([sheet.properties.title for sheet in spreadsheet.sheets])
913
+ ),
914
+ )
915
+
916
+ sheet_url = sheet_url_template.format(
917
+ spreadsheet_id=spreadsheet_id,
918
+ sheet_id=target_sheet.properties.sheetId,
919
+ )
920
+
921
+ return target_sheet.properties.title, target_sheet.properties.sheetId, sheet_url
922
+
923
+
924
+ def raise_for_large_payload(data: dict) -> None:
925
+ """Enforce a 10MB limit on the data size.
926
+
927
+ Args:
928
+ data (dict): The data to enforce the size limit on.
929
+
930
+ Raises:
931
+ ToolExecutionError:
932
+ If the data size exceeds 10MB
933
+ """
934
+ num_bytes = len(str(data).encode("utf-8"))
935
+
936
+ if num_bytes >= (10 * 1024 * 1024):
937
+ raise ToolExecutionError(
938
+ message="Spreadsheet size exceeds 10MB limit. "
939
+ "Please reduce the number of rows and columns you are requesting and try again.",
940
+ developer_message=f"Data size: {num_bytes / 1024 / 1024:.4f}MB",
941
+ )
942
+
943
+
944
+ # ------------------------------
945
+ # Search Utils
946
+ # ------------------------------
947
+ def build_files_list_query(
948
+ mime_type: str,
949
+ document_contains: list[str] | None = None,
950
+ document_not_contains: list[str] | None = None,
951
+ ) -> str:
952
+ query = [f"(mimeType = '{mime_type}' and trashed = false)"]
953
+
954
+ if isinstance(document_contains, str):
955
+ document_contains = [document_contains]
956
+
957
+ if isinstance(document_not_contains, str):
958
+ document_not_contains = [document_not_contains]
959
+
960
+ if document_contains:
961
+ for keyword in document_contains:
962
+ name_contains = keyword.replace("'", "\\'")
963
+ full_text_contains = keyword.replace("'", "\\'")
964
+ keyword_query = (
965
+ f"(name contains '{name_contains}' or fullText contains '{full_text_contains}')"
966
+ )
967
+ query.append(keyword_query)
968
+
969
+ if document_not_contains:
970
+ for keyword in document_not_contains:
971
+ name_not_contains = keyword.replace("'", "\\'")
972
+ full_text_not_contains = keyword.replace("'", "\\'")
973
+ keyword_query = (
974
+ f"(name not contains '{name_not_contains}' and "
975
+ f"fullText not contains '{full_text_not_contains}')"
976
+ )
977
+ query.append(keyword_query)
978
+
979
+ return " and ".join(query)
980
+
981
+
982
+ def build_files_list_params(
983
+ mime_type: str,
984
+ page_size: int,
985
+ order_by: list[OrderBy],
986
+ pagination_token: str | None,
987
+ include_shared_drives: bool,
988
+ search_only_in_shared_drive_id: str | None,
989
+ include_organization_domain_spreadsheets: bool,
990
+ spreadsheet_contains: list[str] | None = None,
991
+ spreadsheet_not_contains: list[str] | None = None,
992
+ ) -> dict[str, Any]:
993
+ query = build_files_list_query(
994
+ mime_type=mime_type,
995
+ document_contains=spreadsheet_contains,
996
+ document_not_contains=spreadsheet_not_contains,
997
+ )
998
+
999
+ params = {
1000
+ "q": query,
1001
+ "pageSize": page_size,
1002
+ "orderBy": ",".join([item.value for item in order_by]),
1003
+ "pageToken": pagination_token,
1004
+ }
1005
+
1006
+ if (
1007
+ include_shared_drives
1008
+ or search_only_in_shared_drive_id
1009
+ or include_organization_domain_spreadsheets
1010
+ ):
1011
+ params["includeItemsFromAllDrives"] = "true"
1012
+ params["supportsAllDrives"] = "true"
1013
+
1014
+ if search_only_in_shared_drive_id:
1015
+ params["driveId"] = search_only_in_shared_drive_id
1016
+ params["corpora"] = Corpora.DRIVE.value
1017
+
1018
+ if include_organization_domain_spreadsheets:
1019
+ params["corpora"] = Corpora.DOMAIN.value
1020
+
1021
+ params = remove_none_values(params)
1022
+
1023
+ return params
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcade_google_sheets
3
- Version: 2.0.0rc1
3
+ Version: 3.1.0
4
4
  Summary: Arcade.dev LLM tools for Google Sheets
5
5
  Author-email: Arcade <dev@arcade.dev>
6
+ License: Proprietary - Arcade Software License Agreement v1.0
6
7
  License-File: LICENSE
7
8
  Requires-Python: >=3.10
8
9
  Requires-Dist: arcade-tdk<3.0.0,>=2.0.0
@@ -12,7 +13,7 @@ Requires-Dist: google-auth-httplib2<1.0.0,>=0.2.0
12
13
  Requires-Dist: google-auth<3.0.0,>=2.32.0
13
14
  Requires-Dist: googleapis-common-protos<2.0.0,>=1.63.2
14
15
  Provides-Extra: dev
15
- Requires-Dist: arcade-ai[evals]<3.0.0,>=2.0.0rc1; extra == 'dev'
16
+ Requires-Dist: arcade-ai[evals]<3.0.0,>=2.0.0; extra == 'dev'
16
17
  Requires-Dist: arcade-serve<3.0.0,>=2.0.0; extra == 'dev'
17
18
  Requires-Dist: mypy<1.6.0,>=1.5.1; extra == 'dev'
18
19
  Requires-Dist: pre-commit<3.5.0,>=3.4.0; extra == 'dev'