Projects and datasets
Projects
from honeyframeapi import HoneyframeClient
client = HoneyframeClient.from_env()
client.list_projects() # projects you can see
project = client.get_project(7) # by id
project = client.get_project("sales") # by slug or name
project = client.create_project("Sales", "sales", description="Order analytics")
A Project handle sends its own id as X-Project-Id on every call it makes, so you can hold several at once. project.get_summary() fetches and caches the project record.
Project variables
Project variables are shared, typed values that SQL references as :name or ${name}.
project.set_variable("min_order_amount", 50)
project.list_variables() # {name: value} — local overrides win over standard
project.delete_variable("min_order_amount")
set_variable(name, value, *, var_type="string", scope="standard") sends the value as a string. get_preferences() / set_preferences(prefs) read and write a separate, per-user JSON blob of UI state.
Datasets
project.get_dataset(name) returns a Dataset handle without a request; the first method you call fetches what it needs.
project.list_datasets(layer="marts", search="orders") # also origin=, tags=
ds = project.get_dataset("fact_orders")
ds.get_metadata() # the full record: columns, types, layer, row_count, …
ds.get_schema() # just the column definitions
ds.head(20) # up to 100 preview rows (server cap)
ds.profile() # per-column stats: distinct, null %, min / max
Create a dataset over an existing table
ds = project.create_dataset(
"orders",
connector_id=5,
connection_config={"schema_name": "sales", "table_name": "orders"},
)
print(ds.name)
The server normalises the name (lower case; spaces and dashes become _), and the returned handle uses the stored name. create_dataset also takes dataset_type (default "sql_table"), layer (default "source"), friendly_name, description, schema_config, sync_mode (default "batch") and sync_interval.
Register raw tables without copying data
ds = project.register_source_table("staging", "orders_raw", connector_id=5)
datasets = project.register_schema("staging", connector_id=5, search="orders")
register_source_table makes any physical schema.table visible in the dataset picker and queryable at once. register_schema registers every table of a schema (optionally filtered by search) and is idempotent. import_dataset(name, ...) is the narrower variant that only finds tables the catalog already knows.
Upload local data
import pandas as pd
df = pd.DataFrame({"store_key": [1, 2], "target": [1200, 950]})
ds = project.upload_dataframe("store_targets", df, materialize=True)
ds = project.upload_file("stg_sales", "sales.csv", replace=True) # .csv or .xlsx
ds.materialize(refresh=True)
Uploads are stored as Parquet on the server. They are invisible to warehouse consumers (dbt models, dashboards, agents) until materialized: materialize=True, or Dataset.materialize() later, copies the data into the project's upload schema. refresh=True overwrites an earlier copy; timeout= (seconds, 0.1.8+) overrides the client timeout for a large copy.
Read a dataset into pandas
df = ds.get_dataframe() # every row, paged through the explore endpoint
df = ds.get_dataframe(max_rows=50_000) # cap a large pull
raw = ds.get_dataframe(as_dataframe=False) # {"columns": [...], "rows": [...]}
get_dataframe pages through the dataset 500 rows at a time (page_size=) until it has every row or max_rows. It reads through the same path as the UI's explore view, so data policies and masking apply.
Export to a file
ds.to_csv("fact_orders.csv") # no pandas needed
ds.to_parquet("fact_orders.parquet", max_rows=100_000) # needs honeyframeapi[parquet]
Exports use the masked read path above, so an export never contains more than you can browse. Both return the path written; new files are created readable by your user only.
Lineage
ds.upstream() # everything this dataset depends on (recursive)
ds.downstream() # everything that reads from it (recursive)
project.dag() # {"nodes": [...], "edges": [...]} for the whole project
Lineage reads through the model catalog, so it works on dbt and native-engine installations alike.
Settings, tags and catalog text (0.1.8+)
ds.get_settings()
ds.update_settings(description="One row per order line.",
column_descriptions={"amount": "Net amount after discounts"},
tags=["finance"])
ds.apply_tags(add=["certified"], remove=["draft"])
project.list_dataset_tags()
project.apply_dataset_tags(["fact_orders", "dim_store"], add=["finance"])
update_settings writes only the arguments you pass; passing None does not clear a field. It also accepts semantic_overrides, masking, sync_mode, sync_interval, partitioning and catalog. Writing settings and tags needs the org.admin permission.
client.ai_search_datasets(intent) and project.ai_search(intent) rank datasets against a natural-language description, across the organization or within one project.
Snapshot versions
for v in ds.versions(): # newest first
print(v["id"], v["row_count"], v["created_at"])
ds.preview_version(version_id, limit=50) # read the data as of a snapshot, masked
ds.update_version(version_id, is_pinned=True, note="month-end close")
ds.rollback_version(version_id) # destructive: restores the live table
rollback_version replaces the live table's contents with the snapshot. It works on table-backed datasets, not on external or uploaded-file datasets.
Metrics and checks
ds.add_metric("row_count", metric_type="row_count")
ds.add_metric("null_store", metric_type="null_pct", column="store_key")
ds.add_metric_check("row_count", operator=">", threshold=0, severity="error")
ds.compute_metrics() # [{"name", "value", "breaches"}] for active metrics
ds.metric_history("row_count")
ds.metrics_status()
ds.delete_metric("null_store")
metric_type is one of row_count, distinct_count, null_pct, min, max, mean or custom_sql (which needs a single-value, read-only sql_expr). A check breaches when its condition does not hold. Metrics are addressed by name; the SDK resolves the id for you.
Schema contracts and drift
ds.capture_schema_contract(enforcement="warn") # off | warn | enforce
ds.schema_drift() # {"status": "ok" | "drift" | "breaking_drift" | ..., "diff": {...}}
ds.set_schema_enforcement("enforce")
ds.schema_contract() # the current baseline, or None
Meanings
ds.meanings() # {column: detected meaning}
ds.validate_meaning("customer_email") # how many sampled values break the meaning's rule
client.list_meanings() # the organization's meaning registry
client.create_meaning, update_meaning and delete_meaning manage the organization's own meanings; creating one needs org admin, and the built-in meanings are read-only.
Delete
ds.delete() # remove the dataset record
ds.delete(managed=True) # the managed-dataset delete endpoint
Connectors
Connectors are organization-level, so they hang off the client.
client.list_connector_catalog() # connector types you can configure
client.list_connectors() # configured connectors (optional category=)
conn = client.create_connector(
"Warehouse replica", "postgresql",
config={"host": "db.internal.example", "database": "analytics"},
)
print(conn.test()) # {status, message, latency_ms, …}
conn = client.get_connector(conn.connector_id)
conn.update(description="Read replica")
conn.rotate({"password": "new-secret"}, rotation_reason="quarterly")
conn.secret_history() # metadata only, never secrets
conn.delete()
rotate tests the new configuration before saving it and aborts on a failed test unless force_save=True; rollback_secret(history_id) restores an earlier configuration after re-testing it. For connectors in per-user credential mode, each user stores their own secrets with conn.set_my_credentials({...}), and client.list_my_credentials() shows which slots you have filled. project.list_connectors() lists the connectors linked to one project.
Verified against honeyframeapi 0.1.8.