Official Python client for the Hotdata HTTP API: workspaces, connections, instant databases, SQL queries, results, uploads, indexes, jobs, embedding providers, and database context.
Python 3.10+
pip install hotdataFor an unreleased revision:
pip install "git+https://github.com/hotdata-dev/sdk-python.git"From a local checkout (editable):
pip install -e .The API uses an API key sent as Authorization: Bearer <key>, plus an X-Workspace-Id header on requests scoped to a workspace.
import hotdata
configuration = hotdata.Configuration(
api_key="YOUR_API_KEY",
workspace_id="YOUR_WORKSPACE_ID",
)Each of these can come from the environment instead, the same variables the
CLI uses: HOTDATA_API_KEY, HOTDATA_WORKSPACE and HOTDATA_API_URL fill in
api_key, workspace_id and host when the argument is not passed. An
explicit argument always wins.
host defaults to https://api.hotdata.dev. Override it (or set
HOTDATA_API_URL) if you target another environment.
Almost every call is workspace-scoped. If no workspace id is configured, the
SDK raises hotdata.MissingWorkspaceError before sending the request rather
than letting the server answer with an unexplained 404.
import hotdata
from hotdata.rest import ApiException
configuration = hotdata.Configuration(
api_key="YOUR_API_KEY",
workspace_id="YOUR_WORKSPACE_ID",
)
with hotdata.ApiClient(configuration) as api_client:
workspaces = hotdata.WorkspacesApi(api_client)
try:
response = workspaces.list_workspaces()
except ApiException as e:
print(f"API error: {e.status} {e.reason}\n{e.body}")Each Api class groups endpoints by resource. Construct the client, then call the typed methods you need.
Loads are serialised per table on the server. hotdata.DatabasesApi and
hotdata.ConnectionsApi retry a load that hits a table another load still
holds (HTTP 409, error code RESOURCE_LOCKED), honouring the server's
Retry-After, for up to 60s by default:
from hotdata import DatabasesApi, LoadRetryPolicy, TableLockedError
databases = DatabasesApi(api_client, load_retry=LoadRetryPolicy(deadline_s=120))
try:
databases.load_database_table(db_id, "public", "events", request)
except TableLockedError as e:
print(f"gave up after {e.attempts} attempts: {e.reason}")Other 409s (CONFLICT, IDEMPOTENCY_KEY_CONFLICT) are raised immediately.
Query results can be fetched as an Apache Arrow IPC stream instead of JSON, which is faster and far more memory-efficient for large result sets. Install the optional extra:
pip install 'hotdata[arrow]'Use hotdata.arrow.ResultsApi (a drop-in subclass of ResultsApi that adds Arrow methods):
from hotdata import ApiClient, Configuration
from hotdata.arrow import ResultsApi
with ApiClient(Configuration(api_key="...", workspace_id="...")) as client:
results = ResultsApi(client)
# Results are scoped to a database via the required `X-Database-Id` header,
# so pass the id of the database the query ran in.
database_id = "your_database_id"
# Buffered: returns a pyarrow.Table.
table = results.get_result_arrow(result_id, database_id)
# Streaming: yields a pyarrow.RecordBatchStreamReader without
# materializing the full table in memory.
with results.stream_result_arrow(result_id, database_id) as reader:
for batch in reader:
...Both methods accept offset and limit for pagination. They raise hotdata.arrow.ResultNotReadyError if the result is still pending or processing — poll results.get_result(result_id, database_id) until status == "ready" first.
hotdata.uploads.UploadsApi (also the default hotdata.UploadsApi) adds
upload_file, which uploads a local file directly to object storage and
finalizes it in one call. It opens an upload session, PUTs the bytes straight
to storage — a single PUT for a small file, concurrent part PUTs for a large
one — then finalizes. The bytes never round-trip through the API.
from hotdata import ApiClient, Configuration, UploadsApi
with ApiClient(Configuration(api_key="...", workspace_id="...")) as client:
uploads = UploadsApi(client)
finalized = uploads.upload_file(
"data.parquet",
content_type="application/parquet",
progress=lambda done, total: print(f"{done}/{total} bytes"),
)
# Pass finalized.upload_id to the managed-table load endpoint.
print(finalized.upload_id)upload_file accepts a path, raw bytes, or a seekable binary file object
(size is inferred for all three; a file object is read from its current
position to the end). The SDK picks single vs. multipart from the size,
auto-scales the part size, and bounds part concurrency to a peak-memory budget
(override with part_size / max_concurrency / part_retry). A file larger than
8 MiB uploads via a streaming session — the SDK mints each part's URL just
before its PUT, so a presigned URL can't expire mid-transfer on a slow upload.
Storage PUTs go through a dedicated, header-isolated connection pool (auth and
workspace headers never reach object storage, which would otherwise reject the
upload) with a 30s connect timeout so a dead endpoint fails fast. Finalize is sent
with retries disabled so the exactly-once call is never accidentally replayed.
Every failure is a subclass of hotdata.uploads.UploadError (also importable as
from hotdata import UploadError), so a single except UploadError catches the
whole flow: SessionCreateError (opening the session — check .status for a
501 PRESIGN_UNSUPPORTED), StorageError (storage returned a non-2xx; .exhausted
is True if it outlived every retry round), StorageTransportError (the PUT
failed before any response), MissingETagError, MintPartError (minting a part
URL), FinalizeError, MalformedSessionError, SizeLimitError, and
UploadCancelledError. The phase errors that wrap a control-plane call chain the
underlying hotdata.exceptions.ApiException as __cause__ (and expose it as
.api_exception / .status). A local file read error surfaces as OSError.
The progress callback receives a cumulative (bytes_done, total) — for a
tqdm bar (whose update(n) wants a delta, and which isn't thread-safe under
multipart) use the ready-made adapter:
from hotdata import tqdm_progress
from tqdm import tqdm
with tqdm(total=size, unit="B", unit_scale=True) as bar:
uploads.upload_file("data.parquet", progress=tqdm_progress(bar))Pass a threading.Event as cancel_event to abort an in-flight upload; tune the
control-plane calls with request_timeout (storage-PUT timeouts are automatic and
size-scaled).
Generated Markdown for every operation and model is in docs/:
- Resource APIs:
docs/*Api.md(for exampleQueryApi.md) - Request and response models:
docs/<ModelName>.md
Questions and issues: github.com/hotdata-dev/sdk-python.