I built snowflake-rest because the official Snowflake connector is absurdly heavy for what most serverless workloads actually need.
The problem is simple: snowflake-connector-python pulls in 50+ MB of C extensions. On AWS Lambda or Cloud Functions, that’s a cold start tax you pay on every invocation. If all you’re doing is running queries, there’s no reason to ship a native binary just to talk to a REST API.
Snowflake has a SQL API — a proper HTTP endpoint that accepts SQL and returns JSON. But using it directly is surprisingly painful. You need to:
- Load your RSA private key, compute the SHA-256 fingerprint of the public key’s DER encoding
- Generate a JWT with a very specific issuer format (
ACCOUNT.USER.SHA256_FINGERPRINT) - Cache and refresh tokens before expiry
- Handle 202 responses (async promotion) by polling a status URL
- Parse paginated results across multiple partitions
- Coerce every value from strings back to proper types using result metadata
- Deal with the binding limitation in multi-statement transactions
That’s easily 800-1,000 lines of boilerplate before you write a single business query. So I wrapped it all up.
The hard part: transactions
The trickiest piece was transaction support. Snowflake’s multi-statement API has a quirk: parameter bindings (? placeholders) only work on the first statement. Every subsequent statement in the transaction needs values inlined directly into the SQL.
This means you need safe escaping that handles every edge case — NULL, booleans, timestamps, strings with embedded quotes, numeric types. Get it wrong and you have SQL injection in your own client library. I ended up building an escaping layer that maps Python types to their Snowflake SQL literals, with extensive test coverage for the weird cases.
What it looks like now
from snowflake_rest import SnowflakeClient
client = SnowflakeClient.from_env()
# Simple query
rows = client.query("SELECT * FROM users WHERE status = ?", ["active"])
# Transaction
with client.transaction() as tx:
tx.set("now_ts", "CURRENT_TIMESTAMP()")
tx.execute("UPDATE users SET updated=$now_ts WHERE id=?", [42])
tx.execute("INSERT INTO audit (action, ts) VALUES (?, $now_ts)", ["update"])
# Streaming for large results
for row in client.query_stream("SELECT * FROM events"):
process(row)
It’s on PyPI — pip install snowflake-rest. Under 100 KB, three dependencies (requests, PyJWT, cryptography), works everywhere Python does.