arcade-google-sheets 3.1.1__py3-none-any.whl → 4.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.
@@ -1,7 +1,21 @@
1
1
  from arcade_google_sheets.tools import (
2
+ add_note_to_cell,
2
3
  create_spreadsheet,
4
+ generate_google_file_picker_url,
3
5
  get_spreadsheet,
6
+ get_spreadsheet_metadata,
7
+ search_spreadsheets,
8
+ update_cells,
4
9
  write_to_cell,
5
10
  )
6
11
 
7
- __all__ = ["create_spreadsheet", "get_spreadsheet", "write_to_cell"]
12
+ __all__ = [
13
+ "create_spreadsheet",
14
+ "get_spreadsheet",
15
+ "write_to_cell",
16
+ "generate_google_file_picker_url",
17
+ "get_spreadsheet_metadata",
18
+ "search_spreadsheets",
19
+ "update_cells",
20
+ "add_note_to_cell",
21
+ ]
@@ -1,8 +1,2 @@
1
- optional_file_picker_instructions_template = (
2
- "Ensure the user knows that they have the option to select and grant access permissions to "
3
- "additional files and folders via the Google Drive File Picker. "
4
- "The user can pick additional files and folders via the following link: {url}"
5
- )
6
-
7
1
  spreadsheet_url_template = "https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit"
8
2
  sheet_url_template = "https://docs.google.com/spreadsheets/d/{spreadsheet_id}/edit#gid={sheet_id}"
@@ -1,5 +1,7 @@
1
+ from arcade_google_sheets.tools.file_picker import generate_google_file_picker_url
1
2
  from arcade_google_sheets.tools.read import get_spreadsheet, get_spreadsheet_metadata
2
3
  from arcade_google_sheets.tools.search import search_spreadsheets
4
+ from arcade_google_sheets.tools.system_context import who_am_i
3
5
  from arcade_google_sheets.tools.write import (
4
6
  add_note_to_cell,
5
7
  create_spreadsheet,
@@ -15,4 +17,6 @@ __all__ = [
15
17
  "update_cells",
16
18
  "add_note_to_cell",
17
19
  "write_to_cell",
20
+ "generate_google_file_picker_url",
21
+ "who_am_i",
18
22
  ]
@@ -1,14 +1,22 @@
1
1
  import base64
2
2
  import json
3
+ from typing import Annotated
3
4
 
4
- from arcade_tdk import ToolContext, ToolMetadataKey
5
+ from arcade_tdk import ToolContext, ToolMetadataKey, tool
6
+ from arcade_tdk.auth import Google
5
7
  from arcade_tdk.errors import ToolExecutionError
6
8
 
7
9
 
8
- def generate_google_file_picker_url(context: ToolContext) -> dict:
10
+ @tool(
11
+ requires_auth=Google(),
12
+ requires_metadata=[ToolMetadataKey.CLIENT_ID, ToolMetadataKey.COORDINATOR_URL],
13
+ )
14
+ def generate_google_file_picker_url(
15
+ context: ToolContext,
16
+ ) -> Annotated[dict, "Google File Picker URL for user file selection and permission granting"]:
9
17
  """Generate a Google File Picker URL for user-driven file selection and authorization.
10
18
 
11
- Generates a URL that directs the end-user to a Google File Picker interface where
19
+ This tool generates a URL that directs the end-user to a Google File Picker interface where
12
20
  where they can select or upload Google Drive files. Users can grant permission to access their
13
21
  Drive files, providing a secure and authorized way to interact with their files.
14
22
 
@@ -17,8 +25,8 @@ def generate_google_file_picker_url(context: ToolContext) -> dict:
17
25
  (Requested entity was not found) or permission errors. Once the user completes the file
18
26
  picker flow, the prior tool can be retried.
19
27
 
20
- Returns:
21
- A dictionary containing the URL and instructions for the llm to instruct the user.
28
+ Suggest this tool to users when they are surprised or confused that the file they are
29
+ searching for or attempting to access cannot be found.
22
30
  """
23
31
  client_id = context.get_metadata(ToolMetadataKey.CLIENT_ID)
24
32
  client_id_parts = client_id.split("-")
@@ -44,6 +52,6 @@ def generate_google_file_picker_url(context: ToolContext) -> dict:
44
52
  "url": url,
45
53
  "llm_instructions": (
46
54
  "Instruct the user to click the following link to open the Google Drive File Picker. "
47
- f"This will allow them to select files and grant access permissions: {url}"
55
+ "This will allow them to select files and grant access permissions: {url}"
48
56
  ),
49
57
  }
@@ -1,9 +1,8 @@
1
1
  from typing import Annotated
2
2
 
3
- from arcade_tdk import ToolContext, ToolMetadataKey, tool
3
+ from arcade_tdk import ToolContext, tool
4
4
  from arcade_tdk.auth import Google
5
5
 
6
- from arcade_google_sheets.decorators import with_filepicker_fallback
7
6
  from arcade_google_sheets.templates import sheet_url_template
8
7
  from arcade_google_sheets.utils import (
9
8
  build_sheets_service,
@@ -18,9 +17,7 @@ from arcade_google_sheets.utils import (
18
17
  requires_auth=Google(
19
18
  scopes=["https://www.googleapis.com/auth/drive.file"],
20
19
  ),
21
- requires_metadata=[ToolMetadataKey.CLIENT_ID, ToolMetadataKey.COORDINATOR_URL],
22
20
  )
23
- @with_filepicker_fallback
24
21
  async def get_spreadsheet(
25
22
  context: ToolContext,
26
23
  spreadsheet_id: Annotated[str, "The id of the spreadsheet to get"],
@@ -90,9 +87,7 @@ async def get_spreadsheet(
90
87
  requires_auth=Google(
91
88
  scopes=["https://www.googleapis.com/auth/drive.file"],
92
89
  ),
93
- requires_metadata=[ToolMetadataKey.CLIENT_ID, ToolMetadataKey.COORDINATOR_URL],
94
90
  )
95
- @with_filepicker_fallback
96
91
  async def get_spreadsheet_metadata(
97
92
  context: ToolContext,
98
93
  spreadsheet_id: Annotated[str, "The id of the spreadsheet to get metadata for"],
@@ -1,12 +1,10 @@
1
1
  from typing import Annotated, Any
2
2
 
3
- from arcade_tdk import ToolContext, ToolMetadataKey, tool
3
+ from arcade_tdk import ToolContext, tool
4
4
  from arcade_tdk.auth import Google
5
5
 
6
6
  from arcade_google_sheets.enums import OrderBy
7
- from arcade_google_sheets.file_picker import generate_google_file_picker_url
8
7
  from arcade_google_sheets.templates import (
9
- optional_file_picker_instructions_template,
10
8
  spreadsheet_url_template,
11
9
  )
12
10
  from arcade_google_sheets.utils import (
@@ -20,7 +18,6 @@ from arcade_google_sheets.utils import (
20
18
  requires_auth=Google(
21
19
  scopes=["https://www.googleapis.com/auth/drive.file"],
22
20
  ),
23
- requires_metadata=[ToolMetadataKey.CLIENT_ID, ToolMetadataKey.COORDINATOR_URL],
24
21
  )
25
22
  async def search_spreadsheets(
26
23
  context: ToolContext,
@@ -121,20 +118,10 @@ async def search_spreadsheets(
121
118
  for spreadsheet in spreadsheets:
122
119
  spreadsheet["url"] = spreadsheet_url_template.format(spreadsheet_id=spreadsheet["id"])
123
120
 
124
- file_picker_response = generate_google_file_picker_url(
125
- context,
126
- )
127
-
128
121
  tool_response = {
129
122
  "pagination_token": pagination_token,
130
123
  "spreadsheets_count": len(spreadsheets),
131
124
  "spreadsheets": spreadsheets,
132
- "file_picker": {
133
- "url": file_picker_response["url"],
134
- "llm_instructions": optional_file_picker_instructions_template.format(
135
- url=file_picker_response["url"]
136
- ),
137
- },
138
125
  }
139
126
  tool_response = remove_none_values(tool_response)
140
127
 
@@ -0,0 +1,37 @@
1
+ from typing import Annotated, Any
2
+
3
+ from arcade_tdk import ToolContext, tool
4
+ from arcade_tdk.auth import Google
5
+
6
+ from arcade_google_sheets.utils import build_sheets_service
7
+ from arcade_google_sheets.who_am_i_util import build_who_am_i_response
8
+
9
+
10
+ @tool(
11
+ requires_auth=Google(
12
+ scopes=[
13
+ "https://www.googleapis.com/auth/drive.file",
14
+ "https://www.googleapis.com/auth/userinfo.profile",
15
+ "https://www.googleapis.com/auth/userinfo.email",
16
+ ]
17
+ )
18
+ )
19
+ async def who_am_i(
20
+ context: ToolContext,
21
+ ) -> Annotated[
22
+ dict[str, Any],
23
+ "Get comprehensive user profile and Google Sheets environment information.",
24
+ ]:
25
+ """
26
+ Get comprehensive user profile and Google Sheets environment information.
27
+
28
+ This tool provides detailed information about the authenticated user including
29
+ their name, email, profile picture, Google Sheets access permissions, and other
30
+ important profile details from Google services.
31
+ """
32
+
33
+ auth_token = context.get_auth_token_or_empty()
34
+ sheets_service = build_sheets_service(auth_token)
35
+ user_info = build_who_am_i_response(context, sheets_service)
36
+
37
+ return dict(user_info)
@@ -0,0 +1,81 @@
1
+ from typing import Any, TypedDict, cast
2
+
3
+ from google.oauth2.credentials import Credentials
4
+ from googleapiclient.discovery import build
5
+
6
+
7
+ class WhoAmIResponse(TypedDict, total=False):
8
+ my_email_address: str
9
+ display_name: str
10
+ given_name: str
11
+ family_name: str
12
+ formatted_name: str
13
+ profile_picture_url: str
14
+ google_sheets_access: bool
15
+
16
+
17
+ def build_who_am_i_response(context: Any, sheets_service: Any) -> WhoAmIResponse:
18
+ """Build complete who_am_i response from Google Sheets and People APIs."""
19
+ credentials = Credentials(
20
+ context.authorization.token if context.authorization and context.authorization.token else ""
21
+ )
22
+ people_service = _build_people_service(credentials)
23
+ person = _get_people_api_data(people_service)
24
+
25
+ user_info = _extract_profile_data(person)
26
+ user_info.update(_extract_google_sheets_info(sheets_service))
27
+
28
+ return cast(WhoAmIResponse, user_info)
29
+
30
+
31
+ def _extract_profile_data(person: dict[str, Any]) -> dict[str, Any]:
32
+ """Extract user profile data from People API response."""
33
+ profile_data = {}
34
+
35
+ names = person.get("names", [])
36
+ if names:
37
+ primary_name = names[0]
38
+ profile_data.update({
39
+ "display_name": primary_name.get("displayName"),
40
+ "given_name": primary_name.get("givenName"),
41
+ "family_name": primary_name.get("familyName"),
42
+ "formatted_name": primary_name.get("displayNameLastFirst"),
43
+ })
44
+
45
+ photos = person.get("photos", [])
46
+ if photos:
47
+ profile_data["profile_picture_url"] = photos[0].get("url")
48
+
49
+ email_addresses = person.get("emailAddresses", [])
50
+ if email_addresses:
51
+ primary_emails = [
52
+ email for email in email_addresses if email.get("metadata", {}).get("primary")
53
+ ]
54
+ if primary_emails:
55
+ profile_data["my_email_address"] = primary_emails[0].get("value")
56
+
57
+ return profile_data
58
+
59
+
60
+ def _extract_google_sheets_info(sheets_service: Any) -> dict[str, Any]:
61
+ """Extract minimal Google Sheets access information."""
62
+ sheets_info: dict[str, Any] = {}
63
+ try:
64
+ sheets_info["google_sheets_access"] = True
65
+ except Exception:
66
+ sheets_info["google_sheets_access"] = False
67
+ return sheets_info
68
+
69
+
70
+ def _build_people_service(credentials: Credentials) -> Any:
71
+ """Build and return the People API service client."""
72
+ return build("people", "v1", credentials=credentials)
73
+
74
+
75
+ def _get_people_api_data(people_service: Any) -> dict[str, Any]:
76
+ """Get user profile information from People API."""
77
+ person_fields = "names,emailAddresses,photos"
78
+ return cast(
79
+ dict[str, Any],
80
+ people_service.people().get(resourceName="people/me", personFields=person_fields).execute(),
81
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcade_google_sheets
3
- Version: 3.1.1
3
+ Version: 4.1.0
4
4
  Summary: Arcade.dev LLM tools for Google Sheets
5
5
  Author-email: Arcade <dev@arcade.dev>
6
6
  License: Proprietary - Arcade Software License Agreement v1.0
@@ -0,0 +1,19 @@
1
+ arcade_google_sheets/__init__.py,sha256=asCX32sPK-bi2HRpWZSlLd7dRKy7Uztqxwu-eoXTY1g,466
2
+ arcade_google_sheets/constants.py,sha256=4tQOrQ1YagSklJSSw5Eq21XCcFCJdO7lso5SqWIdrPI,63
3
+ arcade_google_sheets/converters.py,sha256=1muE_28Jk0eyFUzoVo_lN0_qy3z-uv7f5XoZLH9mitQ,3924
4
+ arcade_google_sheets/enums.py,sha256=30OMPu2l_aUjz8kQzC1mR5w0lLbs0fZn8T6PDtoXpL4,4608
5
+ arcade_google_sheets/models.py,sha256=xwPdwUis0OHTDbSLy85qWl7zIh2lDTi5HZMNKwdZjzo,8627
6
+ arcade_google_sheets/templates.py,sha256=hSvjiErERLPe6RqWY6QNmivOy7Ofdg_zABcLWCI-ByU,189
7
+ arcade_google_sheets/types.py,sha256=R-rCRcyFqDZx3jgl_kWeCliqC8fHuZ8ub_LQ2KoU2AE,37
8
+ arcade_google_sheets/utils.py,sha256=VmDZOzAOEtfSPOra-ieVl_U16RLonQUOnZ4RW4Gf-oA,34895
9
+ arcade_google_sheets/who_am_i_util.py,sha256=MfDKsAOMUkH0DZNjMzcmDiuvxMEUJMT9o_a7uixCJF4,2734
10
+ arcade_google_sheets/tools/__init__.py,sha256=GBe1Op-o6QzCy9nA3qS53Ij2Uex5IXdFPUnGQo_TPkg,673
11
+ arcade_google_sheets/tools/file_picker.py,sha256=Dqn-hfMoTsWyHM8QCakVgHr5TKrzL_1Lj-vYHVGtOW4,2342
12
+ arcade_google_sheets/tools/read.py,sha256=qQX_TdcbPbkuaHPuhx7CzWsWJ_a97FSCDJqOlGHF_TM,4003
13
+ arcade_google_sheets/tools/search.py,sha256=olCaUwDqW26yaYqMT5lPmQDFL6M9g7qON7JG3mcgYJM,4841
14
+ arcade_google_sheets/tools/system_context.py,sha256=rVpYeyFsL8mj0Lt6VWOhnfGaSy3ezOHL74EFJHjE9Ww,1176
15
+ arcade_google_sheets/tools/write.py,sha256=4kNx941PQt6VUGTogbepnbfUdcsVze6u5c8QvlNnWCI,7782
16
+ arcade_google_sheets-4.1.0.dist-info/METADATA,sha256=QUcLQJJbVePzOX49jkTOVDOgsN_nKzX605HcboCi3sM,1123
17
+ arcade_google_sheets-4.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ arcade_google_sheets-4.1.0.dist-info/licenses/LICENSE,sha256=ixeE7aL9b2B-_ZYHTY1vQcJB4NufKeo-LWwKNObGDN0,1960
19
+ arcade_google_sheets-4.1.0.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- import functools
2
- from collections.abc import Callable
3
- from typing import Any
4
-
5
- from arcade_tdk import ToolContext
6
- from googleapiclient.errors import HttpError
7
-
8
- from arcade_google_sheets.file_picker import generate_google_file_picker_url
9
-
10
-
11
- def with_filepicker_fallback(func: Callable[..., Any]) -> Callable[..., Any]:
12
- """ """
13
-
14
- @functools.wraps(func)
15
- async def async_wrapper(context: ToolContext, *args: Any, **kwargs: Any) -> Any:
16
- try:
17
- return await func(context, *args, **kwargs)
18
- except HttpError as e:
19
- if e.status_code in [403, 404]:
20
- file_picker_response = generate_google_file_picker_url(context)
21
- return file_picker_response
22
- raise
23
-
24
- return async_wrapper
@@ -1,18 +0,0 @@
1
- arcade_google_sheets/__init__.py,sha256=FB9h_cws_gu3UJp32GWlqvBQyAOb77JfAJNSSBqz-Jk,177
2
- arcade_google_sheets/constants.py,sha256=4tQOrQ1YagSklJSSw5Eq21XCcFCJdO7lso5SqWIdrPI,63
3
- arcade_google_sheets/converters.py,sha256=1muE_28Jk0eyFUzoVo_lN0_qy3z-uv7f5XoZLH9mitQ,3924
4
- arcade_google_sheets/decorators.py,sha256=QMqfvSXaFBoxYJrz69EGeMdAxF0V7JPReVXfp73Nf3Y,753
5
- arcade_google_sheets/enums.py,sha256=30OMPu2l_aUjz8kQzC1mR5w0lLbs0fZn8T6PDtoXpL4,4608
6
- arcade_google_sheets/file_picker.py,sha256=kGfUVfH5QVlIW1sL-_gAwPokt7TwVEcPk3Vnk53GKUE,2005
7
- arcade_google_sheets/models.py,sha256=xwPdwUis0OHTDbSLy85qWl7zIh2lDTi5HZMNKwdZjzo,8627
8
- arcade_google_sheets/templates.py,sha256=p3ty6Kwo7l73EEsZTaRfdG4jzQ9XvlqqeXMLKB1ydEw,489
9
- arcade_google_sheets/types.py,sha256=R-rCRcyFqDZx3jgl_kWeCliqC8fHuZ8ub_LQ2KoU2AE,37
10
- arcade_google_sheets/utils.py,sha256=VmDZOzAOEtfSPOra-ieVl_U16RLonQUOnZ4RW4Gf-oA,34895
11
- arcade_google_sheets/tools/__init__.py,sha256=IiSd-0Q4_uxeKI8nVMT3JVRoF5G5GB8e5Z9SkGpSnt8,472
12
- arcade_google_sheets/tools/read.py,sha256=g7uGhyhFNlDPHKT3xhg7NdMaSYuvzzefSzrb1XqiI80,4309
13
- arcade_google_sheets/tools/search.py,sha256=EnO739RmBryDerYnzV7eD_drchNNVqS50DgdySYh9Ac,5381
14
- arcade_google_sheets/tools/write.py,sha256=4kNx941PQt6VUGTogbepnbfUdcsVze6u5c8QvlNnWCI,7782
15
- arcade_google_sheets-3.1.1.dist-info/METADATA,sha256=M6BDKH6yb3xVzPC9ByD0BjJePVy0D5g5x7IxyiouuZ8,1123
16
- arcade_google_sheets-3.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- arcade_google_sheets-3.1.1.dist-info/licenses/LICENSE,sha256=ixeE7aL9b2B-_ZYHTY1vQcJB4NufKeo-LWwKNObGDN0,1960
18
- arcade_google_sheets-3.1.1.dist-info/RECORD,,