Skip to main content

snowflake-rest

PyPI GitHub

PyPI | GitHub | Issues

Lightweight Python client for the Snowflake SQL API. No native connector needed.

Built for serverless environments (Cloud Functions, AWS Lambda) where snowflake-connector-python is too heavy (50+ MB, C dependencies, slow cold starts). Works anywhere Python runs.

CLI Demo

Table of Contents

Install

pip install snowflake-rest

Dependencies: requests, PyJWT, cryptography

Quick Start

from snowflake_rest import SnowflakeClient

client = SnowflakeClient(
    account="myorg-myaccount",
    user="SVC_USER",
    private_key_path="~/.snowflake/rsa_key.p8",
    database="MY_DB",
    schema="PUBLIC",
    warehouse="COMPUTE_WH",
)

rows = client.query("SELECT * FROM users WHERE status = ?", ["active"])
# [{"ID": 42, "NAME": "Alice", "CREATED_AT": datetime(2026, 1, 15, ...), "BALANCE": Decimal("150.00")}]

Or from environment variables (suited for serverless):

client = SnowflakeClient.from_env()

Features

Queries

# List of dicts (default)
rows = client.query("SELECT * FROM users WHERE age > ?", [25])

# Single row
row = client.query_one("SELECT * FROM users WHERE id = ?", [42])

# Single value
count = client.query_scalar("SELECT COUNT(*) FROM users")

# Single column as flat list
ids = client.query_column("SELECT id FROM users")

# Tuples (lighter than dicts)
rows = client.query("SELECT id, name FROM users", as_tuples=True)
# [(1, "Alice"), (2, "Bob")]

Type Coercion

Snowflake SQL API returns everything as strings. This library auto-converts using result metadata:

Snowflake Type Python Type
FIXED (scale=0) int
FIXED (scale>0) Decimal
REAL / FLOAT float
BOOLEAN bool
TIMESTAMP_* datetime
DATE date
TIME time
VARIANT / OBJECT / ARRAY dict / list

Disable with type_coercion=False if you want raw strings.

Result Mapping

Map results directly to dataclasses or Pydantic models:

from dataclasses import dataclass

@dataclass
class User:
    ID: int
    NAME: str
    STATUS: str

users = client.query("SELECT * FROM users", row_type=User)
# [User(ID=42, NAME="Alice", STATUS="active"), ...]

Transactions

with client.transaction() as tx:
    tx.set('now_ts', 'CURRENT_TIMESTAMP()')
    tx.execute("UPDATE users SET status=?, updated=$now_ts WHERE id=?", ["inactive", 42])
    tx.execute("INSERT INTO audit (action, ts) VALUES (?, $now_ts)", ["deactivate"])
# Atomic: all succeed or none do

Handles Snowflake's multi-statement binding limitation internally with safe value escaping.

Batch Insert

client.insert_many("users", [
    {"NAME": "Alice", "AGE": 30},
    {"NAME": "Bob", "AGE": 25},
    # ... 10,000 rows
], batch_size=1000)

Async Queries

handle = client.submit("SELECT * FROM huge_table")
print(handle.status())  # "running" | "complete" | "failed"
rows = handle.result()  # blocks until done

Stored Procedures

result = client.call("MY_PROCEDURE", [arg1, arg2])
# Parsed VARIANT result (dict, list, or scalar)

Export (COPY INTO)

export = client.export(
    sql="SELECT * FROM users WHERE status = ?",
    params=["active"],
    stage="@MY_STAGE",
    path="exports/2026/",
)
# ExportResult(rows_unloaded=5000, file_size=102400, path="@MY_STAGE/exports/2026/export_abc.csv.gz")

Streaming

for row in client.query_stream("SELECT * FROM billion_row_table"):
    process(row)  # one row at a time, partitions fetched lazily

Pandas

pip install snowflake-rest[pandas]
df = client.query_df("SELECT * FROM users")
# pandas DataFrame with correct dtypes

Retries

client = SnowflakeClient(..., retries=3)
# Auto-retries on: connection errors, 429, 502, 503, 504
# Does NOT retry on: syntax errors, permission denied

Query Profiling

def log_query(sql, duration_ms, rows_returned, query_id):
    print(f"[{query_id}] {duration_ms:.0f}ms, {rows_returned} rows")

client = SnowflakeClient(..., on_query=log_query)

Serverless Usage

# handler.py — works with AWS Lambda, Google Cloud Functions, Azure Functions, etc.
from snowflake_rest import SnowflakeClient

client = SnowflakeClient.from_env()

def handler(event, context):
    rows = client.query(
        "SELECT * FROM users WHERE status = ?",
        [event["status"]]
    )
    return {"statusCode": 200, "body": rows}

No C extensions, no connector binaries — just pip install snowflake-rest in your deployment package.

CLI

export SNOWFLAKE_ACCOUNT=myorg-myaccount
export SNOWFLAKE_USER=SVC_USER
export SNOWFLAKE_PRIVATE_KEY_PATH=~/.snowflake/rsa_key.p8
export SNOWFLAKE_DATABASE=MY_DB
export SNOWFLAKE_WAREHOUSE=COMPUTE_WH

snowflake-rest query "SELECT * FROM users LIMIT 5"
snowflake-rest query "SELECT * FROM users" --format csv > users.csv
snowflake-rest query "SELECT * FROM users" --format json
snowflake-rest query -f report.sql --format csv
snowflake-rest query "SELECT * FROM t WHERE id = ?" --params '[42]'

Configuration

Constructor

SnowflakeClient(
    account="myorg-myaccount",       # Required
    user="SVC_USER",                 # Required
    private_key="-----BEGIN...",     # PEM string/bytes
    private_key_path="/path/key.p8", # OR file path
    private_key_passphrase=b"pass",  # Optional
    database="MY_DB",               # Optional defaults
    schema="PUBLIC",
    warehouse="COMPUTE_WH",
    role="MY_ROLE",
    timeout=45,                      # Query timeout (seconds)
    poll_interval=2.0,               # Async poll interval
    max_poll_time=300.0,             # Max async wait
    type_coercion=True,              # Auto type conversion
    timezone="UTC",                  # Session timezone
    retries=0,                       # Retry on transient errors
    pool_size=10,                    # HTTP connection pool size
    on_query=callback,               # Profiling hook
)

Environment Variables

Variable Required Description
SNOWFLAKE_ACCOUNT Yes Account identifier
SNOWFLAKE_USER Yes Username
SNOWFLAKE_PRIVATE_KEY One of these PEM key string
SNOWFLAKE_PRIVATE_KEY_PATH One of these Path to .p8 file
SNOWFLAKE_DATABASE No Default database
SNOWFLAKE_SCHEMA No Default schema
SNOWFLAKE_WAREHOUSE No Default warehouse
SNOWFLAKE_ROLE No Default role

Error Handling

from snowflake_rest import QueryError, AuthError, TimeoutError

try:
    rows = client.query("SELECT * FROM t")
except QueryError as e:
    print(e.error_code)    # "002003"
    print(e.sql_state)     # "42S02"
    print(e.args[0])       # "Object 'T' does not exist."
except AuthError:
    print("Key/JWT issue")
except TimeoutError:
    print("Query took too long")

Comparison with snowflake-connector-python

snowflake-rest snowflake-connector-python
Package size < 100 KB 50+ MB
Native deps None Yes (C extensions)
Lambda-friendly Yes Needs layers, slow cold starts
Protocol REST (SQL API) Custom (Snowflake protocol)
Auth JWT keypair only Multiple methods
Features Query, transactions, async, export Full feature set

Use snowflake-connector-python if you need: OAuth/SSO auth, PUT/GET file transfer, or Snowpark. Use snowflake-rest if you want a lightweight client that just works in serverless.

Using the Snowflake SQL API directly requires:

1. Load RSA private key, compute SHA-256 fingerprint of the public key DER
2. Generate JWT (iss = ACCOUNT.USER.FINGERPRINT, RS256, 1hr expiry)
3. Cache and refresh tokens before expiry
4. POST to /api/v2/statements with Bearer token + KEYPAIR_JWT header
5. Handle 202 async promotion — poll statementStatusUrl until 200
6. Handle retries on 429/502/503/504
7. For transactions: bindings only apply to the first statement,
   so manually escape and inline values for all subsequent statements
8. Parse paginated results across multiple partitions
9. Coerce all values from strings (the API returns everything as strings)

That typically means maintaining a custom client of 800–1,000 lines, plus boilerplate at every call site:

# Snowflake SQL API — transaction with manual bindings
statements = [
    {"sql": "SET now_ts = CURRENT_TIMESTAMP()", "bindings": {}},
    {
        "sql": 'UPDATE users SET "STATUS"=?, "DISPOSITION"=?, '
               '"UPDATED_TS"=$now_ts WHERE "ID"=?',
        "bindings": {
            "1": {"type": "TEXT", "value": status},
            "2": {"type": "TEXT", "value": disposition},
            "3": {"type": "FIXED", "value": str(uid)},
        },
    },
    {
        "sql": 'INSERT INTO audit_log ("USER_ID", "ACTION", "DETAIL", "TS") '
               "VALUES (?, ?, ?, $now_ts)",
        "bindings": {
            "1": {"type": "FIXED", "value": str(uid)},
            "2": {"type": "TEXT", "value": "status_change"},
            "3": {"type": "TEXT", "value": f"{status}/{disposition}"},
        },
    },
]
result = client.execute_transaction(statements)
# + the 800–1,000 lines behind execute_transaction:
#   JWT fingerprinting, token caching, async polling,
#   value escaping, pagination, retry logic, etc.

With snowflake-rest, all of the above is handled internally:

with client.transaction() as tx:
    tx.set("now_ts", "CURRENT_TIMESTAMP()")
    tx.execute(
        'UPDATE users SET "STATUS"=?, "DISPOSITION"=?, "UPDATED_TS"=$now_ts WHERE "ID"=?',
        [status, disposition, uid],
    )
    tx.execute(
        'INSERT INTO audit_log ("USER_ID", "ACTION", "DETAIL", "TS") '
        "VALUES (?, ?, ?, $now_ts)",
        [uid, "status_change", f"{status}/{disposition}"],
    )

Benchmarks

Each library ran 3 times with the connector executing first to avoid cache bias.

snowflake-rest snowflake-connector-python
Run 1 Run 2 Run 3 Run 1 Run 2 Run 3
Import 0.11s 0.08s 0.08s 0.31s 0.29s 0.27s
Connect 1.14s 1.51s 1.17s 19.84s 3.63s 1.38s
Cold query 0.64s 0.73s 0.71s 0.39s 0.60s 0.55s
Warm query 0.63s 0.53s 0.66s 0.36s 0.61s 0.56s
Total 2.52s 2.85s 2.61s 20.90s 5.12s 2.76s

Query speeds are comparable, but snowflake-rest has a faster and more consistent connection setup. The official connector's connect time swings from 1.4s to 19.8s depending on warehouse state — in serverless environments where cold starts are frequent, this unpredictability is the main bottleneck.

Install size: 40 MB (with deps) vs 117 MB.

Benchmarks run with warmup=True. See examples repo for benchmark scripts.

License

MIT

Metadata

Release files for snowflake-rest 0.1.4

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

Source distribution (sdist)

Source distribution for snowflake-rest 0.1.4
File Size Uploaded Provenance
snowflake_rest-0.1.4.tar.gz 38.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for snowflake-rest 0.1.4
File Interpreter ABI Platform
snowflake_rest-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 66.6 kB

Release files / snowflake_rest-0.1.4.tar.gz

Download URL snowflake_rest-0.1.4.tar.gz
Size 38.7 kB
Tags Source
SHA-256 checksum
How to use checksums
90730e1f114856612209fb25754dcf1339d53414fee27b44236eb92f2cd56796
BLAKE2b-256 checksum
How to use checksums
48ca6373e4ae12b2f1862327a9a5f84a21309458c1d8167c9e38c0e00e2321c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.3

Release files / snowflake_rest-0.1.4-py3-none-any.whl

Download URL snowflake_rest-0.1.4-py3-none-any.whl
Size 27.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ca8682f375fb3417831ac3cf1ee226b8b58c1735f718a26777ec2030fbdb0b3d
BLAKE2b-256 checksum
How to use checksums
aed7131a860f99b117bff26945c6b6a7460617d71ac37ee749ca0ee1289070c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.3

Security for snowflake-rest 0.1.4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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