A no-frills Python library for interacting with the Google AppSheet API.
Project description
py-appsheet
A no-frills Python library for interacting with the Google AppSheet API. Depends only on requests — no other third-party dependencies.
Installation
pip install py-appsheet
Setup
To use the AppSheet API you need an AppSheet App (not just an AppSheet Database).
- Open your app and go to Settings (gear icon) → Integrations
- Enable the API and note your App ID
- Generate an Application Access Key
Store these as environment variables — never hardcode them:
import os
from py_appsheet import AppSheetClient
client = AppSheetClient(
app_id=os.environ.get("APPSHEET_APP_ID"),
api_key=os.environ.get("APPSHEET_API_KEY"),
)
locale and timezone default to "en-US" and "UTC". If AppSheet is misinterpreting date values, pass your local settings:
client = AppSheetClient(
app_id=os.environ.get("APPSHEET_APP_ID"),
api_key=os.environ.get("APPSHEET_API_KEY"),
locale="de-DE",
timezone="Europe/Berlin",
)
Export & Schema Workflow
Py-appsheet includes methods to help backup and export your tables and their schemas. The recommended workflow for exporting data safely:
Step 1 — Infer the schema from live data:
# Single table
schema = client.infer_schema("Orders")
# Multiple tables at once — returns {table_name: schema}
schemas = client.infer_all_schemas(["Orders", "Patients", "Results"])
This fetches all rows, infers column types, and returns a schema dict with contains_pii=False on every column.
Step 2 — Review and mark PII columns:
Save the schema to a JSON file, edit it, and flip contains_pii to true for any columns that contain personal data. Optionally correct any type mismatches.
{
"Orders": {
"columns": [
{"name": "patient_name", "contains_pii": true, "inferred_type": "string"},
{"name": "order_ref", "contains_pii": false, "inferred_type": "string"}
]
}
}
Step 3 — Export using the schema:
import json
with open("schemas.json") as f:
schemas = json.load(f)
# Full export (emits a UserWarning if PII columns are present and not redacted)
data, log = client.export_all_tables(["Orders", "Patients"], schemas=schemas)
# De-identified export — PII columns replaced with "[REDACTED]"
data, log = client.export_all_tables(["Orders", "Patients"], schemas=schemas, redact_pii=True)
See examples/export_workflow.py for a complete runnable example.
Note: Columns that are entirely blank across all rows may be omitted from the AppSheet API response and will be absent from the inferred schema. If your table has always-empty columns, add them manually to the schema JSON.
Note: Redacted values are always replaced with the string
"[REDACTED]"regardless of the column's original type (number, boolean, etc.). AppSheet returns all values as strings, so this is consistent with the data format throughout.
Methods
find_items — Read
Search a table for rows matching a value. Supports both local filtering (simple) and server-side filtering via an AppSheet selector expression (efficient for large tables).
# Return all rows in a table
rows = client.find_items("My Table")
# Filter by a specific column (local)
rows = client.find_items("My Table", "ABC123", target_column="Serial Number")
# Filter across all columns (local)
rows = client.find_items("My Table", "ABC123")
# Server-side filtering using an AppSheet selector expression (recommended for large tables)
from py_appsheet import build_selector
selector = build_selector("My Table", "Status", "In Progress")
rows = client.find_items("My Table", selector=selector)
# Combine: selector narrows server-side, then local filter refines further
rows = client.find_items("My Table", "Jane", target_column="Assignee", selector=selector)
add_items — Create
Add one or more rows to a table.
rows = [
{"Title": "Task A", "Assignee": "Alice", "Status": "Not Started"},
{"Title": "Task B", "Assignee": "Bob", "Status": "In Progress"},
]
response = client.add_items("My Table", rows)
update_item — Update
Update an existing row. The key column must be included in row_data.
response = client.update_item(
"My Table",
"Serial Number", # key column name
{
"Serial Number": "ABC123", # key column value (identifies the row)
"Status": "Complete", # fields to update
"Notes": "Shipped",
}
)
edit_item() is available as a backwards-compatible alias for update_item().
delete_item — Delete
Delete a row by its key.
# Single key column
response = client.delete_item("My Table", "Serial Number", "ABC123")
# Composite key: pass a dict of all key column values (see Composite Keys below)
response = client.delete_item("My Table", {"keycol1": "foo", "keycol2": "bar"})
delete_row() is available as a backwards-compatible alias for delete_item().
export_table — Full table export
# Export all rows
rows = client.export_table("Orders")
# With schema — ensures all schema columns present, even if blank in AppSheet
rows = client.export_table("Orders", schema=orders_schema)
# De-identified — PII columns replaced with "[REDACTED]"
rows = client.export_table("Orders", schema=orders_schema, redact_pii=True)
export_all_tables — Multi-table export
data, log = client.export_all_tables(
["Orders", "Patients"],
schemas=schemas, # dict of {table_name: schema}
redact_pii=True,
)
# data -> {"Orders": [...rows...], "Patients": [...rows...]}
# log -> {"status": "complete", "exported": [...], "failed": [...], ...}
Failed tables are logged and skipped — the export continues for remaining tables.
infer_schema / infer_all_schemas — Data-driven schema inference
# Single table
schema = client.infer_schema("Orders")
# Pass pre-fetched rows to avoid a redundant API call
rows = client.export_table("Orders")
schema = client.infer_schema("Orders", rows=rows)
# Multiple tables — returns {table_name: schema} ready for export_all_tables()
schemas = client.infer_all_schemas(["Orders", "Patients", "Results"])
diff_schemas — Schema change detection
from py_appsheet import diff_schemas
diff = diff_schemas(old_schema, new_schema)
# -> {"added": [...], "removed": [...], "type_changed": [...], "unchanged": [...]}
Works with schemas produced by infer_schema() or any user-provided schema dict containing a columns list with name and inferred_type (or appsheet_type) fields.
Composite Key Tables
When two or more columns are marked as keys in AppSheet, the app automatically creates
a computed key column (named _ComputedKey by default) whose value is the key columns
concatenated with ": " as the separator.
Use build_composite_key() to construct the expected _ComputedKey value for filtering:
from py_appsheet import build_composite_key
key = build_composite_key("foo", "bar") # -> "foo: bar"
# Find a row by its computed key
rows = client.find_items("My Table", key, target_column="_ComputedKey")
For update and delete, include all key columns directly in the row data — AppSheet
does not accept _ComputedKey in write payloads:
# Update: include all key columns + fields to update in row_data
client.update_item(
"My Table",
"keycol1", # any one key column goes first
{"keycol1": "foo", "keycol2": "bar", "val": "new"}, # all key cols + updated fields
)
# Delete: pass a dict of all key column values
client.delete_item("My Table", {"keycol1": "foo", "keycol2": "bar"})
Utilities
build_selector
Constructs an AppSheet Filter() expression for use with find_items().
from py_appsheet import build_selector
build_selector("Tasks", "Status", "In Progress")
# -> "Filter(Tasks, [Status] = 'In Progress')"
build_selector("Tasks", "Priority", "3", operator=">=")
# -> "Filter(Tasks, [Priority] >= '3')"
build_composite_key
Constructs a composite key string matching AppSheet's default _ComputedKey formula.
from py_appsheet import build_composite_key
build_composite_key("foo", "bar") # -> "foo: bar"
build_composite_key("a", "b", "c") # -> "a: b: c"
build_composite_key("x", "y", separator="|") # -> "x|y"
Troubleshooting
- Schema out of date: If you've added or changed columns in AppSheet, regenerate the schema in the app's Data view.
- Key column errors: Confirm your key column is marked correctly in AppSheet's column settings for that table.
- Detailed error logs: AppSheet → pulse icon → Monitor → Audit History → Launch Log Analyzer.
- Table name encoding: Table names may contain spaces (converted to
%20automatically) but should not contain other URL special characters (&,?,#).
Running Tests
Unit tests (no credentials needed):
pytest
Integration tests (requires a real AppSheet project):
pytest -m integration
Integration tests run against two specific tables. To set them up, create an AppSheet app backed by a spreadsheet with the following tables:
example_table
| Column | Type | Key? |
|---|---|---|
| Title Example | Text | ✅ |
| Assignee | Text | |
| Status | Enum (Not Started, In Progress, Complete) |
|
| Date | Date | |
| Another Column | Text |
dual_key_table
| Column | Type | Key? |
|---|---|---|
| keycol1 | Text | ✅ |
| keycol2 | Text | ✅ |
| val | Text |
_ComputedKeyis generated automatically by AppSheet when multiple key columns are present.
Add your App ID and access key to a .env file in the project root:
APP_ID=your-app-id
ACCESS_KEY=your-access-key
Contributing
Contributions are welcome. Please submit pull requests to the dev branch.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_appsheet-0.3.0.tar.gz.
File metadata
- Download URL: py_appsheet-0.3.0.tar.gz
- Upload date:
- Size: 14.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c52ec0d674f9fe5894a5d31d95788e8072f3830ddbab65a0c93b2ccd3776f6b
|
|
| MD5 |
84ea86d3f6ec07a098fe3919c1312c02
|
|
| BLAKE2b-256 |
c55de5d9043ce2c7cd1514df912e1b1b80d08d0e044870b9ce2c9c2259aaa72f
|
File details
Details for the file py_appsheet-0.3.0-py3-none-any.whl.
File metadata
- Download URL: py_appsheet-0.3.0-py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c6ea683d623b644dd8ad6655b5d9bd662a81e3b7f1a167e6454b602943516e1
|
|
| MD5 |
e5c97e40dfa859539cdd73791e38d22e
|
|
| BLAKE2b-256 |
9e0fe1a191e3cf35440235e89d1ece0471b012a8985d5f59489006c7be52f61a
|