dbt and recipes
Warehouse tables are built by recipes, not by raw DDL. Which authoring path you use depends on the installation's engine:
project.engine() # "dbt" or "native"
project.uses_dbt() # True on a dbt installation
- dbt — author dbt model files through
project.dbtand build them with dbt. - native — an installation without dbt. Author SQL recipes through
Recipeand build them withproject.build_native().
engine() is cached after the first call (refresh=True re-reads it). See the native engine page for how the platform chooses.
dbt: the DbtIDE handle
project.dbt is a property that returns a DbtIDE handle over the platform's dbt API, the same one the platform's dbt editor uses. Most of these calls need org-admin permission.
Files and models
dbt = project.dbt
dbt.write_file("models/staging/stg_sales.sql",
"select * from {{ source('erp', 'sales') }}")
dbt.read_file("models/staging/stg_sales.sql") # {path, content, size_bytes, modified}
dbt.list_files("models") # the file tree under a dbt directory
dbt.delete_file("models/staging/old_model.sql")
dbt.create_model("stg_sales", schema_name="staging",
sql="select * from {{ source('erp', 'sales') }}",
description="Sales lines, typed")
dbt.list_models()
dbt.compile("models/staging/stg_sales.sql") # resolved SQL in "compiled_sql"
write_file creates or overwrites a file; the platform registers new models in the catalog. create_model scaffolds a new model with its dataset registration and schema entry and returns {name, path, schema, sql}; schema_name is the layer (staging, intermediate or marts).
Run, test, build
res = dbt.run(select="stg_sales+")
print(res["success"], res["return_code"], res["duration_seconds"])
print(res["output"])
dbt.test(select="stg_sales")
dbt.build(select="+fact_orders")
dbt.execute("seed") # run | test | compile | build | seed | snapshot | deps | ls
dbt.runs(limit=20) # recent dbt executions
These calls run the dbt command and return its result ({success, return_code, output, duration_seconds, command}) when it finishes, so set a generous client timeout for long builds.
Version control
dbt.git_status()
dbt.git_commit("add stg_sales", files=["models/staging/stg_sales.sql"], push=True)
dbt.git_push()
Building through the job pipeline
project.run_dbt(select) and Recipe.build() start the build as a pipeline job instead of a direct dbt call. They return as soon as the job is started; with wait=True they block until the run finishes and return the run row (0.1.8+: in 0.1.7, wait=True returned immediately).
run = project.run_dbt("stg_sales+", wait=True, timeout=3600)
print(run["status"]) # "success" / "failed" / …
project.get_recipe("fact_orders").build(downstream=True, wait=True)
build(downstream=True) selects model+, upstream=True selects +model. See Scenarios and jobs for the underlying trigger_and_wait.
Recipes
project.get_recipe(model_name) returns a Recipe handle for a flow recipe, addressed by its output model name.
r = project.get_recipe("int_orders_enriched")
r.get_metadata() # {id, recipe_type, inputs, steps, version, …}
r.recipe_id
r.runs(limit=5) # recent executions
r.engine_status() # selected engine, pinned or default, and which engines are selectable
Author a prepare recipe
r = project.get_recipe("stg_orders_clean")
r.save_prepare_steps(
source="orders_raw",
steps=[...], # prepare steps in the Flow Builder's step format
materialized="table",
)
save_prepare_steps stores the steps, generates the SQL and (on a dbt installation) the model file. Pass expected_version (from get_metadata()["version"]) for optimistic locking: a stale version is refused with 409.
Author any recipe type
r = project.get_recipe("orders_by_store")
r.save(recipe_type="sql_script",
steps=[],
inputs=["fact_orders"],
output_datasets=["orders_by_store"],
inline_code=(
"DROP TABLE IF EXISTS marts.orders_by_store;\n"
"CREATE TABLE marts.orders_by_store AS "
"SELECT store_key, COUNT(*) AS orders FROM marts.fact_orders GROUP BY store_key;"
))
save writes a recipe of any type (prepare, python, sql_query, sql_script, split, …) with its configuration in the Flow Builder's shape for that type. The platform validates prepare steps; for sql_script, the script goes in inline_code.
Native engine: land SQL and build (0.1.8+)
On a native-engine installation, save only compiles inline_code for prepare recipes. For the other SQL-shaped types, land the SQL with set_sql, then build:
r = project.get_recipe("orders_by_store")
r.set_sql("""
SELECT store_key, COUNT(*) AS orders
FROM {{ ref('fact_orders') }}
GROUP BY store_key
""")
project.build_native(select="orders_by_store+", dry_run=True) # resolved order only
result = project.build_native(select="orders_by_store+")
print(result["ok"], result["built"], result["total"])
set_sql resolves {{ ref(...) }} and {{ source(...) }} against the project's datasets and refuses the write with 400 if any template expression is left. A dbt installation answers 409: author the model through project.dbt instead.
build_native(select=None, *, dry_run=False, background=False, wait=False) builds the project's SQL recipes in dependency order and stops at the first failure. select uses dbt syntax (model, model+, +model, comma-separated). background=True returns {run_id, status: "running", …} at once; add wait=True to block on that run.
Change recipe engines
project.change_recipe_engines(["orders_by_store", "fact_orders"], "sql", dry_run=True)
target is sql (pin to the in-database engine), dbt, or auto (clear pins). A recipe that cannot compile to the requested engine is reported and left unchanged. Needs org admin.
Plugin recipes
Plugins your organization has enabled can contribute custom recipe types. Discover them, then create and run one:
for t in client.plugin_recipe_types():
print(t["type"], t["plugin"], t["display_name"])
rec = project.create_plugin_recipe(
"row_tools.rowcount", "count_orders", # a type from plugin_recipe_types()
config={"input_table": "marts.fact_orders"},
run=True,
)
print(rec.last_result)
rec.update_config({"input_table": "marts.fact_orders_archive"}) # merges by default
rec.run(timeout=600)
rec.runs(limit=5)
rec.delete()
create_plugin_recipe(recipe_type, name, *, config=None, inputs=None, output_datasets=None, run=False, wait=False) returns a PluginRecipe. The platform refuses creation with 403 when the owning plugin is not enabled for your organization. run() is synchronous: it returns when the plugin's handler finishes, and timeout= bounds that HTTP call. The result is also kept on rec.last_result.
To write a plugin of your own, see Plugins.
Verified against honeyframeapi 0.1.8.