betterspread logo
betterspreadv1.1.0 · PyPI
A1fx=AWAIT(gspread)
RowABC
1

betterspread

2

Every cell and row, first-class and async.

3

An async Python wrapper around gspread that gives every cell and row real async methods — read, write, clear, style and delete without ever leaving your async/await flow.

4
$ pip install betterspreadRead the docsView source

Python ≥ 3.10 · or uv add betterspread

Why betterspread?

gspread is a great library — but it’s synchronous, and it works at the spreadsheet level.

RowA · FeatureB · gspreadC · betterspread
1Async-native APINot supportedSupported
2Cell as a first-class objectNot supportedSupported
3Row as a first-class objectNot supportedSupported
4Per-cell update / clear / style / deleteNot supportedSupported
5Per-row update / clear / style / deleteNot supportedSupported
6Lazy connection (opens on first use)Not supportedSupported
7Credentials from a file or a dictNot supportedSupported
8Automatic retry/backoff on rate limitsNot supportedSupported
9Numeric cell accessor (cell.number)Not supportedSupported

Quick start

Point it at a service-account key, name your sheet, and await.

main.py
import asyncio
from betterspread import Connection, Sheet

async def main():
    con = Connection(credentials_path="./credentials.json")
    sheet = Sheet(connection=con, sheet_name="My Spreadsheet")

    tab = await sheet.get_tab("Sheet1")

    # --- read ---
    rows = await tab.values()          # list[Row]
    row  = await tab.get_row(1)        # Row (1-based)
    cell = await tab.get_cell("B2")    # Cell

    print(cell)                        # Cell is a plain str subclass
    print(row[0].label, row[0])        # "A"  "hello"

    # --- write ---
    cell = await cell.update("world")
    await row.update(["Alice", "30", "Engineer"])

    # --- append ---
    await tab.append(["Bob", "25", "Designer"])

    # --- clear / delete ---
    await cell.clear()
    await row.delete()

asyncio.run(main())

The objects

Six of them. That’s the whole library.

RowA · ObjectB · What it isC · Methods
1Connection

Authenticates with Google and holds the gspread client.

Connection(credentials_path=..., credentials_dict=...)
  • credentials_path
  • credentials_dict
2Sheet

A spreadsheet. Opens lazily — no network call until the first await.

Sheet(sheet_name, connection, folder_id=None)
  • await open()
  • await get_tab(name)
  • await tabs()
3Tab

A worksheet. Reads return Rows and Cells, not raw lists.

tab = await sheet.get_tab('Sheet1')
  • await values()
  • await get_row(n)
  • await get_cell('B2')
  • await append([...])
  • await del_row(start, end)
  • await del_cell(...)
4Row

A list subclass — index it like a list, await methods on it.

row = await tab.get_row(1)
  • await update([...])
  • await clear()
  • await style(obj)
  • await refetch()
  • await append_cell(v)
  • await delete()
5Cell

A str subclass — use it as a string, or read cell.number.

cell = await tab.get_cell('B2')
  • cell.number
  • cell.label
  • await update(v)
  • await clear()
  • await style(obj)
  • await delete(shift='left')
6Style

Formatting, without touching gspread_formatting directly.

Style(bg_color='#fff', bold=True, ...)
  • bg_color
  • text_color
  • horizontal_align
  • vertical_align
  • bold
  • italic
  • strikethrough

Google API setup

One-time, and you need a service account.

1Open the Google Cloud Console, create or select a project.
2Enable the Google Sheets API and the Google Drive API.
3APIs & Services → Credentials → Create Credentials → Service Account.
4Open the service account → Keys → download a JSON key as credentials.json.
5Share your Sheet with the service account's client_email as an Editor.

Keep the key out of git. credentials.json is in .gitignore by default — never commit it. Loading from an env var works too: Connection(credentials_dict=json.loads(os.environ["GOOGLE_CREDENTIALS"]))

betterspread runs gspread’s blocking calls in a thread pool and retries transient 429/5xx errors with exponential backoff. Size the pool with BETTERSPREAD_MAX_WORKERS.