dbt reference¶
Everything about tables built by dbt in one place: the table file, the files that dpb generate writes for dbt, the task that runs dbt, and what dpb new --from-dbt imports. For the steps, see Build a table with dbt and Import an existing dbt project.
A table built by dbt¶
A table is built by dbt when its executor resolves to dbt: executor: dbt on its data product or higher, or build: {executor: dbt} on the table itself.
It has the same contract as any other table, and needs no
buildblock.entrypoint,paramsandneedsfail on it, because dbt takes the order from the model’sref()calls.Its SQL is the dbt model
models/<dbt_model>.sqlin the data product’s folder. The name comes from thedbt_modelnaming template inconventions.names, by default{dataproduct}__{table}. A missing model file fails.A
.pyfile anywhere in amodels/folder fails, because dbt would read it as a dbt Python model.A model reads another data product’s output port with
source('<port>', '<table>'). Aref()into another data product’s models is a read of a private table and fails.
dpb expects one dbt project for the repo, with its dbt_project.yml at the repo root. A repo without a table built by dbt needs no dbt_project.yml.
What dpb validate checks lists every check on these tables, on the committed dbt files and on what the models read.
Generated dbt files¶
For tables built by dbt, dpb generate writes dbt files into the data product folders, not into the output folder. They are committed, because dbt reads them on every run (ADR-0013). Nobody writes them by hand. Each starts with a line saying it is generated from the table file.
File |
For |
Holds |
|---|---|---|
|
Each table built by dbt |
The properties of the model |
|
Each data product with such a table |
The dbt group, with the data product’s owner |
|
Each input port that reads another data product’s output port |
A dbt source named after the port |
|
Each table rule |
A singular dbt data test |
|
The repo |
The |
Properties¶
The properties file of a model holds:
the model’s description, and each column with
data_typeand descriptionan enforced model contract
the
alias(the table name), theschema(the data product’s private schema) and thegroup(the data product)each column rule as a dbt data test:
not_null,uniqueandaccepted_valuespii: truein themetaof a PII column
Severity error is written as error and warning as warn. A rule with severity none is left out.
Singular tests¶
Each table rule becomes a singular dbt data test, with no dbt package needed (ADR-0019). The SQL selects the rows that break the rule, so the test passes when it returns none:
Rule |
The test selects |
|---|---|
|
Rows with a null in a key column, or a key that occurs more than once |
|
One row when the row count is outside |
|
The rule’s SELECT as written, which names the model with |
Further rules of the same name in one table file get a suffix _2, _3. Severity maps as for column rules, and a rule with severity none gets no file.
Sources¶
For each input port that reads another data product’s output port, models/_sources.yml holds a dbt source named after the port. It points at the output port’s schema and lists the producer’s tables and columns from its table files (ADR-0018). Only the contract is copied: table names, columns and their descriptions, not quality rules or freshness.
When several data products read one output port, the source is written once, in the first of them built by dbt in name order.
dbt_project.yml¶
Once any table is built by dbt, dpb generate owns the model-paths and test-paths keys of the dbt_project.yml at the repo root, and only those. They are replaced by the models/ and tests/ folders of the data products with such a table, so an entry added by hand is lost. The file is committed, so --output does not move it. They are written after everything else is generated.
With a table built by dbt, a missing
dbt_project.ymlfails the run (exit 2) with a message saying it is expected at the repo root, and nothing is written.When no table is built by dbt any more,
dpb generateonly removes the entries that point at a data product’smodels/ortests/folder, and a key if none is left. The file is then not needed.
Files that nothing produces any more¶
A properties, sources or test file that carries the generated header but that nothing produces any more is deleted and listed as removed. A file without the header is never touched.
The schema name macro¶
The generated properties put each model in its data product’s private schema with config: {schema: <private port name>}. By default dbt does not use that name as written: it builds <target schema>_<custom schema>. For models to land in the private schema as written, the dbt project must override the generate_schema_name macro, for example in macros/generate_schema_name.sql:
{% macro generate_schema_name(custom_schema_name, node) -%}
{{ custom_schema_name | trim if custom_schema_name is not none else target.schema }}
{%- endmacro %}
dpb generate does not write or check this macro, because it never reads the macros of the dbt project. Without it, tables are created in a schema such as <target schema>_<private port name>, and the views and grants, which name the private schema, do not find them.
dbt on the worker¶
A data product with tables built by dbt gets one task, build_dbt, which runs dbt build --select path:<its models folder> on the worker.
It runs after the data product’s Python tables, so the dbt models can read their output, and before the views and grants.
dbt builds the models and runs their data tests in dependency order. A broken rule with severity
errorfails the task, anddpbgenerates no audit task for these tables.dpbuses the dbt profile the team already has and manages no connection (ADR-0001).dbt is run as the
dbtcommand of the worker’s environment.dpbinstalls nothing.
All tables built by dbt in a data product share this one task. So they must resolve to the same runtime, and it must be worker. A Python table cannot list a dbt table in needs, because the Python tables are built first.
The dbt settings¶
Three settings, in datapatchbay.yaml only:
dbt:
project_dir: . # the dbt project on the worker
profiles_dir: ~/.dbt # the folder with profiles.yml on the worker
manifest: target/manifest.json # the dbt manifest made by `dbt parse`
project_dirWhere the dbt project is on the worker. A relative path is relative to the repo root as deployed with the generated files. If the dbt project sits elsewhere on the worker with the layout of the repo, set an absolute path. A relative
project_dirother than.must contain the data product folders.profiles_dirThe folder with
profiles.ymlon the worker.~is the home of the worker’s user, and a relative path is relative to the dbt project. Authenticate through the profile, for example with environment variables on the worker.manifestRead on the machine that runs
dpb validate, not on the worker. It is relative to the repo root. Set it ifdbt parsewrites elsewhere, for example with--target-path.
The defaults assume that the repo is deployed to the worker with the generated files, that dbt_project.yml is at the repo root, and that dbt and the profile are installed for the worker’s user.
What dpb new --from-dbt imports¶
dpb new --from-dbt <path> builds a data product from the dbt project below <path>, which is read recursively. It asks for the name, kind, domain and owners as usual, and --no-input still lists what is missing. The data product gets executor: dbt, whatever the repo’s executor.
For each model that has a .sql file and properties with at least one column, it writes two files.
- The model,
models/<dbt_model>.sql Named by the
dbt_modelnaming template. The SQL is copied, not moved, so the old project keeps working until you switch over, anddpbnever deletes your files.ref('old_name')calls to other imported models are renamed to the new model names.- The table file,
outputs/<port>/<model>.yaml With the model’s description and its columns:
data_typeastype,description, andpiifrommeta: {pii: true}. A column withoutdata_typeis written astextand reported, because dbt would check the contract against it.
Quality rules come from the data tests of each column: not_null, unique and accepted_values, with severity: warn as warning. This is the reverse of what dpb generate writes, so dpb generate then reproduces the same tests.
Both
data_tests:and the oldertests:are read, with arguments directly under the test or underarguments:, andseveritydirectly or underconfig:.A rule gets an explicit
severityonly when it differs from the default severity in the settings.primary_key,row_countandsqlare not imported. They are generated as singular tests, which no property ties to a model, so write them by hand from your singular tests.
What is not imported¶
What is not imported is reported on stderr, never dropped silently, and is also in the --json lists:
Report |
Covers |
|---|---|
|
Data tests with no matching rule ( |
|
Models without properties or columns, properties without a |
|
A column without |
|
A |
A model name that is not snake_case, two models with the same name, or a dbt_model template that gives two models one name fails the command and writes nothing.
Your hand-written properties are never touched. The ones below the new data product’s models/ folder are a duplicate of the generated ones: dpb validate points them out and tells you to remove them. Those elsewhere in the old project stay as they are.