add_task wrote something — a task appeared in Google Tasks the moment you called it. Sheets works the same direction but in reverse first: you read before you write. Your rolling KPI tracker sits in a Sheet right now. How do you get that data into Python?
With add_task I passed a list ID and the data I wanted to insert. For Sheets I'd guess I pass some kind of sheet ID and tell it which cells I want — like a named range?
Exactly. GOOGLESHEETS_BATCH_GET takes a spreadsheet_id and a ranges list — one or more A1 notation strings like "Sheet1!A1:C10". Here's the call:
result = toolset.execute_action(Action.GOOGLESHEETS_BATCH_GET, {
"spreadsheet_id": sheet_id,
"ranges": [range_a1],
})The response key is valueRanges — plural? And it's a list even though I only asked for one range?
Right — the action is called "batch" because it supports fetching multiple ranges in a single call. Even when you pass one range you get back a list. Index zero is your range: result.get("valueRanges", [{}])[0].get("values", []). That inner values key holds the 2D list — a list of rows, each row a list of strings.
So a row in Sheets is just a list of strings. Every cell is a string in a list. If I have 50 rows in my KPI tracker I get back 50 sublists, and I can loop or filter them directly in Python without ever touching a browser.
You just described the entire weekly KPI ritual in one sentence. That's Week 3 thinking. Here's the full function — note the double .get() so an empty sheet returns an empty list instead of crashing:
def read_range(sheet_id: str, range_a1: str) -> list:
result = toolset.execute_action(Action.GOOGLESHEETS_BATCH_GET, {
"spreadsheet_id": sheet_id,
"ranges": [range_a1],
})
values = result.get("valueRanges", [{}])[0].get("values", [])
print(f"Read {len(values)} rows from {range_a1}")
return valuesAnd once I have this list I can pass it straight to the next action — format it as a report, compare it to last week's numbers, append a summary row. Reading is always the first step in any workflow.
Exactly. Every cross-app workflow in Week 4 starts here: read the Sheet, transform the data, then write somewhere else. The next step is appending rows — GOOGLESHEETS_SPREADSHEETS_VALUES_APPEND — which is the write side of the same sheet.
GOOGLESHEETS_BATCH_GET returns a valueRanges list — always a list, even for one range.
result["valueRanges"][0]["values"] → [["row1col1", "row1col2"], ["row2col1", ...]]
Use double .get() so an empty sheet or missing range returns [] instead of raising a KeyError.
"Sheet1!A1:C10" selects rows 1–10, columns A–C. Omit the sheet name ("A1:C10") to use the first sheet. Pass the exact string your spreadsheet uses.
Reads are idempotent — call read_range as many times as needed. Transformations happen in Python; the sheet is never modified.
add_task wrote something — a task appeared in Google Tasks the moment you called it. Sheets works the same direction but in reverse first: you read before you write. Your rolling KPI tracker sits in a Sheet right now. How do you get that data into Python?
With add_task I passed a list ID and the data I wanted to insert. For Sheets I'd guess I pass some kind of sheet ID and tell it which cells I want — like a named range?
Exactly. GOOGLESHEETS_BATCH_GET takes a spreadsheet_id and a ranges list — one or more A1 notation strings like "Sheet1!A1:C10". Here's the call:
result = toolset.execute_action(Action.GOOGLESHEETS_BATCH_GET, {
"spreadsheet_id": sheet_id,
"ranges": [range_a1],
})The response key is valueRanges — plural? And it's a list even though I only asked for one range?
Right — the action is called "batch" because it supports fetching multiple ranges in a single call. Even when you pass one range you get back a list. Index zero is your range: result.get("valueRanges", [{}])[0].get("values", []). That inner values key holds the 2D list — a list of rows, each row a list of strings.
So a row in Sheets is just a list of strings. Every cell is a string in a list. If I have 50 rows in my KPI tracker I get back 50 sublists, and I can loop or filter them directly in Python without ever touching a browser.
You just described the entire weekly KPI ritual in one sentence. That's Week 3 thinking. Here's the full function — note the double .get() so an empty sheet returns an empty list instead of crashing:
def read_range(sheet_id: str, range_a1: str) -> list:
result = toolset.execute_action(Action.GOOGLESHEETS_BATCH_GET, {
"spreadsheet_id": sheet_id,
"ranges": [range_a1],
})
values = result.get("valueRanges", [{}])[0].get("values", [])
print(f"Read {len(values)} rows from {range_a1}")
return valuesAnd once I have this list I can pass it straight to the next action — format it as a report, compare it to last week's numbers, append a summary row. Reading is always the first step in any workflow.
Exactly. Every cross-app workflow in Week 4 starts here: read the Sheet, transform the data, then write somewhere else. The next step is appending rows — GOOGLESHEETS_SPREADSHEETS_VALUES_APPEND — which is the write side of the same sheet.
GOOGLESHEETS_BATCH_GET returns a valueRanges list — always a list, even for one range.
result["valueRanges"][0]["values"] → [["row1col1", "row1col2"], ["row2col1", ...]]
Use double .get() so an empty sheet or missing range returns [] instead of raising a KeyError.
"Sheet1!A1:C10" selects rows 1–10, columns A–C. Omit the sheet name ("A1:C10") to use the first sheet. Pass the exact string your spreadsheet uses.
Reads are idempotent — call read_range as many times as needed. Transformations happen in Python; the sheet is never modified.
Create a free account to get started. Paid plans unlock all tracks.