arcade-google-sheets 2.0.0__py3-none-any.whl → 3.0.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.
@@ -23,3 +23,8 @@ class NumberFormatType(str, Enum):
23
23
  NUMBER = "NUMBER"
24
24
  PERCENT = "PERCENT"
25
25
  CURRENCY = "CURRENCY"
26
+
27
+
28
+ class SheetIdentifierType(str, Enum):
29
+ POSITION = "position"
30
+ ID_OR_NAME = "id_or_name"
@@ -6,7 +6,9 @@ from arcade_tdk.auth import Google
6
6
  from arcade_google_sheets.decorators import with_filepicker_fallback
7
7
  from arcade_google_sheets.utils import (
8
8
  build_sheets_service,
9
- parse_get_spreadsheet_response,
9
+ get_spreadsheet_with_pagination,
10
+ process_get_spreadsheet_params,
11
+ raise_for_large_payload,
10
12
  )
11
13
 
12
14
 
@@ -20,23 +22,63 @@ from arcade_google_sheets.utils import (
20
22
  async def get_spreadsheet(
21
23
  context: ToolContext,
22
24
  spreadsheet_id: Annotated[str, "The id of the spreadsheet to get"],
25
+ sheet_position: Annotated[
26
+ int | None,
27
+ "The position/tab of the sheet in the spreadsheet to get. "
28
+ "A value of 1 represents the first (leftmost/Sheet1) sheet . "
29
+ "Defaults to 1.",
30
+ ] = 1,
31
+ sheet_id_or_name: Annotated[
32
+ str | None,
33
+ "The id or name of the sheet to get. "
34
+ "Defaults to None, which means sheet_position will be used instead.",
35
+ ] = None,
36
+ start_row: Annotated[int, "Starting row number (1-indexed, defaults to 1)"] = 1,
37
+ start_col: Annotated[
38
+ str, "Starting column letter(s) or 1-based column number (defaults to 'A')"
39
+ ] = "A",
40
+ max_rows: Annotated[
41
+ int,
42
+ "Maximum number of rows to fetch for each sheet in the spreadsheet. "
43
+ "Must be between 1 and 1000. Defaults to 1000.",
44
+ ] = 1000,
45
+ max_cols: Annotated[
46
+ int,
47
+ "Maximum number of columns to fetch for each sheet in the spreadsheet. "
48
+ "Must be between 1 and 100. Defaults to 100.",
49
+ ] = 100,
23
50
  ) -> Annotated[
24
51
  dict,
25
- "The spreadsheet properties and data for all sheets in the spreadsheet",
52
+ "The spreadsheet properties and data for the specified sheet in the spreadsheet",
26
53
  ]:
54
+ """Gets the specified range of cells from a single sheet in the spreadsheet.
55
+
56
+ sheet_id_or_name takes precedence over sheet_position. If a sheet is not mentioned,
57
+ then always assume the default sheet_position is sufficient.
27
58
  """
28
- Get the user entered values and formatted values for all cells in all sheets in the spreadsheet
29
- along with the spreadsheet's properties
30
- """
59
+ sheet_identifier, sheet_identifier_type, start_row, start_col, max_rows, max_cols = (
60
+ process_get_spreadsheet_params(
61
+ sheet_position,
62
+ sheet_id_or_name,
63
+ start_row,
64
+ start_col,
65
+ max_rows,
66
+ max_cols,
67
+ )
68
+ )
69
+
31
70
  service = build_sheets_service(context.get_auth_token_or_empty())
32
71
 
33
- response = (
34
- service.spreadsheets()
35
- .get(
36
- spreadsheetId=spreadsheet_id,
37
- includeGridData=True,
38
- fields="spreadsheetId,spreadsheetUrl,properties/title,sheets/properties,sheets/data/rowData/values/userEnteredValue,sheets/data/rowData/values/formattedValue,sheets/data/rowData/values/effectiveValue",
39
- )
40
- .execute()
72
+ data = get_spreadsheet_with_pagination(
73
+ service,
74
+ spreadsheet_id,
75
+ sheet_identifier,
76
+ sheet_identifier_type,
77
+ start_row,
78
+ start_col,
79
+ max_rows,
80
+ max_cols,
41
81
  )
42
- return parse_get_spreadsheet_response(response)
82
+
83
+ raise_for_large_payload(data)
84
+ return data
@@ -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,7 @@ 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 NumberFormatType, SheetIdentifierType
13
14
  from arcade_google_sheets.models import (
14
15
  CellData,
15
16
  CellExtendedValue,
@@ -21,6 +22,7 @@ from arcade_google_sheets.models import (
21
22
  Sheet,
22
23
  SheetDataInput,
23
24
  SheetProperties,
25
+ Spreadsheet,
24
26
  )
25
27
  from arcade_google_sheets.types import CellValue
26
28
 
@@ -546,3 +548,238 @@ def parse_write_to_cell_response(response: dict) -> dict:
546
548
  "updatedCell": response["updatedData"]["range"].split("!")[1],
547
549
  "value": response["updatedData"]["values"][0][0],
548
550
  }
551
+
552
+
553
+ def calculate_a1_sheet_range(
554
+ sheet_name: str,
555
+ sheet_row_count: int,
556
+ sheet_col_count: int,
557
+ start_row: int,
558
+ start_col: str,
559
+ max_rows: int,
560
+ max_cols: int,
561
+ ) -> str | None:
562
+ """Calculate a single range for a sheet based on start position and limits.
563
+
564
+ Args:
565
+ sheet_name (str): The name of the sheet.
566
+ sheet_row_count (int): The number of rows in the sheet.
567
+ sheet_col_count (int): The number of columns in the sheet.
568
+ start_row (int): The row from which to start fetching data.
569
+ start_col (str): The column letter(s) from which to start fetching data.
570
+ max_rows (int): The maximum number of rows to fetch.
571
+ max_cols (int): The maximum number of columns to fetch.
572
+
573
+ Returns:
574
+ str | None: The A1 range for the sheet, or None if there is no data to fetch.
575
+ """
576
+ start_col_index = col_to_index(start_col)
577
+
578
+ effective_max_rows = min(sheet_row_count, max_rows or sheet_row_count)
579
+ effective_max_cols = min(sheet_col_count, max_cols or sheet_col_count)
580
+
581
+ end_row = min(start_row + effective_max_rows - 1, sheet_row_count)
582
+ end_col_index = min(start_col_index + effective_max_cols - 1, sheet_col_count - 1)
583
+
584
+ # Only create a range if there's actually data to fetch
585
+ if start_row <= end_row and start_col_index <= end_col_index:
586
+ range_start = f"{index_to_col(start_col_index)}{start_row}"
587
+ range_end = f"{index_to_col(end_col_index)}{end_row}"
588
+ return f"'{sheet_name}'!{range_start}:{range_end}"
589
+
590
+ return None
591
+
592
+
593
+ def find_sheet_by_identifier(
594
+ sheets: list[Sheet], sheet_identifier: str, sheet_identifier_type: SheetIdentifierType
595
+ ) -> Sheet | None:
596
+ """
597
+ Find a sheet by identifier (name, sheet ID, or 1-based position index).
598
+
599
+ Args:
600
+ sheets (list): List of Sheet objects from the spreadsheet.
601
+ sheet_identifier (str): The identifier of the sheet to get.
602
+ sheet_identifier_type (SheetIdentifierType): The type of the identifier.
603
+
604
+ Returns:
605
+ Sheet | None: The matching sheet, or None if not found.
606
+ """
607
+ if sheet_identifier_type == SheetIdentifierType.POSITION:
608
+ index = int(sheet_identifier) - 1
609
+ if 0 <= index < len(sheets):
610
+ return sheets[index]
611
+
612
+ if sheet_identifier_type == SheetIdentifierType.ID_OR_NAME:
613
+ for sheet in sheets:
614
+ sheet_title = sheet.properties.title
615
+ sheet_id = sheet.properties.sheetId
616
+ if (
617
+ sheet_title.casefold() == sheet_identifier.casefold()
618
+ or str(sheet_id).casefold() == sheet_identifier.casefold()
619
+ ):
620
+ return sheet
621
+
622
+ return None
623
+
624
+
625
+ def get_spreadsheet_with_pagination( # type: ignore[no-any-unimported]
626
+ service: Resource,
627
+ spreadsheet_id: str,
628
+ sheet_identifier: str,
629
+ sheet_identifier_type: SheetIdentifierType,
630
+ start_row: int,
631
+ start_col: str,
632
+ max_rows: int,
633
+ max_cols: int,
634
+ ) -> dict:
635
+ """
636
+ Get spreadsheet data with pagination support for large spreadsheets.
637
+
638
+ Args:
639
+ service (Resource): The Google Sheets service.
640
+ spreadsheet_id (str): The ID of the spreadsheet provided to the tool.
641
+ sheet_position (int | None): The position/tab of the sheet to get.
642
+ sheet_id_or_name (str | None): The id or name of the sheet to get.
643
+ start_row (int): The row from which to start fetching data.
644
+ start_col (str): The column letter(s) from which to start fetching data.
645
+ max_rows (int): The maximum number of rows to fetch.
646
+ max_cols (int): The maximum number of columns to fetch.
647
+
648
+ Returns:
649
+ dict: The spreadsheet data for the specified sheet in the spreadsheet.
650
+
651
+ """
652
+
653
+ # First, only get the spreadsheet metadata to collect the sheet names and dimensions
654
+ metadata_response = (
655
+ service.spreadsheets()
656
+ .get(
657
+ spreadsheetId=spreadsheet_id,
658
+ includeGridData=False,
659
+ fields="spreadsheetId,spreadsheetUrl,properties/title,sheets/properties",
660
+ )
661
+ .execute()
662
+ )
663
+ spreadsheet = Spreadsheet.model_validate(metadata_response)
664
+
665
+ target_sheet = find_sheet_by_identifier(
666
+ spreadsheet.sheets, sheet_identifier, sheet_identifier_type
667
+ )
668
+ if not target_sheet:
669
+ raise ToolExecutionError(
670
+ message=f"Sheet with identifier '{sheet_identifier}' not found",
671
+ developer_message=(
672
+ "Sheet(s) in the spreadsheet: "
673
+ + ", ".join([sheet.model_dump_json() for sheet in spreadsheet.sheets])
674
+ ),
675
+ )
676
+
677
+ a1_ranges = []
678
+ sheet_name = target_sheet.properties.title
679
+ grid_props = target_sheet.properties.gridProperties
680
+ if grid_props:
681
+ sheet_row_count = grid_props.rowCount
682
+ sheet_col_count = grid_props.columnCount
683
+
684
+ curr_range = calculate_a1_sheet_range(
685
+ sheet_name,
686
+ sheet_row_count,
687
+ sheet_col_count,
688
+ start_row,
689
+ start_col,
690
+ max_rows,
691
+ max_cols,
692
+ )
693
+ if curr_range:
694
+ a1_ranges.append(curr_range)
695
+
696
+ # Next, get the data for the ranges
697
+ if a1_ranges:
698
+ response = (
699
+ service.spreadsheets()
700
+ .get(
701
+ spreadsheetId=spreadsheet_id,
702
+ includeGridData=True,
703
+ ranges=a1_ranges,
704
+ fields="spreadsheetId,spreadsheetUrl,properties/title,sheets/properties,sheets/data/rowData/values/userEnteredValue,sheets/data/rowData/values/formattedValue,sheets/data/rowData/values/effectiveValue",
705
+ )
706
+ .execute()
707
+ )
708
+ else:
709
+ response = metadata_response
710
+
711
+ return parse_get_spreadsheet_response(response)
712
+
713
+
714
+ def process_get_spreadsheet_params(
715
+ sheet_position: int | None,
716
+ sheet_id_or_name: str | None,
717
+ start_row: int,
718
+ start_col: str,
719
+ max_rows: int,
720
+ max_cols: int,
721
+ ) -> tuple[str, SheetIdentifierType, int, str, int, int]:
722
+ """Process and validate the input parameters for the get_spreadsheet tool.
723
+
724
+ Args:
725
+ sheet_position (int | None): The position/tab of the sheet to get.
726
+ sheet_id_or_name (str | None): The id or name of the sheet to get.
727
+ start_row (int): Processed to be within the range [1, 1000]
728
+ start_col (str): Processed to be alphabetic column representation. e.g., A, Z, QED
729
+ max_rows (int): Processed to be within the range [1, 1000]
730
+ max_cols (int): Processed to be within the range [1, 26]
731
+
732
+ Returns:
733
+ tuple[str, str, int, str, int, int]: The processed parameters.
734
+
735
+ Raises:
736
+ ToolExecutionError:
737
+ If the start_col is not one of alphabetic or numeric
738
+ """
739
+ if sheet_id_or_name:
740
+ sheet_identifier = sheet_id_or_name
741
+ sheet_identifier_type = SheetIdentifierType.ID_OR_NAME
742
+ elif sheet_position:
743
+ sheet_identifier = str(sheet_position)
744
+ sheet_identifier_type = SheetIdentifierType.POSITION
745
+ else:
746
+ raise RetryableToolError("Either sheet_position or sheet_id_or_name must be provided")
747
+
748
+ processed_start_row = max(1, start_row)
749
+ processed_max_rows = max(1, min(max_rows, 1000))
750
+ processed_max_cols = max(1, min(max_cols, 26))
751
+ if not all(c in string.ascii_letters for c in start_col):
752
+ if not start_col.isdigit():
753
+ raise ToolExecutionError("Input 'start_col' must be alphabetic (A-Z) or numeric")
754
+ processed_start_col = index_to_col(int(start_col) - 1)
755
+ else:
756
+ processed_start_col = start_col.upper()
757
+
758
+ return (
759
+ sheet_identifier,
760
+ sheet_identifier_type,
761
+ processed_start_row,
762
+ processed_start_col,
763
+ processed_max_rows,
764
+ processed_max_cols,
765
+ )
766
+
767
+
768
+ def raise_for_large_payload(data: dict) -> None:
769
+ """Enforce a 10MB limit on the data size.
770
+
771
+ Args:
772
+ data (dict): The data to enforce the size limit on.
773
+
774
+ Raises:
775
+ ToolExecutionError:
776
+ If the data size exceeds 10MB
777
+ """
778
+ num_bytes = len(str(data).encode("utf-8"))
779
+
780
+ if num_bytes >= (10 * 1024 * 1024):
781
+ raise ToolExecutionError(
782
+ message="Spreadsheet size exceeds 10MB limit. "
783
+ "Please reduce the number of rows and columns you are requesting and try again.",
784
+ developer_message=f"Data size: {num_bytes / 1024 / 1024:.4f}MB",
785
+ )
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcade_google_sheets
3
- Version: 2.0.0
3
+ Version: 3.0.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
@@ -1,15 +1,15 @@
1
1
  arcade_google_sheets/__init__.py,sha256=FB9h_cws_gu3UJp32GWlqvBQyAOb77JfAJNSSBqz-Jk,177
2
2
  arcade_google_sheets/constants.py,sha256=4tQOrQ1YagSklJSSw5Eq21XCcFCJdO7lso5SqWIdrPI,63
3
3
  arcade_google_sheets/decorators.py,sha256=QMqfvSXaFBoxYJrz69EGeMdAxF0V7JPReVXfp73Nf3Y,753
4
- arcade_google_sheets/enums.py,sha256=_gZxlgciXK-_Sg-62lTv6JFpmxWV7obH9VWE-s1zjug,942
4
+ arcade_google_sheets/enums.py,sha256=xaBm-P6h63q2xrnvd8qqzS1gW5tAkUqbQcw0KL6q7Iw,1038
5
5
  arcade_google_sheets/file_picker.py,sha256=kGfUVfH5QVlIW1sL-_gAwPokt7TwVEcPk3Vnk53GKUE,2005
6
6
  arcade_google_sheets/models.py,sha256=VQy3L_Acch1MEM2RkTe-Qp_AEU-cb0JciLJ-0Ci87aw,7613
7
7
  arcade_google_sheets/types.py,sha256=R-rCRcyFqDZx3jgl_kWeCliqC8fHuZ8ub_LQ2KoU2AE,37
8
- arcade_google_sheets/utils.py,sha256=CqQJPwXP_QoMJG18LrltG4sblTcFe4Mu5psY4U2nsvc,18412
8
+ arcade_google_sheets/utils.py,sha256=0SCjW92wedFilovwc-tERAtfdBYGZ1OZlil_XUZEiVc,26803
9
9
  arcade_google_sheets/tools/__init__.py,sha256=TPlitJn1VJffCXFkpOtoYXNsaEFkpujQzsYvuikCe4U,209
10
- arcade_google_sheets/tools/read.py,sha256=Fh9nh8ISHBgf6akixVYQIt3TihrmW7P9WW8yeb-njhI,1416
10
+ arcade_google_sheets/tools/read.py,sha256=tdDrXwt1PCytQy1oBlU4HKZ7OjiTpIU43og5VkFAMqs,2704
11
11
  arcade_google_sheets/tools/write.py,sha256=gmbErdBbBKUEPxGjCWDpMJ9pMWRAvoEjApIXFQax6Z4,3598
12
- arcade_google_sheets-2.0.0.dist-info/METADATA,sha256=MDLh2hWNrBrwRExy66wGsJpPv2QiMEZ_o_voWe5EiIs,1061
13
- arcade_google_sheets-2.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- arcade_google_sheets-2.0.0.dist-info/licenses/LICENSE,sha256=XjKuCk1TG4bFrY-8x79oGMmNqrS4TP7c_Zv4-TrMWQY,1063
15
- arcade_google_sheets-2.0.0.dist-info/RECORD,,
12
+ arcade_google_sheets-3.0.0.dist-info/METADATA,sha256=gZhKVztmJe2asXyH_cTcjcQDcACl-2r_AaglFBLBqOs,1123
13
+ arcade_google_sheets-3.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
+ arcade_google_sheets-3.0.0.dist-info/licenses/LICENSE,sha256=ixeE7aL9b2B-_ZYHTY1vQcJB4NufKeo-LWwKNObGDN0,1960
15
+ arcade_google_sheets-3.0.0.dist-info/RECORD,,
@@ -0,0 +1,35 @@
1
+ # The Arcade Software License Agreement
2
+
3
+ - Version 1.0
4
+ - Effective Date: July 24, 2025
5
+
6
+ ---
7
+
8
+ This software and associated documentation (collectively, the “Software”) are the intellectual property of Arcade Technologies, Inc. (“Arcade”). All rights are reserved.
9
+
10
+ 1. License Grant
11
+
12
+ No license or other rights are granted to any party under this Agreement, whether by implication, estoppel, or otherwise. This Software is proprietary and confidential. Use, reproduction, modification, distribution, display, or creation of derivative works based on the Software is strictly prohibited without the prior written consent of Arcade.
13
+
14
+ 2. Commercial Use Only
15
+
16
+ Any use of this Software requires a commercial license agreement with Arcade. You may not use any part of the Software for any purpose—including but not limited to evaluation, testing, or internal use—without first obtaining explicit written permission and entering into a commercial licensing arrangement.
17
+
18
+ To inquire about licensing terms, contact Arcade at:
19
+ 🔗 www.arcade.dev
20
+
21
+ 3. No Open Source Rights
22
+
23
+ This Software is not licensed under an open-source license. No part of it may be incorporated into open-source or publicly available projects under any open-source terms or licenses.
24
+
25
+ 4. Ownership
26
+
27
+ Arcade retains all right, title, and interest in and to the Software, including all intellectual property rights therein. No ownership or license rights are transferred under this Agreement.
28
+
29
+ 5. Disclaimer of Warranty
30
+
31
+ The Software is provided “as is” without warranty of any kind. Arcade disclaims all warranties, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, and noninfringement.
32
+
33
+ 6. Limitation of Liability
34
+
35
+ In no event shall Arcade be liable for any damages arising out of or in connection with the use or performance of the Software, whether in an action of contract, tort (including negligence), or otherwise.
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Arcade
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.