Build a table with dbt¶
A table is built by a Python function unless its executor is dbt. With dbt, the table file still holds the contract, the SQL is a dbt model in the data product folder, and dpb generate writes the dbt properties, sources and tests from the files.
This guide builds the table turnaround of the data product agg_order_turnaround with dbt. The data product has an input port on the output port sa_jaffle_orders.
1. Choose dbt¶
Set the executor for the whole data product, in its dataproduct.yaml:
executor: dbt
To build only one table with dbt, set it in that table’s file instead:
build:
executor: dbt
When executor: dbt is set in datapatchbay.yaml, dpb new scaffolds a stub model per table instead of a Python function.
2. Write the contract¶
The table file has the same contract as any other table, and no entrypoint, params or needs. dbt takes the order of the models from their ref() calls.
# dataproducts/agg_order_turnaround/outputs/agg_order_turnaround/turnaround.yaml
schema_version: 1
name: turnaround
description: Hours between the order and its completion, one row per completed order
columns:
- name: order_id
type: text
rules: [not_null, unique]
- name: turnaround_hours
type: numeric
rules: [not_null]
rules:
- rule: row_count
min: 1
3. Write the model¶
The model is models/<data product>__<table>.sql in the data product folder. The name comes from the dbt_model naming template.
-- dataproducts/agg_order_turnaround/models/agg_order_turnaround__turnaround.sql
select
order_id,
extract(epoch from completed_at - ordered_at) / 3600 as turnaround_hours
from {{ source('sa_jaffle_orders', 'orders') }}
where completed_at is not null
A model reads another data product’s output port with source('<port>', '<table>'). dpb generate writes that dbt source for each input port that reads an output port, so the data product must declare the input port first. Do not use ref() on another data product’s models: that reads its private tables, and dpb validate fails on it.
4. Add a dbt project¶
dpb expects a dbt_project.yml at the repo root. If the repo has none, create one:
name: jaffle_shop
version: "1.0"
profile: jaffle_shop
For models to land in the private schema of their data product, the project must also override the generate_schema_name macro. See the table file reference for the macro.
5. Generate the dbt files¶
$ dpb generate
Besides the DAG files, this writes inside the repo:
models/agg_order_turnaround__turnaround.yml: the model’s description, columns and types, an enforced model contract, and the column rules as dbt data tests.models/_group.yml: the dbt group of the data product, with its owner.models/_sources.yml: a dbt source for each output port that the data product reads, with the tables and columns of that port.tests/agg_order_turnaround__turnaround__row_count.sql: one singular dbt data test per table rule. It selects the rows that break the rule.The
model-pathsandtest-pathskeys ofdbt_project.yml, with themodels/andtests/folders of every data product that has a table built by dbt.
Commit all of them. dbt reads them on every run. Never edit them by hand: change the table file and run dpb generate again.
6. Parse and validate¶
dpb validate checks what the dbt models read. It does not run dbt: it reads the dbt manifest that dbt parse writes, target/manifest.json by default. So run dbt parse first, and again after every change to a model:
$ dbt parse
$ dpb validate
ok: 2 data product(s) valid
dbt parse needs the dbt adapter and a profile, but it does not connect to the warehouse. If it writes the manifest somewhere else, set dbt.manifest in datapatchbay.yaml. Without a manifest, dpb validate fails and says to run dbt parse.
The order matters in a new repo: dpb generate first, because dbt cannot parse the project without the generated sources, then dbt parse, then dpb validate.
From now on dpb validate fails when a committed dbt file does not match the table file:
dataproducts/agg_order_turnaround/models/agg_order_turnaround__turnaround.yml: dbt properties file does not match its table file; run `dpb generate` to refresh it
When you remove a table rule or a table, run dpb generate again. It removes the generated files that nothing produces any more and lists them as removed. It never touches a file that you wrote by hand.
What runs¶
All dbt tables of a data product share one task in its DAG, build_dbt. It runs dbt build on the data product’s models/ folder on the Airflow worker, after the Python tables and before the views and grants. dbt runs the data tests itself, so these tables get no audit task. The worker needs the dbt command and a profile. See dbt on the worker.
Limits¶
The runtime of a dbt table must be
worker.dockerandkubernetesfail indpb validate.A Python table cannot list a dbt table in
needs, because the Python tables are built first.A
.pyfile in amodels/folder fails, because dbt would read it as a dbt Python model.A model that a table file builds must not be declared in another YAML file of a
models/folder.