vibedasher — Python SDK
Official Python SDK for the Vibedasher headless data
engine. The method you'll use most is client.query(...) — it runs SQL across your
managed datasets and returns typed rows, so you can render a dashboard
natively from your own backend.
Install
pip install vibedasher
Headless / eject — start here
Vibedasher hosts the data engine (ETL, datasets, query); you own the frontend/app. You author a dashboard in Vibedasher, eject its code, and wire it to the engine with a single call:
from vibedasher import Vibedasher
# Server-side (API key held on your server, never shipped to a browser).
client = Vibedasher(api_key="sk_...", region="eu-central-1")
result = client.query(
sql="SELECT region, AVG(lead_price_usd) AS avg_price FROM sales GROUP BY region",
params={"region": "Caribe"}, # bound/escaped server-side, never interpolated
)
for col in result.columns:
print(col.name, col.type) # e.g. region string / avg_price number
for row in result.rows:
print(row["region"], row["avg_price"])
dataset_idsis optional (plural). Omit it and the server infers the dataset set from the aliases yoursqlreferences. Pass it to pin an exact set explicitly:client.query(sql=..., dataset_ids=[12, 47], params=...)— each id resolves server-side to that dataset's alias under RLS, and a dataset not in the set is rejected (deny-don't-drop). A mistyped/unknown alias raises400 query_dataset_unresolved.sqlis inline and alias-only — it references dataset aliases, never a physical table.- Results are typed columnar —
result.columnsareQueryColumn(name, type)withtype∈string | number | boolean | date | timestamp | json, andresult.rowsare dicts keyed by column name. - Transport is hidden — inline vs presigned object storage, MessagePack
decoding, and transient retries all happen inside
query(). One call in, typed rows out.
query()is the one hand-written method (_query.py); everything else is generated (see below).
Where the query runs: type="wasm"
By default the server executes your SQL and returns rows. Pass type="wasm" to
get a plan instead — the injected SQL plus one presigned Parquet per dataset —
and execute it yourself (in DuckDB-WASM in a browser, or plain DuckDB locally):
result = client.query(sql="SELECT region, COUNT(*) FROM sales GROUP BY region", type="wasm")
if result.mode == "wasm":
for src in result.plan.sources:
print(src.alias, src.url, src.bytes) # register each under `alias`
print(result.plan.sql) # then run this
else:
print(result.rows, result.fallback_reason) # ran server-side, and why
Authorization does not move. The server still authorizes, validates alias-only SQL, and injects row filters — in that order, before the capability check. Asking for wasm skips no gate; you get a plan only for data you were already allowed to read.
Asking for wasm does not guarantee getting it. If any participating dataset
isn't wasm-capable — no current extract, an extract over the size cap, or a
mandatory row-level filter — the query runs server-side and the response says so
via mode: "backend" and a fallback_reason. It is never a silent downgrade, so
always branch on result.mode, never on what you requested.
VibedasherEmbedClient (scoped-token) has no wasm lane and raises ValueError
rather than quietly serving rows.
Embed vs eject
- Embed (iframe, zero-code): paste a snippet; we host and render. Nothing to build.
- Eject (this SDK, own-your-code): pull the dashboard's code into your stack and
feed it data via
client.query(...). Your app owns the rendering; only the data crosses the wire. See a runnable host app inexamples/nextjs-embed(TS), the same contract as here.
Client-side (no backend) auth
For a pure-frontend app, mint a short-TTL scoped token and use the same method via the token-pinned client:
from vibedasher import VibedasherEmbedClient
client = VibedasherEmbedClient(token=scoped_token, region="eu-central-1")
result = client.query(sql=sql, params=params)
# The token pins the viz + datasets — dataset_ids are accepted for symmetry but the
# server enforces the token's bound set.
Concurrency: a 503 is a limit, not an outage
The query engine has a finite, shared concurrency ceiling. A 503 Service Unavailable from query() means the request was throttled, not that the service
is down — the same SQL run serially succeeds. query() retries throttled
requests (429/502/503/504) with jittered backoff and honours Retry-After.
httpx.Client is synchronous, so one client is already one query at a time and
there is no in-flight gate here — that lives in the TypeScript SDK, where a
browser dashboard fans out on mount. If you thread this client yourself, keep the
width modest.
Errors raise QueryHttpError (a httpx.HTTPStatusError subclass, so existing
except clauses keep working) carrying status_code, the API's own message, and
request_id — quote that in a bug report.
Everything else: resource methods (generated)
Datasets, uploads, viz metadata, API keys, etc. are resource-grouped on the same
client, generated from the OpenAPI spec with flat, fully typed signatures: path
params positional (in path order), then the request body, then the query params as
keyword-only kwargs. No **kwargs, no bare Any; a non-2xx raises
VibedasherApiError (status_code, detail, request_id).
from vibedasher import Vibedasher, VibedasherApiError
from vibedasher.models import DatasetUpdate
client = Vibedasher(api_key="sk_...", region="eu-central-1")
page = client.datasets.list(limit=50, folder_id=7) # -> DatasetListResponse
ds = client.datasets.update(42, DatasetUpdate(name="renamed")) # -> DatasetResponse
client.viz.delete(7) # 204 -> None
try:
client.viz.get(999999)
except VibedasherApiError as e:
print(e.status_code, e.detail, e.request_id)
The low-level generated modules remain available for anything the facade does not
expose (vibedasher.api.<tag>.<operation_id> with .sync(), .sync_detailed(),
.asyncio(), .asyncio_detailed()), driven by create_client(api_key, region=...).
Auth is an API key sent as X-Api-Key (mint one via POST /v1/api-keys).
Paging a list endpoint: iter_all()
Every cursor-paginated list endpoint returns one page plus a nextKey:
{ "datasets": [ ... ], "nextKey": 812 }
nextKey: null means that was the last page — it is the only end-of-list
signal. Rather than hand-rolling that loop, iterate:
for dataset in client.datasets.iter_all():
print(dataset.name) # pages transparently, rows streamed
for dataset in client.datasets.iter_all(page_size=50, max_pages=10, folder_id=7):
... # cap the work; extra kwargs are forwarded
for page in client.datasets.iter_pages():
print(page.next_key) # page-at-a-time, e.g. to checkpoint
Nested collections take the method name:
for upload in client.datasets.iter_all(method="list_uploads", dataset_id=4):
...
Notes:
- Bounded memory — rows are yielded as each page arrives, and
breakstops the requests immediately. Nothing is prefetched or accumulated. max_pagescaps the work: iterating an unbounded table to exhaustion is a footgun, so the ceiling is one keyword away.- A repeated cursor raises
CursorNotAdvancingErrorinstead of looping forever. That is a server bug in the endpoint'snextKey; the SDK surfaces it rather than absorbing it as a short read. - For a paginated endpoint that is not on the resource, the standalone form
works on any list callable:
from vibedasher import iter_all.
Layout
vibedasher/_query.py— the hand-writtenquery()(transport, decode, retry, typed rows).vibedasher/_pagination.py— the hand-writteniter_all()/iter_pages()cursor loop.vibedasher/_facade.py— GENERATED resource classes with the flat signatures +VibedasherApiError.tests/test_facade_surface.py— the blocking lint on_facade.py(no**kwargs, no bareAny, snake_case only).vibedasher/api/<tag>/<operation>.py— one module per API operation.vibedasher/models/— request/response models (attrs classes).vibedasher/factory.py—create_client(api_key, region=..., base_url=...).
Generated code — do not edit
vibedasher/by hand. Regenerate withmake pyfrompackages/sdk/. Source of truth:../openapi.json.query()and the pagination iterators are the deliberate exceptions — hand-written inpackages/sdk/py/{_query,_pagination}.pyand re-copied after each generation.
Release files for vibedasher 4.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vibedasher-4.3.1.tar.gz | 438.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vibedasher-4.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.4 MB
Release files / vibedasher-4.3.1.tar.gz
| Download URL | vibedasher-4.3.1.tar.gz |
|---|---|
| Size | 438.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1cc496b648a7714d7f4f51cddd8f2ee1681bcf2f4abd0a93b28e81b0e9560736
|
|
BLAKE2b-256 checksum How to use checksums |
19d4dfdb952d815a5fec0209ce2234ebd373da191e23497d0385ba9f3651a5db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / vibedasher-4.3.1-py3-none-any.whl
| Download URL | vibedasher-4.3.1-py3-none-any.whl |
|---|---|
| Size | 1.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2c6e4b48862a57f960b3400400df18b1cb5f25e13063003d890a9b6a5e14a9d5
|
|
BLAKE2b-256 checksum How to use checksums |
ceeee2ddd0c0354d1a222b7407b6cfef983e07acf0bcd54616813b463850d201
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|