squeezle (Python client)
A small, dependency-light Python client for the Squeezle SQL query API. Authenticate with a personal sqz_ token, run saved queries or ad-hoc SQL, and get the rows back.
Install
pip install squeezle # from this directory: pip install -e .
pip install "squeezle[pandas]" # add .to_pandas() support
Only runtime dependency: requests.
Quick start
from squeezle import Client
sqz = Client("sqz_...") # or: Client.from_env()
# Run a saved query and block until it finishes
run = sqz.run_saved("query-uuid", variables={"since": "2026-01-01"})
for row in run.dicts(): # up to ~1000 rows (stored preview)
print(row) # {"id": 7, "email": "a@b.com"}
df = run.to_pandas() # needs the [pandas] extra
Client.from_env() reads SQUEEZLE_TOKEN and, optionally, SQUEEZLE_BASE_URL (default https://api.squeezle.app).
Ad-hoc SQL
run = sqz.run_sql(
data_source_id="ds-uuid",
sql="SELECT status, count(*) FROM orders WHERE created_at > {{since}} GROUP BY 1",
variables={"since": "2026-01-01"},
row_limit=5000, # int, or "max"
)
print(run.dicts())
Your token needs the queries:write scope to run ad-hoc SQL, plus runs:read to read results.
Variables
{{name}} placeholders in SQL bind as typed Postgres params ($1, $2, ...), so values can never change the query shape (injection-safe). For a saved query the types are stored, so you pass only variables. For ad-hoc SQL, declare each variable's type in variable_definitions.
| type | JSON value you pass | example |
|---|---|---|
text |
string | "hello" |
integer |
int or numeric string | 42 |
float |
number or numeric string | 3.14 |
boolean |
bool (or "yes"/"no"/"1"/"0") |
True |
date |
"YYYY-MM-DD" string |
"2026-07-29" |
timestamp |
ISO-8601 string | "2026-07-29T14:00:00Z" |
uuid |
uuid string | "0189a2f0-...-444455556666" |
json |
object/array (bound as jsonb) | {"a": 1} |
text[] / integer[] / any *[] |
JSON array | ["paid","shipped"] |
date_range / timestamp_range |
{"start": ..., "end": ...} |
{"start": "2026-01-01", "end": "2026-03-31"} |
Three things that trip people up:
- Lists use
= ANY(...), notIN. A*[]variable binds as one array param, so writeWHERE id = ANY({{ids}}).WHERE id IN {{ids}}is invalid SQL. - A range is one variable, two placeholders. Define
periodasdate_range, then write{{period_start}}and{{period_end}}in the SQL; pass{"period": {"start": ..., "end": ...}}. requireddefaults toTrue. For an optional variable set"required": Falseand a"default", or you get a422"is required".
Enum / dropdown is type: "text" with control: "select" and options. Datetime is type: "timestamp". There is no number/list/enum type name.
run = sqz.run_sql(
"ds-uuid",
"SELECT * FROM orders WHERE status = ANY({{statuses}}) AND created_at >= {{since}}",
variables={"statuses": ["paid", "shipped"], "since": "2026-01-01"},
variable_definitions=[
{"name": "statuses", "type": "text[]"},
{"name": "since", "type": "date"},
],
)
See examples/variables.py for a runnable example of every type.
Getting all the data (beyond 1000 rows)
The API keeps only a ~1000-row preview in the database. For the full result, export to an artifact and download it:
rows = run.fetch_all("json") # list[dict], the whole result
csv_text = run.fetch_all("csv") # str
run.download("xlsx", "orders.xlsx") # straight to disk (csv/json/jsonl/xlsx)
url = run.export_url("csv") # short-lived signed URL, do it yourself
Two-step control (start now, wait later)
run = sqz.start_saved("query-uuid", variables={"n": 3}) # returns immediately (queued)
run.wait(timeout=120, poll_interval=1.0) # poll until terminal
run.raise_for_status() # raise unless it succeeded
Manage saved queries
q = sqz.create_query(
name="Daily orders",
sql="SELECT * FROM orders WHERE created_at > {{since}}",
data_source_id="ds-uuid",
folder_id="folder-uuid", # optional; visibility, tags, ... too
)
# Edit. The API optimistic-locks on `version`; omit it and the client reads the
# current version first (pass one you already hold to avoid the extra GET).
sqz.update_query(q["id"], sql="SELECT * FROM orders LIMIT 100")
sqz.update_query(q["id"], version=q["version"], name="Renamed")
sqz.delete_query(q["id"])
A stale version raises ConflictError (details["current_version"]). Query writes need the queries:write scope.
Browse the workspace
sqz.me() # user, capabilities, active org, plan
sqz.list_data_sources() # connections you can query
sqz.list_queries() # saved queries
sqz.get_query("query-uuid")
sqz.list_runs(query_id="query-uuid", limit=10)
sqz.get_run("run-uuid")
sqz.compile("SELECT {{id}}") # discover a query's variables without running it
Errors
Every API error raises a subclass of SqueezleError carrying .code, .message, .details, and .status:
| Exception | HTTP | When |
|---|---|---|
AuthenticationError |
401 | bad/expired token, org membership lapsed |
ForbiddenError |
403 | missing scope or role; details["reason"] explains |
PlanLimitError |
402 | plan entitlement or feature gate hit |
NotFoundError |
404 | no such resource, or the run's result expired |
ConflictError |
409 | saved-query version conflict |
ValidationError |
422 | bad request; details maps field -> messages |
RateLimitError |
429 | too many requests (auto-retried a few times first) |
RunFailedError |
- | the run ended in error/timeout/cancelled |
RunTimeout |
- | wait() gave up before the run finished |
from squeezle import ValidationError
try:
sqz.run_sql("ds-uuid", "SELECT bad")
except ValidationError as exc:
print(exc.code, exc.details)
Test
python -m unittest discover -s tests # stdlib only, no network
# or: pytest
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file squeezle-0.1.0.tar.gz.
File metadata
- Download URL: squeezle-0.1.0.tar.gz
- Upload date:
- Size: 17.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afaf1caeeb487c3357193fbb4f3a3d9e50f363a779be6df2e51affe37b62d9a4
|
|
| MD5 |
95c03e441562ab55a036d3faec2d0666
|
|
| BLAKE2b-256 |
c73ec74b5946a242bad10e80b9dd6f7ec04b6e0d3223f1cb3b30cfa1594173fd
|
Provenance
The following attestation bundles were made for squeezle-0.1.0.tar.gz:
Publisher:
publish.yml on koode/squeezle-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
squeezle-0.1.0.tar.gz -
Subject digest:
afaf1caeeb487c3357193fbb4f3a3d9e50f363a779be6df2e51affe37b62d9a4 - Sigstore transparency entry: 2415049175
- Sigstore integration time:
-
Permalink:
koode/squeezle-py@9cc7faf9e5cc6a5028a44fe62537a358bc5c89bb -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/koode
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9cc7faf9e5cc6a5028a44fe62537a358bc5c89bb -
Trigger Event:
release
-
Statement type:
File details
Details for the file squeezle-0.1.0-py3-none-any.whl.
File metadata
- Download URL: squeezle-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21b4109c7e697f248dd859eab19c10a990bea3d7100c6c8478a3d135e894d165
|
|
| MD5 |
f24d7a37279ba610177d7759a5a94ca0
|
|
| BLAKE2b-256 |
f55118ef35c32ffaeb6163d47d961117fa65e6d5231c204947b7b45df8d56012
|
Provenance
The following attestation bundles were made for squeezle-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on koode/squeezle-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
squeezle-0.1.0-py3-none-any.whl -
Subject digest:
21b4109c7e697f248dd859eab19c10a990bea3d7100c6c8478a3d135e894d165 - Sigstore transparency entry: 2415049184
- Sigstore integration time:
-
Permalink:
koode/squeezle-py@9cc7faf9e5cc6a5028a44fe62537a358bc5c89bb -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/koode
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9cc7faf9e5cc6a5028a44fe62537a358bc5c89bb -
Trigger Event:
release
-
Statement type: