Skip to main content

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).

  1. Open your app and go to Settings (gear icon) → Integrations
  2. Enable the API and note your App ID
  3. 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 %20 automatically) 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

_ComputedKey is 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_appsheet-0.3.0.tar.gz (14.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_appsheet-0.3.0-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

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

Hashes for py_appsheet-0.3.0.tar.gz
Algorithm Hash digest
SHA256 1c52ec0d674f9fe5894a5d31d95788e8072f3830ddbab65a0c93b2ccd3776f6b
MD5 84ea86d3f6ec07a098fe3919c1312c02
BLAKE2b-256 c55de5d9043ce2c7cd1514df912e1b1b80d08d0e044870b9ce2c9c2259aaa72f

See more details on using hashes here.

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

Hashes for py_appsheet-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4c6ea683d623b644dd8ad6655b5d9bd662a81e3b7f1a167e6454b602943516e1
MD5 e5c97e40dfa859539cdd73791e38d22e
BLAKE2b-256 9e0fe1a191e3cf35440235e89d1ece0471b012a8985d5f59489006c7be52f61a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page