SQL and DataFrames
sql() is the shortest path from a question to a DataFrame. It exists on the project and, as a shortcut, on the client:
df = project.sql("SELECT store_key, SUM(amount) AS revenue FROM marts.fact_orders GROUP BY 1")
client.using(project_id=7)
df = client.sql("SELECT COUNT(*) AS n FROM marts.dim_store")
client.sql needs a project on the client (project_id= at construction, HONEYFRAME_PROJECT_ID, or client.using(project_id=...)); without one it raises HoneyframeConfigError.
Signature
project.sql(query, *, connector_id=None, row_limit=10000, params=None)
| Argument | Meaning |
|---|---|
query | One SQL statement. |
connector_id | Run the query through this connector. |
row_limit | Maximum rows returned. Default 10,000; the platform caps it at 50,000. |
params | Named binds for :name placeholders (0.1.8+). |
What "governed" means
The query goes to the platform's POST /api/sql endpoint, which:
- accepts read-only statements only (no DDL or DML);
- runs inside the project's scope, with the project's data policies and column masking applied;
- enforces a statement timeout and the row cap.
To create or change tables, author dbt models or recipes instead; see dbt and recipes.
Named parameters
df = project.sql(
"SELECT * FROM marts.fact_orders WHERE order_date >= :start AND store_key = :store",
params={"start": "2026-01-01", "store": 12},
)
Bind values with params instead of formatting them into the SQL string. Parameters need a platform that has the /api/sql endpoint (see the fallback below).
Truncation
When the result was cut at row_limit, the platform says so and the SDK (0.1.8+):
- emits a
UserWarningnaming the limit, and - sets
df.attrs["truncated"] = True(or"truncated": Trueon the dict returned without pandas).
import warnings
with warnings.catch_warnings():
warnings.simplefilter("error") # turn a silent cut into an exception
df = project.sql("SELECT * FROM marts.fact_orders", row_limit=50_000)
For full-table extracts, page through the dataset instead: project.get_dataset("fact_orders").get_dataframe(), Dataset.to_parquet(...), or the CLI's honeyframe export. See Projects and datasets.
Without pandas
pandas is optional. On an installation without it, sql() returns a dict:
res = project.sql("SELECT store_key, store_name FROM marts.dim_store")
res["columns"] # ["store_key", "store_name"]
res["rows"] # [[1, "North"], [2, "South"], ...]
Install honeyframeapi[pandas] to get DataFrames.
Errors
| Situation | Raised |
|---|---|
No project on the client (client.sql) | HoneyframeConfigError |
The endpoint answers with an error in its response body (for example a failing statement) | HoneyframeAPIError with status_code == 422 |
| The request is refused with HTTP 400, 409 or 422 | HoneyframeValidationError |
| You lack access to the project | HoneyframeAuthError |
| Unknown connector or project | HoneyframeNotFoundError |
See Errors and retries.
Older platforms
On a platform that predates the /api/sql endpoint (the request returns 405, or the route itself is missing), the SDK falls back to running the query through a hidden per-project scratch dashboard card titled __honeyframe_sdk_scratch__. The fallback cannot bind params (it raises HoneyframeError instead), applies row_limit on the client side, and needs the project.edit permission. A 404 that the endpoint itself returns, such as an unknown connector, is raised and never sent to the fallback.
Natural-language questions
Dataset.ask(question, *, row_limit=100) sends a natural-language question about one dataset to the platform and returns its answer as a dict:
project.get_dataset("fact_orders").ask("How many orders were placed last week?")
For multi-turn conversations with an agent, see Agents and knowledge.
Verified against honeyframeapi 0.1.8.