Объекты в context: ref / source / var / target / this / model / adapter / graph
ProviderContext предоставляет десяток объектов и функций. В этом уроке мы пройдём каждый: что он делает, что возвращает, какие методы, edge cases. После этого вы будете точно понимать, что доступно в Jinja-коде моделей.
ref(): соединяем модели в граф (dbt I) generate_schema_name: per-developer и clean prod schemas (dbt II)
ref()
Самая важная функция в dbt. Регистрирует зависимость и возвращает Relation для использования в SQL.
{{ ref('foo') }}
{{ ref('foo', version=2) }}
{{ ref('other_project', 'foo') }} -- cross-project ref
{{ ref('foo', version=2, package='my_pkg') }}
Сигнатура:
def ref(*args, version=None, package=None) -> Relation:
# args: 1 (name) или 2 (project, name)
На parse: регистрирует RefArgs в model.refs. Возвращает placeholder Relation (правильное имя, но без full metadata).
На execute: возвращает full Relation. Используется в SQL rendering — {{ ref('foo') }} становится "my_db"."marts"."foo".
Return type: Relation (dbt-core class).
Relation API
relation = ref('foo')
# Strings — fully-qualified
relation.identifier # 'foo'
relation.schema # 'marts'
relation.database # 'my_db'
# Render — quoted identifier
str(relation) # '"my_db"."marts"."foo"'
relation.render() # same
# Manipulate
new_rel = relation.incorporate(path={'identifier': 'foo_v2'})
relation.include(database=False, schema=True, identifier=True)
Usage paterns:
-- Basic
SELECT * FROM {{ ref('upstream') }}
-- Custom-named CTE
WITH source AS (
SELECT * FROM {{ ref('upstream') }}
)
-- Get just the identifier (без schema)
{{ ref('upstream').identifier }}
Edge cases
-
refto ephemeral model — генерирует CTE, не table reference:`{{ ref('ephemeral_model') }}` -- compiled: __dbt__cte__ephemeral_model -
refto disabled model — fail с “ref to disabled model”. -
refto non-existent model — fail с “model not found”. -
Versioned ref —
ref('foo', version=2)lookupsmodel.my_project.foo.v2. Если version=latest_version -> автоматически.
source()
Аналог ref для sources. Регистрирует source dependency.
{{ source('schema_name', 'table_name') }}
Сигнатура:
def source(source_name: str, table_name: str) -> Relation:
Return: Relation, как ref.
SELECT * FROM {{ source('raw', 'users') }}
-- compiled: SELECT * FROM "my_db"."raw_schema"."users"
Source конфигурируется в _sources.yml:
sources:
- name: raw
schema: raw_schema # actual warehouse schema
tables:
- name: users
identifier: users_raw # actual table name (если отличается от name)
source('raw', 'users') -> Relation для raw_schema.users_raw.
Edge cases:
- Freshness config —
sourceимеетfreshnessconfig для проверки stale data. - Quoting — adapter-specific quoting (Snowflake case-sensitive).
- Cross-database source — поддерживается через
databaseв source config.
var()
Доступ к variables. Variables приходят из dbt_project.yml, profiles.yml, CLI --vars.
{{ var('my_var') }}
{{ var('my_var', 'default_value') }} -- с default
Сигнатура:
def var(name: str, default: Any = None) -> Any:
Precedence (override порядок):
- CLI
--vars 'my_var: value'— highest - profiles.yml
vars:для target - dbt_project.yml
vars:для project (или global) - Default из вызова
var('name', default)
Usage:
{{ config(materialized=var('default_materialization', 'view')) }}
WHERE date >= '{{ var('start_date', '2020-01-01') }}'
{% if var('enable_feature_x', false) %}
-- feature-specific SQL
{% endif %}
Senior gotcha: var() returns string by default. Для boolean — нужен cast:
{% if var('enable_feature') %}
-- WRONG — 'true' и 'false' оба truthy strings
{% endif %}
{% if var('enable_feature') | as_bool %}
-- CORRECT
{% endif %}
env_var()
Доступ к environment variables.
{{ env_var('MY_VAR') }}
{{ env_var('MY_VAR', 'default') }}
Сигнатура:
def env_var(name: str, default: Optional[str] = None) -> str:
Returns: Always string. Cast для int/bool:
{{ env_var('THREADS', '4') | int }}
{{ env_var('ENABLE_X', 'false') | as_bool }}
Senior gotcha 1: env_var('FOO') без default — fails при отсутствии var. С default — silent fallback.
Senior gotcha 2: env_var tracked для partial parse cache invalidation (модуль 02). Changing env vars инвалидирует.
Senior gotcha 3: secrets. Никогда не log env_var output если он содержит password.
target
Объект с info про active target (из profiles.yml).
{{ target.name }} -- 'dev' / 'prod' / 'ci'
{{ target.type }} -- 'duckdb' / 'snowflake' / 'postgres'
{{ target.database }} -- 'analytics'
{{ target.schema }} -- 'main' (or custom)
{{ target.threads }} -- 4
{{ target.user }} -- 'alice'
Adapter-specific:
-- Snowflake
{{ target.warehouse }}
{{ target.role }}
{{ target.account }}
-- BigQuery
{{ target.project }}
{{ target.dataset }}
-- Postgres
{{ target.host }}
{{ target.port }}
Usage:
{{ config(
materialized="{{ 'table' if target.name == 'prod' else 'view' }}"
) }}
{% if target.type == 'snowflake' %}
-- Snowflake-specific SQL
{% endif %}
Edge case: target available на parse phase (resolved from profiles.yml at config load time).
this
Relation object текущей модели. Available в model SQL files (через ProviderContext).
{{ this }} -- '"my_db"."schema"."my_model"'
{{ this.identifier }} -- 'my_model'
{{ this.schema }} -- 'schema'
{{ this.database }} -- 'my_db'
Usage:
-- Incremental model
{{ config(materialized='incremental') }}
SELECT * FROM {{ ref('upstream') }}
{% if is_incremental() %}
WHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }})
{% endif %}
this — это сама текущая модель. Используется для:
- Incremental —
WHERE > (SELECT MAX FROM {{ this }}). - Self-references — заменя на
{{ this }}в snapshots. - Logging —
{{ log('Building ' ~ this, info=True) }}.
Edge cases:
thisavailable только в model body (ProviderContext). В macros вызываемых из model — тоже доступен.- В singular tests
thisundefined — нет «текущей модели». - В hooks
thisundefined.
model
Dict с метаданными текущей модели.
{{ model.name }} -- 'my_model'
{{ model.unique_id }} -- 'model.my_project.my_model'
{{ model.tags }} -- ['nightly', 'critical']
{{ model.config }} -- {materialized: 'table', ...}
{{ model.depends_on }} -- {nodes: [...], macros: [...]}
Usage: introspection для conditional logic:
{% if 'critical' in model.tags %}
-- extra validation
{% endif %}
{% if model.config.materialized == 'incremental' %}
-- incremental-specific logic
{% endif %}
Edge case: model доступен только в ProviderContext (in model body). В hooks limited.
adapter
Главный объект для warehouse interaction. На parse — placeholder, на execute — real.
{{ adapter.get_columns_in_relation(ref('upstream')) }}
{{ adapter.execute(sql, fetch=True) }}
{{ adapter.dispatch('my_macro')(arg) }}
{{ adapter.commit() }}
{{ adapter.cache_renamed(...) }}
Methods (selected):
| Method | На execute |
|---|---|
get_columns_in_relation(rel) | Returns List[Column] |
get_relation(database, schema, identifier) | Returns Relation or None |
list_relations_without_caching(schema) | List[Relation] |
execute(sql, auto_begin=True, fetch=False) | Returns AdapterResponse |
add_query(sql, ...) | Executes |
quote(value) | Returns quoted identifier |
cache_added(relation) | Adds to cache |
cache_dropped(relation) | Removes from cache |
cache_renamed(from_rel, to_rel) | Cache update |
commit() | Commits transaction |
dispatch(macro_name, macro_namespace=None) | Adapter-specific macro resolution |
Adapter-specific methods добавляются adapter packages (dbt-snowflake adds query_warehouse, etc.).
statement(‘main’)
Wrapper над adapter.execute с named result tracking.
{% call statement('main', fetch_result=True) %}
SELECT COUNT(*) FROM {{ ref('foo') }}
{% endcall %}
{% set count = load_result('main')['data'][0][0] %}
statement('main') — это specifically для materialization. Materialization runs a “main” statement which is the actual model SQL (CREATE TABLE …). Other statements have other names.
Parameters:
name— string. Может быть'main','pre_hook','post_hook', custom.fetch_result— boolean. Если True, results доступны черезload_result(name).auto_begin— boolean. Implicit transaction start.
Usage:
{% materialization my_table, adapter='duckdb' %}
{% call statement('main') %}
CREATE TABLE {{ this }} AS ({{ sql }})
{% endcall %}
{% call statement('row_count', fetch_result=True) %}
SELECT COUNT(*) FROM {{ this }}
{% endcall %}
{% set rows = load_result('row_count')['data'][0][0] %}
{{ log('Created ' ~ this ~ ' with ' ~ rows ~ ' rows', info=True) }}
{% endmaterialization %}
run_query()
Convenience wrapper. Execute arbitrary SQL и получить результат сразу.
{% if execute %}
{% set result = run_query("SELECT MAX(date) FROM " ~ ref('foo')) %}
{% set max_date = result.columns[0].values()[0] %}
{% endif %}
Сигнатура:
def run_query(sql: str) -> Optional[agate.Table]:
Returns: agate.Table (third-party library for tabular data) или None на parse.
Agate Table API:
result.columns # List[Column]
result.columns[0].name # 'max_date'
result.columns[0].values() # tuple of values
result.rows # List[Row]
result.rows[0]['max_date'] # value
Where to use: dynamic logic в macros (как в pre-hooks для backfill date computation).
Senior gotcha: всегда wrap в {% if execute %}.
load_result()
Get result of previously-named statement.
{% call statement('check_count', fetch_result=True) %}
SELECT COUNT(*) FROM {{ this }}
{% endcall %}
{% set count_result = load_result('check_count') %}
-- count_result is dict: {response, data, agate_table, status}
{% set count = count_result['data'][0][0] %}
load_result сохраняет state across multiple statement calls в одном macro/materialization invocation.
graph
Доступ к compiled Manifest (graph of all nodes). Available на runtime в hooks.
{% if execute %}
{% for node in graph.nodes.values() %}
{% if node.resource_type == 'model' and 'critical' in node.tags %}
{{ log('Critical model: ' ~ node.name, info=True) }}
{% endif %}
{% endfor %}
{% endif %}
Available fields:
graph.nodes— dict unique_id -> nodegraph.sources— dict unique_id -> sourcegraph.macros— dict unique_id -> macrograph.exposures— dict unique_id -> exposure- … плюс другие top-level keys Manifest
Usage: writing observability hooks, custom selectors, governance checks в on-run-end.
modules
Whitelist Python modules доступных в Jinja.
{{ modules.datetime.datetime.now() }}
{{ modules.datetime.timedelta(days=1) }}
{{ modules.pytz.timezone('UTC') }}
{{ modules.re.match('.*', 'test') }}
{{ modules.itertools.chain([1, 2], [3, 4]) | list }}
Available modules:
datetime— datetime, timedelta, date, timepytz— timezonesre— regexitertools— iteration tools
Senior usage: date arithmetic, regex extraction, set operations.
{% set yesterday = (modules.datetime.datetime.now() - modules.datetime.timedelta(days=1)).date() %}
WHERE date >= '{{ yesterday }}'
Прочие функции
log(msg, info=False)— log to terminal.info=Trueshows in UI, otherwise debug log.exceptions.raise_compiler_error(msg)— raise error.exceptions.warn(msg)— warning.return(value)— return value из macro (special syntax —{% set x = return(value) %}или{{ return(value) }}).set— Jinja-native, для creating Python sets.
Полная map: что где доступно
Попробуй сам
-
Создайте comprehensive inspect macro:
`{% macro inspect_full() %}` `{{ log("--- Context Inspection ---", info=True) }}` `{{ log("Class: " ~ self.__class__.__name__, info=True) }}` `{{ log("execute: " ~ execute, info=True) }}` `{{ log("target.name: " ~ target.name, info=True) }}` `{{ log("target.type: " ~ target.type, info=True) }}` `{% if ref is defined %}` `{{ log("ref available", info=True) }}` `{% endif %}` `{% if this is defined %}` `{{ log("this: " ~ this, info=True) }}` `{% endif %}` `{% if model is defined %}` `{{ log("model.unique_id: " ~ model.unique_id, info=True) }}` `{% endif %}` `{% if graph is defined %}` `{{ log("graph.nodes count: " ~ (graph.nodes | length), info=True) }}` `{% endif %}` `{% endmacro %}` -
Используйте в разных contexts:
-- models/test.sql `{{ inspect_full() }}` SELECT 1 as xdbt compile --select test 2>&1 | grep "Context\|Class\|execute\|target\|ref\|this\|model\|graph" -
In dbt_project.yml:
on-run-end: - "`{{ inspect_full() }}`"dbt run --select test 2>&1 | grep "Context"Observe: this и model probably NOT defined in hook context.
-
Откройте
core/dbt/context/providers.py, найдитеclass ProviderContext. Read all @contextproperty methods.