Learning Platform
Глоссарий Troubleshooting
Урок 04.03 · 28 мин
Продвинутый
jinjacontextobjects

Объекты в 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

  1. ref to ephemeral model — генерирует CTE, не table reference:

    `{{ ref('ephemeral_model') }}`
    -- compiled: __dbt__cte__ephemeral_model
  2. ref to disabled model — fail с “ref to disabled model”.

  3. ref to non-existent model — fail с “model not found”.

  4. Versioned refref('foo', version=2) lookups model.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:

  1. Freshness configsource имеет freshness config для проверки stale data.
  2. Quoting — adapter-specific quoting (Snowflake case-sensitive).
  3. 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 порядок):

  1. CLI --vars 'my_var: value' — highest
  2. profiles.yml vars: для target
  3. dbt_project.yml vars: для project (или global)
  4. 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 — это сама текущая модель. Используется для:

  1. Incremental — WHERE > (SELECT MAX FROM {{ this }}).
  2. Self-references — заменя на {{ this }} в snapshots.
  3. Logging — {{ log('Building ' ~ this, info=True) }}.

Edge cases:

  1. this available только в model body (ProviderContext). В macros вызываемых из model — тоже доступен.
  2. В singular tests this undefined — нет «текущей модели».
  3. В hooks this undefined.

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 -> node
  • graph.sources — dict unique_id -> source
  • graph.macros — dict unique_id -> macro
  • graph.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, time
  • pytz — timezones
  • re — regex
  • itertools — 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=True shows 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: что где доступно

Availability map: что в каком context
ObjectОбъект Jinja context
In modelProviderContext
In macroDepends on calling context
In hookMacroContext, limited
In gen_nameGenerateNameContext
ref()
[x]
inherit
[ ]
[ ]
source()
[x]
inherit
[ ]
[ ]
var(), env_var()
[x]
[x]
[x]
[x]
target
[x]
[x]
[x]
[x]
this
[x]
inherit
[ ]
[ ]
model
[x]
inherit
[ ]
[x]
adapter
[x]
[x]
[x]
[ ]
statement, run_query
if execute
if execute
if execute
[ ]
graph
partial
partial
[x]
[ ]

Попробуй сам

  1. Создайте 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 %}`
  2. Используйте в разных contexts:

    -- models/test.sql
    `{{ inspect_full() }}`
    SELECT 1 as x
    dbt compile --select test 2>&1 | grep "Context\|Class\|execute\|target\|ref\|this\|model\|graph"
  3. 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.

  4. Откройте core/dbt/context/providers.py, найдите class ProviderContext. Read all @contextproperty methods.


Проверка знанийKnowledge check
Вы пишете macro get_audit_relation() который должен возвращать Relation для audit table в той же database/schema что и current model, с suffix _audit. Где он будет вызываться из материализации модели. Какие из объектов context вам понадобятся, и какой код покажет правильную работу с потенциально nullable полями?
ОтветAnswer
Нужны: this (для current model location), target (для defaults если this missing нет), Relation class (для constructing new Relation). Код: ```sql {% macro get_audit_relation() %} {%- set audit_identifier = this.identifier ~ '_audit' -%} {%- set audit_rel = api.Relation.create( database=this.database, schema=this.schema, identifier=audit_identifier ) -%} {{ return(audit_rel) }} {% endmacro %} ```. Здесь api.Relation.create — фактори для конструирования Relation (доступен в ProviderContext через api namespace). this — это current model's Relation, у которого есть .database, .schema, .identifier как attributes. Edge cases (nullable): (1) this может быть None в некоторых contexts (на singular tests, hooks) — добавить guard: {% if this is defined and this %} {% else %} {{ exceptions.raise_compiler_error("get_audit_relation must be called from model context") }} {% endif %}. (2) this.database может быть None если adapter не поддерживает databases (e.g. некоторые SQLite-like). Fallback: database=this.database or target.database. (3) this.schema overrides через generate_schema_name могут быть quoted/lower-cased differently — better использовать exact value, не reconstruct. (4) audit_identifier collision — если у вас уже есть model "foo_audit", get_audit_relation для model "foo" вернёт ту же relation. Защита: проверить, что audit_identifier не существует в Manifest как dbt-managed model: {% set conflict = graph.nodes.values() | selectattr('identifier', 'equalto', audit_identifier) | list %} {% if conflict %} {{ exceptions.raise_compiler_error("...") }} {% endif %}. (5) Cross-schema audit — если бизнес требует все audit в "audit" schema regardless of model schema, override schema=target.audit_schema or var('audit_schema', 'audit'). Production-grade version всё это валидирует и documenting.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Senior понимает, что ref() registers зависимость + возвращает Relation. Какие methods/properties Relation должен знать?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5