Перейти к основному содержимому

Недостающий гид по debug() в dbt

· 7 мин. чтения
Benoit Perigaud

Примечание редактора — этот пост предполагает средний уровень знаний Jinja и разработки макросов в dbt. Для введения в Jinja в dbt ознакомьтесь с документацией и бесплатным курсом Jinja, Macros, Packages.

Jinja приносит много возможностей в dbt, позволяя нам использовать ref(), source(), условный код и макросы. Но, хотя Jinja приносит гибкость, она также добавляет сложность, и, как часто бывает с кодом, вещи могут работать не так, как ожидалось.

Макрос debug() в dbt — отличный инструмент для тех, кто пишет много кода на Jinja, но может быть сложно понять, как его использовать и какие преимущества он приносит.

Давайте погрузимся в последний случай, когда я использовал debug() и как он помог мне решить ошибки в моем коде.

Jinja в dbt

Работая над функцией для пакета dbt_project_evaluator, мои запуски dbt начали постоянно завершаться сбоем, предоставляя мне следующее сообщение:

16:49:26  Database error while running on-run-end
16:49:26 Encountered an error:
Runtime Error
Parser Error:

И всё!?!?

via GIPHY

Так как моя конфигурация on-run-end в dbt_project.yml была следующей, я, по крайней мере, мог определить, что проблема была в моем макросе print_dbt_project_evaluator_issues:

on-run-end: "{{ dbt_project_evaluator.print_dbt_project_evaluator_issues() }}"

Но, кроме этого понимания, не было упоминания о конкретной строке или неудачном макросе — поэтому первым шагом было попытаться понять, какая часть моего кода вызывает ошибку. У меня было два варианта:

  1. Написать кучу операторов print("Here") или log("there", info=true) в моих макросах и посмотреть, какие из них будут напечатаны, а какие нет.
  2. Использовать команду debug(), чтобы как найти, где мой код не работает, так и посмотреть на мои переменные во время выполнения кода.

Как вы могли догадаться, это руководство о варианте №2.

Введение в debug() в Jinja

debug() — это команда, доступная в dbt, используемая для установки точек останова в вашем коде Jinja. Эти точки останова останавливают выполнение вашего кода и предоставляют возможность исследовать переменные и выполнять следующую часть вашего кода шаг за шагом.

Как использовать

Прежде всего, debug() недоступен в dbt Cloud, так как он не предоставляет полный доступ к терминалу, поэтому вам придется установить и использовать dbt-core локально.

Затем, чтобы войти в режим отладки, вам нужно:

  • Написать {{ debug() }} в вашем коде — там, где вы хотите начать отладку — и
  • установить переменную окружения DBT_MACRO_DEBUGGING в любое значение. Это можно сделать для всей сессии оболочки, введя export DBT_MACRO_DEBUGGING=1 в командной строке, или для каждой команды, добавив переменную окружения перед всей командой, например, DBT_MACRO_DEBUGGING=1 dbt build. Без этой переменной команда debug() не будет оценена, и вы не войдете в режим отладки.

Вернемся к нашей первоначальной проблеме, давайте используем debug, чтобы определить, где в нашем коде есть ошибки

Если вы поместите {{ debug() }} в одну или несколько секций вашего кода и в режиме отладки нажмете c, отладчик остановится на каждой из ваших точек останова, позволяя вам найти, какая часть кода не работает.

В моем случае,

{% set my_results = run_query(sql_statement) %}
{{ debug() }}

не удалось войти в режим отладки, но

{{ debug() }}
{% set my_results = run_query(sql_statement) %}

вошел в режим отладки, сообщив мне, что что-то не так с выполнением моего фактического запроса.

Теперь, когда мы нашли, где проблема, может ли debug() помочь нам ее исправить? Давайте посмотрим на различные команды, доступные в отладчике.

Использование полной мощности отладки Jinja

команды отладки

С кодом в режиме отладки мы получаем полностью функциональный интерактивный отладчик Python, показывающий нам эту информацию: ipdb> (технически, ipdb означает IPython отладчик).

Первая команда, которую мы можем ввести, это h, чтобы получить список помощи и доступных команд:

Documented commands (type help <topic>):
========================================
EOF clear display l pfile return tbreak where
a commands down list pinfo retval u
alias condition enable ll pinfo2 run unalias
args cont exit longlist pp rv undisplay
b context h n psource s unt
break continue help next q skip_hidden until
bt d ignore p quit skip_predicates up
c debug j pdef r source w
cl disable jump pdoc restart step whatis

Miscellaneous help topics:
==========================
exec pdb

Undocumented commands:
======================
interact

Это руководство не будет описывать все команды ipdb, доступные нам, существует множество онлайн-руководств по этой теме, но мы сосредоточимся на самых полезных в большинстве случаев отладки Jinja:

  • a: Список текущих параметров для функций, в которых вы находитесь.
  • c: Продолжить выполнение кода до следующей точки останова или до конца программы, если других точек останова нет.
  • p и pp: Печать и красивая печать данных.
    • p часто будет печатать данные в одной строке, обернутой на несколько строк.
    • pp напечатает ту же информацию, но добавит новые строки, чтобы было легче быстро взглянуть на переменную; pp особенно полезен для печати списков и словарей.

Использование интерактивной подсказки для решения нашей проблемы

Находясь в ipdb, вы также можете ввести некоторый код на Python, чтобы исследовать вашу программу и текущее значение ваших переменных. Например, ввод locals().keys() или p locals().keys() возвращает список текущих локальных переменных (ввод просто locals() печатает как имена переменных, так и их значения, что, скорее всего, полностью заполнит ваш терминал).

ipdb в Jinja не вернет список переменных с точно такими же именами, как в вашем коде, но вы увидите переменные с очень похожими именами, с просто префиксом, как l_1_<my_variable> или l_2_<my_variable> в зависимости от циклов в вашем коде Jinja.

В моем случае отладчик возвращает следующий (сокращенный) список:

dict_keys(['l_1_schema_project_evaluator', 'l_1_db_project_evaluator', 't_2', ..., 'l_1_results', ..., 'l_2_graph', ..., 'l_2_sql_statement', 'environment', 'missing', 'resolve', 't_1', 'undefined'])

Советую искать переменные с похожими именами на переменные, которые я либо сам определил, либо прочитал из своего кода. Здесь я вижу l_2_sql_statement как часть моего списка переменных и могу также напечатать его значение в своем терминале, введя p l_2_sql_statement.

Ввод p l_2_sql_statement вернул следующее в мой терминал:

`'\n select * from duck.main.model.dbt_project_evaluator.fct_documentation_coverage\n '`

Мы можем сразу увидеть, что есть проблема в SQL, сгенерированном в рамках моего макроса, так как я пытаюсь прочитать из duck.main.model.dbt_project_evaluator.fct_documentation_coverage (конкатенируя базу данных, схему и уникальный идентификатор модели) вместо duck.main.fct_documentation_coverage (конкатенируя базу данных, схему и имя таблицы модели). Мы нашли проблему.

Чтобы исправить это, мы можем воспользоваться возможностью изменять переменные в режиме отладки. Сначала мы можем присвоить новое значение переменной, введя l_2_sql_statement = '\n select * from duck.main.fct_documentation_coverage\n ', а затем ввести c в отладчике, чтобы позволить макросу выполняться до завершения или достижения новой точки останова. В моем случае оператор сработал после того, как я изменил l_2_sql_statement, и я могу вернуться к логике в своем коде, чтобы увидеть, почему его значение не такое, как я ожидал.

Использование отладчика для анализа переменных Jinja в dbt

Отладчик также можно использовать для исследования встроенных переменных и функций Jinja, доступных в dbt.

В моем коде я также смотрел на объект результатов, доступный в контексте on-run-end. Мы можем фактически увидеть его в предыдущем списке, названном l_1_results.

В отладчике, если я введу type(l_1_results), программа скажет мне, что это list. Затем я могу выполнить type(l_1_results[0]), и dbt теперь скажет мне, что тип переменной — это dbt.contracts.results.RunResult.

Мой последний шаг для анализа объекта результатов — ввести pp l_1_results[0].to_dict(), и CLI затем вернет красиво отформатированную версию всех полей и значений, доступных в первом элементе моего объекта results.

Заключительные мысли

Надеюсь, это краткое руководство дало вам представление о том, как debug() может помочь вам более эффективно разрабатывать код Jinja и исследовать потенциальные ошибки. И не стесняйтесь переходить в #advice-dbt-for-power-users в сообществе dbt в Slack, если вы хотите обсудить более подробно отладку!

Comments

Loading