Skip to main content

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)
ArgumentMeaning
queryOne SQL statement.
connector_idRun the query through this connector.
row_limitMaximum rows returned. Default 10,000; the platform caps it at 50,000.
paramsNamed 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 UserWarning naming the limit, and
  • sets df.attrs["truncated"] = True (or "truncated": True on 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​

SituationRaised
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 422HoneyframeValidationError
You lack access to the projectHoneyframeAuthError
Unknown connector or projectHoneyframeNotFoundError

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.