Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
ysolanky committed Feb 5, 2025
1 parent 997654e commit 8a81e69
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 40 deletions.
13 changes: 2 additions & 11 deletions cookbook/tools/googlesheets_tools.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
"""
This script demonstrates how to use the GoogleSheetsTools class to interact with Google Sheets through an Agent.
Setup:
Google Sheets Toolkit can be used to read, create, update and duplicate Google Sheets.
1: Follow the steps: https://developers.google.com/sheets/api/quickstart/python
2: Save the credentials.json file to the root of the project or update the path in the GoogleSheetsTools class
3: Run the script to authenticate and generate the token.json file. In the initial run, the script will open a browser window to authenticate the application.
4: Update the SCOPES as per the requirements of the application.
# Example spreadsheet: https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/
Example spreadsheet: https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/
The ID is the URL of the spreadsheet and the range is the sheet name and the range of cells to read.
"""


from agno.agent import Agent
from agno.tools.googlesheets import GoogleSheetsTools

SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]
SAMPLE_SPREADSHEET_ID = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
SAMPLE_RANGE_NAME = "Class Data!A2:E"

google_sheets_tools = GoogleSheetsTools(
spreadsheet_id=SAMPLE_SPREADSHEET_ID,
spreadsheet_range=SAMPLE_RANGE_NAME,
scopes=SCOPES,
)

agent = Agent(
Expand Down
54 changes: 33 additions & 21 deletions libs/agno/agno/tools/gmail.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""
Gmail Toolset for interacting with Gmail API
Gmail Toolkit for interacting with Gmail API
Required Environment Variables:
-----------------------------
- GOOGLE_CLIENT_ID: Google OAuth client ID
- GOOGLE_CLIENT_SECRET: Google OAuth client secret
- GOOGLE_PROJECT_ID: Google Cloud project ID
- GOOGLE_REDIRECT_URI: Google OAuth redirect URI (default: http://localhost)
- GMAIL_CLIENT_ID: Google OAuth client ID
- GMAIL_CLIENT_SECRET: Google OAuth client secret
- GMAIL_PROJECT_ID: Google Cloud project ID
- GMAIL_REDIRECT_URI: Google OAuth redirect URI (default: http://localhost)
How to Get These Credentials:
---------------------------
Expand All @@ -24,25 +24,26 @@
- Give it a name and click "Create"
- You'll receive:
* Client ID (GMAIL_CLIENT_ID)
* Client Secret (GOOGLE_CLIENT_SECRET)
- The Project ID (GOOGLE_PROJECT_ID) is visible in the project dropdown at the top of the page
* Client Secret (GMAIL_CLIENT_SECRET)
- The Project ID (GMAIL_PROJECT_ID) is visible in the project dropdown at the top of the page
5. Set up environment variables:
Create a .envrc file in your project root with:
```
export GOOGLE_CLIENT_ID=your_client_id_here
export GOOGLE_CLIENT_SECRET=your_client_secret_here
export GOOGLE_PROJECT_ID=your_project_id_here
export GOOGLE_REDIRECT_URI=http://localhost # Default value
export GMAIL_CLIENT_ID=your_client_id_here
export GMAIL_CLIENT_SECRET=your_client_secret_here
export GMAIL_PROJECT_ID=your_project_id_here
export GMAIL_REDIRECT_URI=http://localhost # Default value
```
Note: The first time you run the application, it will open a browser window for OAuth authentication.
A token.json file will be created to store the authentication credentials for future use.
"""

import base64
import os
from datetime import datetime, timedelta
from os import getenv
from pathlib import Path
from typing import List, Optional

from agno.tools import Toolkit
Expand Down Expand Up @@ -73,11 +74,16 @@ def __init__(
create_draft_email: bool = True,
send_email: bool = True,
search_emails: bool = True,
creds: Optional[Credentials] = None,
credentials_path: Optional[str] = None,
token_path: Optional[str] = None,
):
"""Initialize GmailTools and authenticate with Gmail API"""
super().__init__()
self.creds = self._authenticate()
self.creds = self._authenticate() if not creds else creds
self.service = build("gmail", "v1", credentials=self.creds)
self.credentials_path = credentials_path
self.token_path = token_path
if get_latest_emails:
self.register(self.get_latest_emails)
if get_emails_from_user:
Expand Down Expand Up @@ -107,28 +113,34 @@ def _authenticate(self) -> Credentials:
"https://www.googleapis.com/auth/gmail.compose",
]

token_file = Path(self.token_path or "token.json")
creds_file = Path(self.credentials_path or "credentials.json")

creds = None
if os.path.exists("token.json"):
if token_file.exists():
creds = Credentials.from_authorized_user_file("token.json", SCOPES)

if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
client_config = {
"installed": {
"client_id": os.getenv("GOOGLE_CLIENT_ID"),
"client_secret": os.getenv("GOOGLE_CLIENT_SECRET"),
"project_id": os.getenv("GOOGLE_PROJECT_ID"),
"client_id": getenv("GMAIL_CLIENT_ID"),
"client_secret": getenv("GMAIL_CLIENT_SECRET"),
"project_id": getenv("GMAIL_PROJECT_ID"),
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"redirect_uris": [os.getenv("GOOGLE_REDIRECT_URI", "http://localhost")],
"redirect_uris": [getenv("GMAIL_REDIRECT_URI", "http://localhost")],
}
}
flow = InstalledAppFlow.from_client_config(client_config, SCOPES)
if creds_file.exists():
flow = InstalledAppFlow.from_client_secrets_file(str(creds_file), SCOPES)
else:
flow = InstalledAppFlow.from_client_config(client_config, SCOPES)
creds = flow.run_local_server(port=0)
with open("token.json", "w") as token:
token.write(creds.to_json())
token_file.write_text(creds.to_json()) if creds else None
return creds

def _format_emails(self, emails: List[dict]) -> str:
Expand Down
139 changes: 131 additions & 8 deletions libs/agno/agno/tools/googlesheets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
"""
Google Sheets Toolset for interacting with Sheets API
Required Environment Variables:
-----------------------------
- GOOGLE_SHEETS_CLIENT_ID: Google OAuth client ID
- GOOGLE_SHEETS_CLIENT_SECRET: Google OAuth client secret
- GOOGLE_SHEETS_PROJECT_ID: Google Cloud project ID
- GOOGLE_SHEETS_REDIRECT_URI: Google OAuth redirect URI (default: http://localhost)
How to Get These Credentials:
---------------------------
1. Go to Google Cloud Console (https://console.cloud.google.com)
2. Create a new project or select an existing one
3. Enable the Gmail API:
- Go to "APIs & Services" > "Enable APIs and Services"
- Search for "Google Sheets API"
- Click "Enable"
4. Create OAuth 2.0 credentials:
- Go to "APIs & Services" > "Credentials"
- Click "Create Credentials" > "OAuth client ID"
- Go through the OAuth consent screen setup
- Give it a name and click "Create"
- You'll receive:
* Client ID (GOOGLE_SHEETS_CLIENT_ID)
* Client Secret (GOOGLE_SHEETS_CLIENT_SECRET)
- The Project ID (GOOGLE_SHEETS_PROJECT_ID) is visible in the project dropdown at the top of the page
5. Set up environment variables:
Create a .envrc file in your project root with:
```
export GOOGLE_SHEETS_CLIENT_ID=your_client_id_here
export GOOGLE_SHEETS_CLIENT_SECRET=your_client_secret_here
export GOOGLE_SHEETS_PROJECT_ID=your_project_id_here
export GOOGLE_SHEETS_REDIRECT_URI=http://localhost # Default value
```
Alternatively, follow the instructions in the Google Sheets API Quickstart guide:
1: Steps: https://developers.google.com/sheets/api/quickstart/python
2: Save the credentials.json file to the root of the project or update the path in the GoogleSheetsTools class
Note: The first time you run the application, it will open a browser window for OAuth authentication.
A token.json file will be created to store the authentication credentials for future use.
"""

import json
from functools import wraps
from os import getenv
from pathlib import Path
from typing import Any, List, Optional

Expand Down Expand Up @@ -29,9 +76,14 @@ def wrapper(self, *args, **kwargs):


class GoogleSheetsTools(Toolkit):
SCOPES = {
"read": "https://www.googleapis.com/auth/spreadsheets.readonly",
"write": "https://www.googleapis.com/auth/spreadsheets",
}

def __init__(
self,
scopes: List[str],
scopes: Optional[List[str]] = None,
spreadsheet_id: Optional[str] = None,
spreadsheet_range: Optional[str] = None,
creds: Optional[Credentials] = None,
Expand All @@ -40,34 +92,41 @@ def __init__(
read: bool = True,
create: bool = False,
update: bool = False,
duplicate: bool = False,
):
super().__init__(name="google_tools")
self.spreadsheet_id = spreadsheet_id
self.spreadsheet_range = spreadsheet_range
self.creds = creds
self.scopes = scopes
self.credentials_path = creds_path
self.token_path = token_path

self.read = read
self.create = create
self.update = update
if scopes is None:
if create or update:
self.scopes = [self.SCOPES["write"]]
else:
self.scopes = [self.SCOPES["read"]]
else:
self.scopes = scopes

if (create or update) and self.SCOPES["write"] not in self.scopes:
raise ValueError("Write operations require the spreadsheets scope")

if read:
self.register(self.read_sheet)
if create:
self.register(self.create_sheet)
if update:
self.register(self.update_sheet)
if duplicate:
self.register(self.create_duplicate_sheet)

def auth(self) -> None:
"""
Authenticate with Google Sheets API
"""
if self.creds and self.creds.valid:
return
if not self.scopes:
raise ValueError("Scopes must be provided")

token_file = Path(self.token_path or "token.json")
creds_file = Path(self.credentials_path or "credentials.json")
Expand All @@ -79,7 +138,21 @@ def auth(self) -> None:
if self.creds and self.creds.expired and self.creds.refresh_token:
self.creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(str(creds_file), self.scopes)
client_config = {
"installed": {
"client_id": getenv("GOOGLE_SHEETS_CLIENT_ID"),
"client_secret": getenv("GOOGLE_SHEETS_CLIENT_SECRET"),
"project_id": getenv("GOOGLE_SHEETS_PROJECT_ID"),
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"redirect_uris": [getenv("GOOGLE_SHEETS_REDIRECT_URI", "http://localhost")],
}
}
if creds_file.exists():
flow = InstalledAppFlow.from_client_secrets_file(str(creds_file), self.scopes)
else:
flow = InstalledAppFlow.from_client_config(client_config, self.scopes)
self.creds = flow.run_local_server(port=0)
token_file.write_text(self.creds.to_json()) if self.creds else None

Expand Down Expand Up @@ -179,3 +252,53 @@ def update_sheet(

except Exception as e:
return f"Error updating Google Sheet: {e}"

@authenticate
def create_duplicate_sheet(self, source_id: str, new_title: Optional[str] = None) -> str:
"""
Create a new Google Sheet that duplicates an existing one.
Args:
source_id: The ID of the source spreadsheet to duplicate
new_title: Optional new title (defaults to "Copy of [original title]")
Returns:
The ID of the created Google Sheet
"""
if not self.creds:
return "Not authenticated. Call auth() first."

try:
service = build("sheets", "v4", credentials=self.creds)

# Get the source spreadsheet to copy its properties
source = service.spreadsheets().get(spreadsheetId=source_id).execute()

if not new_title:
new_title = f"{source['properties']['title']}"

body = {"properties": {"title": new_title}, "sheets": source["sheets"]}

# Create new spreadsheet with copied properties
spreadsheet = service.spreadsheets().create(body=body, fields="spreadsheetId").execute()

spreadsheet_id = spreadsheet.get("spreadsheetId")

# Copy the data from source to new spreadsheet
for sheet in source["sheets"]:
range_name = sheet["properties"]["title"]

# Get data from source
result = service.spreadsheets().values().get(spreadsheetId=source_id, range=range_name).execute()
values = result.get("values", [])

if values:
# Copy to new spreadsheet
service.spreadsheets().values().update(
spreadsheetId=spreadsheet_id, range=range_name, valueInputOption="RAW", body={"values": values}
).execute()

return f"Spreadsheet created: https://docs.google.com/spreadsheets/d/{spreadsheet_id}"

except Exception as e:
return f"Error creating duplicate Google Sheet: {e}"

0 comments on commit 8a81e69

Please sign in to comment.