Skip to main content

Scan Google Sheet

Read public Google Sheets into Polars DataFrames and LazyFrames — no auth, no service accounts, no API keys.

PyPI Python License: MIT


🦀 Now powered by Rust

This is a from-scratch rewrite of the original pure-Python scan-google-sheet. The public API is unchanged — read_google_sheet, scan_google_sheet, and the same exception hierarchy all work exactly as before — but the fetch-and-parse core underneath is now Rust, compiled to a native extension via PyO3 and maturin.

Before (0.1.x) Now (0.2.0)
HTTP client httpx (Python) reqwest (Rust, blocking + rustls)
CSV parsing polars (Python API) polars (Rust, native)
GIL behaviour Held during fetch + parse Released for the whole fetch + parse
Distribution Pure-Python wheel Compiled extension module (_core) per platform
Public API read_google_sheet, scan_google_sheet Unchanged — drop-in upgrade
Errors Python exception hierarchy Same hierarchy, raised from Rust

Why it's faster: the HTTP request and CSV parse both happen in Rust with the Python GIL released, so a fetch no longer blocks other Python threads, and the parse avoids the overhead of going through Python's polars bindings twice (once for the raw text, once for the frame).

What didn't change: if you're upgrading from 0.1.x, this is a drop-in replacement — same function signatures, same return types, same exceptions. Nothing in your calling code needs to change.


Requirements

  • Python ≥ 3.9
  • The spreadsheet must be set to Anyone with the link can view

Installation

pip install scan-google-sheet
# or
uv add scan-google-sheet

Prebuilt wheels are published for common platforms/Python versions; pip falls back to building from source (requires a Rust toolchain) if no matching wheel is available.


Quick start

from scan_google_sheet import read_google_sheet, scan_google_sheet

Eager — returns a DataFrame immediately:

df = read_google_sheet("Sheet1", sheet_id="1BxiMVs0XRA5nFMdKvBdBZjgm...")

Lazy — returns a LazyFrame, participates in Polars query optimisation:

lf = scan_google_sheet("Sheet1", sheet_id="1BxiMVs0XRA5nFMdKvBdBZjgm...")

df = (
    lf
    .filter(pl.col("year") == 2025)
    .select("vessel", "amount")
    .collect()
)

You can also pass a full Google Sheets URL instead of a bare sheet ID:

df = read_google_sheet(
    "Sheet1",
    url="https://docs.google.com/spreadsheets/d/1BxiMVs0.../edit#gid=0",
)

API

read_google_sheet

def read_google_sheet(
    sheet_name: str,
    sheet_id: str | None = None,
    url: str | None = None,
    *,
    timeout: int = 10,
    parse_dates: bool = True,
) -> pl.DataFrame

Fetches the sheet and returns a collected DataFrame. The HTTP fetch and CSV parse both run in Rust, released from the Python GIL.

scan_google_sheet

def scan_google_sheet(
    sheet_name: str,
    sheet_id: str | None = None,
    url: str | None = None,
    *,
    timeout: int = 10,
    parse_dates: bool = True,
    batch_size: int = 1_000,
) -> pl.LazyFrame

Returns a LazyFrame registered via the Polars IO plugin API. Projection pushdown, predicate pushdown, head(), and streaming are all supported.

Note: Google Sheets does not support partial HTTP reads. The full sheet is always downloaded in one request by the Rust core. Pushdowns reduce processing cost, not network cost. This is the one part of the API that stays in Python: register_io_source is a Polars Python-only hook with no Rust-side equivalent, so scan_google_sheet is a thin Python wrapper around the Rust-backed read_google_sheet.

Parameters shared by both functions:

Parameter Type Default Description
sheet_name str — Tab name as shown in Google Sheets
sheet_id str | None None Spreadsheet ID from the URL
url str | None None Full Google Sheets URL (ID extracted automatically)
timeout int 10 HTTP timeout in seconds
parse_dates bool True Attempt automatic date/datetime parsing

Provide either sheet_id or url, not both.


URL utilities

from scan_google_sheet import extract_sheet_id, build_gviz_url, from_url

# Extract the sheet ID from any Google Sheets URL
sheet_id = extract_sheet_id("https://docs.google.com/spreadsheets/d/ABC123/edit")
# "ABC123"

# Build a gviz CSV export URL from a sheet ID and tab name
url = build_gviz_url("ABC123", "Sheet1")
# "https://docs.google.com/spreadsheets/d/ABC123/gviz/tq?tqx=out:csv&sheet=Sheet1"

# Build a gviz URL directly from a full Google Sheets URL
url = from_url("https://docs.google.com/spreadsheets/d/ABC123/edit", "Sheet1")

Error handling

All exceptions inherit from ReadSheetError, so you can catch everything with one handler or branch on specific types:

from scan_google_sheet import (
    read_google_sheet,
    ReadSheetError,
    SheetFetchError,
    SheetURLError,
    SheetParseError,
    NetworkError,
    ConfigurationError,
)

try:
    df = read_google_sheet("Sheet1", sheet_id="...")
except ReadSheetError as e:
    match e:
        case SheetFetchError() if e.is_auth_error:
            print("Make the sheet public (Share → Anyone with the link)")
        case SheetFetchError() if e.is_not_found:
            print(f"Sheet not found — check the ID: {e.url}")
        case NetworkError():
            print(f"No connection: {e.cause}")
        case SheetURLError(raw=r):
            print(f"Could not parse URL: {r!r}")
        case SheetParseError():
            print(f"CSV parse failed: {e.cause}")
        case ConfigurationError():
            print(str(e))

Exception hierarchy

ReadSheetError
├── SheetURLError       malformed URL or unextractable sheet ID  (.raw)
├── SheetFetchError     non-200 HTTP response                    (.url, .status_code)
│                                                                (.is_auth_error, .is_not_found)
├── SheetParseError     CSV or Polars parsing failure             (.column)
├── NetworkError        transport failure, no response received  (.url)
└── ConfigurationError  invalid argument combination

Raised from Rust via PyO3, but the hierarchy, messages, and attributes are the same ones the pure-Python 0.1.x release raised.


Making your sheet public

In Google Sheets: Share → Change to Anyone with the link → Viewer → Done.

The export URL used by this library (gviz/tq?tqx=out:csv) requires the sheet to be publicly readable. No data is ever written.


How it works

Google Sheets URL / ID
        │
        ▼
  build_gviz_url()          constructs the CSV export URL          [Rust]
        │
        ▼
    fetch_raw()              reqwest GET, GIL released              [Rust]
        │
        ▼
    parse_csv()              polars CSV → DataFrame                 [Rust]
        │
        ▼
  read_google_sheet()        PyO3 boundary → pl.DataFrame     [Rust → Python]
        │
        ▼
  scan_google_sheet()        register_io_source() lazy wrapper     [Python]
        │
        ▼
  LazyFrame / DataFrame      ready for your pipeline

The crate also builds as a standalone Rust library (cargo build, no Python required) — see src/lib.rs for the read_public_sheet entry point used by the python feature's PyO3 bindings.


Development

git clone https://github.com/Attica-oss/scan_google_sheet
cd scan_google_sheet

# Rust-only build/check
cargo build
cargo test

# Python extension, editable install into a venv
uv venv
source .venv/bin/activate
uv pip install maturin
maturin develop --features python

Changelog

0.2.0

  • Full rewrite of the fetch/parse core in Rust (PyO3 + maturin), GIL released during network and parse work
  • Public API and exception hierarchy unchanged from 0.1.x
  • scan_google_sheet's lazy IO-plugin wrapper stays in Python (Polars register_io_source has no Rust-side equivalent)

0.1.1 and earlier

  • Pure-Python implementation (httpx + polars)
  • read_google_sheet and scan_google_sheet
  • Polars IO plugin for lazy evaluation
  • Structured exception hierarchy

License

MIT © 2026 Garry (Attica-oss)

Metadata

Release files for scan-google-sheet 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for scan-google-sheet 0.2.0
File Interpreter ABI Platform
scan_google_sheet-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.17+ x86-64 Details

Release files / scan_google_sheet-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL scan_google_sheet-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 7.7 MB
Tags CPython 3.13 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
f3eb107d377c89893068e31f87d5c88defbcfe9e6429ae675bf6a6f6e22b247a
BLAKE2b-256 checksum
How to use checksums
e7e876b3f2d61754e60693a7fcf64c2df4b4913ceddffb49f55b9f7b7c92cb27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.3.0

28 release files

0.2.1

1 release file

This release

0.2.0 This release

1 release file

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page