Что такое Jinja при использовании в Apache Superset?
Jinja2 — это шаблонизатор для Python, используемый для генерации динамического SQL за счет встраивания в код запроса переменных, условий и выражений.
Суть его работы заключается в том, что он заменяет часть SQL кода значениями переменных или функций с помощью определенного синтаксиса, который будет описан дальше в статье. После того, как код динамически сгенерировался — он отправляется на сервер базы данных.
Тем самым с помощью использования Jinja мы можем повысить интерактивность своих Dashboards, за счет кастомизации sql в метриках, динамических измерениях и виртуальных датасетах.
Если кратко, то выглядит это примерно так (пока без погружения в детали, картинка с хабра):
Еще один пример с хабра:
На высоком уровне шаблонизация — это возможность добавлять программные возможности к SQL, при этом Jinja действует как мост, позволяющий нам вводить контекст в запросы.
Для чего можно использовать Jinja
Включение Jinja повышает гибкость функциональности фильтра Superset. Вот несколько более конкретных примеров того, для чего можно использовать Jinja:
- Отображение только данных вошедшего в систему пользователя.
- Использование компонента фильтра для фильтрации запроса, когда имя столбца фильтра не соответствует имени в текущем запросе.
- Применение ограничений фильтра через URL-адрес дашборда Superset.
- Для таблиц, содержащих массивы, возможность поиска записей с определенным значением в массиве.
- Возможность применять операторы OR в фильтре для двух разных столбцов (например
‘name’ = “Matthew” OR ‘age’ = 12) - Персонализированные дашборды.
- Управление доступом к данным с использованием доступа на уровне строк на основе User_ID или Email (использование информации о пользователе в Row Level Security RLS — построчное ограничение доступа на уровне датасета).
- Исследуйте доступ к метаданным Superset по User_ID.
Как работает контекст фильтров и Jinja в Superset
В контексте Jinja состояние фильтра можно менять за счет использования информации:
- Состояние пользователя
- user_id человека, вошедшего в систему
- имя пользователя
- email пользователя
- Состояние URL
- вы можете добавить параметры в URL-адрес Superset, который можно вставить в ваш запрос (то есть внедрить внутрь url параметры и их значения использовать через Jinja).
- Например, если у вас есть Dashboard по продажам, а конечный пользователь хочет видеть продажи только в Барселоне:
- Можно отправить панель мониторинга, где они смогут перейти к фильтру и нажать «Барселона».
- Отправьте информационную панель с предварительно отфильтрованным URL-адресом с городом в самом параметре URL-адреса, чтобы при загрузке дашборда они могли сразу увидеть то, что хотят.
Немного про синтаксис Jinja
- Выражение
{{ ... }}: выражения используются, когда вы хотите вывести строку. Вы можете использовать выражения для ссылки на переменные и вызова макросов. - Оператор
{% ... %}: этот оператор не выводит строку. Он используется для потока управления, например, для настройки for циклов и if (условных) операторов, для установки или изменения переменных или для определения макросов. - Комментарий
{# ... #}: комментарии Jinja используются для предотвращения выполнения текста внутри комментария или вывода строки.
Как активировать работу Jinja в Apache Superset?
Для того, чтобы начать работу с Jinja в Apache Superset, необходимо в файле
superset/docker/pythonpath_dev/superset_config.py
активировать настройку (флаг)
"ENABLE_TEMPLATE_PROCESSING": True
Полный код в конфиг файле выглядит как:
FEATURE_FLAGS = {
"ALERT_REPORTS": True,
"DATAPANEL_CLOSED_BY_DEFAULT": True,
"DASHBOARD_VIRTUALIZATION": True,
"DASHBOARD_RBAC": True,
"ENABLE_TEMPLATE_PROCESSING": True,
"ESCAPE_MARKDOWN_HTML": True,
"LISTVIEWS_DEFAULT_CARD_VIEW": True,
"THUMBNAILS": True,
"DRILL_BY": True,
"DRILL_TO_DETAIL": True,
"HORIZONTAL_FILTER_BAR": True,
"ESTIMATE_QUERY_COST": True,
"TAGGING_SYSTEM": True,
"HTML_SANITIZATION": False,
}
Jinja Templates (Шаблоны Джинджа) в Superset
Функциональность Superset «SQL Lab» и «Explore» поддерживает шаблоны Jinja в SQL запросах. Если включено использование шаблонов, код Python можно внедрить в виртуальные наборы данных и в пользовательский SQL в фильтрах и элементах управления метриками в Explore. По умолчанию в контексте Jinja доступны следующие переменные:
columns: столбцы, по которым группируется запрос.filter: фильтры, примененные в запросеfrom_dttm: начальноеdatetimeзначение из выбранного временного диапазона (Noneесли не определено)to_dttm: конечноеdatetimeзначение из выбранного временного диапазона (Noneесли не определено)groupby: столбцы, по которым группируется запрос (устарело)metrics: агрегированные выражения в запросеrow_limit: ограничение строки запросаrow_offset: смещение строки запросаtable_columns: столбцы, доступные в наборе данныхtime_column: временной столбец запроса (Noneесли не определен)time_grain: выбранный временной интервал (Noneесли не определен)
Параметры Jinja вызываются в двойных скобках в запросе и без них в логических блоках.
SELECT *
FROM tbl
WHERE dttm_col > '{{ from_dttm }}' and dttm_col < '{{ to_dttm }}'
Вы также можете использовать логику Jinja, чтобы сделать ваш запрос устойчивым к очистке фильтра временного диапазона:
SELECT *
FROM tbl
WHERE (
{% if from_dttm is not none %}
dttm_col > '{{ from_dttm }}' AND
{% endif %}
{% if to_dttm is not none %}
dttm_col < '{{ to_dttm }}' AND
{% endif %}
true
)
Добавление пользовательских функций/макросов в контекст Jinja
Чтобы добавить пользовательские макросы в контекст Jinja, вам необходимо перегрузить контекст Jinja по умолчанию в вашей среде, указав в JINJA_CONTEXT_ADDONS конфигурации Apache Superset (файл superset_config.py). Объекты, указанные в этом словаре, доступны для использования пользователями там, где доступен контекст Jinja.
Синтаксис:
JINJA_CONTEXT_ADDONS = {
'<Template_name>': <function body>,
}
Пример:
JINJA_CONTEXT_ADDONS = {
'my_new_custom_macro': lambda x: x*2,
}
Как использовать JINJA_CONTEXT_ADDONS в SQL в Superset
Рассмотрим на примере простой функции:
JINJA_CONTEXT_ADDONS = {
'tst': lambda x=0, y=0: x * y,
}
Добавляем в конфиг:
Добавляем в SQL запрос в SQL Lab:
Default Jinja value в SQL Lab
В SQL Lab справа рядом с кнопкой сохранить, нажмите на три точки и выберите Parameters:
Откроется меню, в котором можно внести значения по умолчанию:
Параметры становятся доступными в вашем SQL (пример: SELECT * FROM {{ my_table }}) с помощью синтаксиса шаблонов Jinja.
Параметры шаблона SQL Lab хранятся вместе с набором данных в формате TEMPLATE PARAMETERS.
Существует специальный _filters параметр, который можно использовать для проверки фильтров, используемых в шаблоне jinja.
{
"_filters": [
{
"col": "action_type",
"op": "IN",
"val": ["sell", "buy"]
}
]
}
SELECT action, count(*) as times
FROM logs
WHERE action in {{ filter_values('action_type'))|where_in }}
GROUP BY action
Примечание _filters не сохраняется вместе с набором данных. Он используется только в пользовательском интерфейсе SQL Lab.
CUSTOM_TEMPLATE_PROCESSORS шаблон SQL
Помимо шаблонов Jinja по умолчанию, лаборатория SQL также поддерживает самоопределяемый процессор шаблонов, устанавливая его CUSTOM_TEMPLATE_PROCESSORS в конфигурации расширенного набора.
Значения в этом словаре перезаписывают процессоры шаблонов Jinja по умолчанию для указанного ядра базы данных. В приведенном ниже примере настраивается пользовательский процессор шаблонов presto, который реализует собственную логику обработки шаблона макроса с анализом регулярных выражений. Он использует макрос $ стиля вместо {{ }} стиля в шаблонах Jinja.
При настройке с помощью CUSTOM_TEMPLATE_PROCESSORS шаблон SQL в базе данных presto обрабатывается пользовательским шаблоном, а не шаблоном по умолчанию.
def DATE(
ts: datetime, day_offset: SupportsInt = 0, hour_offset: SupportsInt = 0
) -> str:
"""Current day as a string."""
day_offset, hour_offset = int(day_offset), int(hour_offset)
offset_day = (ts + timedelta(days=day_offset, hours=hour_offset)).date()
return str(offset_day)
class CustomPrestoTemplateProcessor(PrestoTemplateProcessor):
"""A custom presto template processor."""
engine = "presto"
def process_template(self, sql: str, **kwargs) -> str:
"""Processes a sql template with $ style macro using regex."""
# Add custom macros functions.
macros = {
"DATE": partial(DATE, datetime.utcnow())
} # type: Dict[str, Any]
# Update with macros defined in context and kwargs.
macros.update(self.context)
macros.update(kwargs)
def replacer(match):
"""Expand $ style macros with corresponding function calls."""
macro_name, args_str = match.groups()
args = [a.strip() for a in args_str.split(",")]
if args == [""]:
args = []
f = macros[macro_name[1:]]
return f(*args)
macro_names = ["$" + name for name in macros.keys()]
pattern = r"(%s)\s*\(([^()]*)\)" % "|".join(map(re.escape, macro_names))
return re.sub(pattern, replacer, sql)
CUSTOM_TEMPLATE_PROCESSORS = {
CustomPrestoTemplateProcessor.engine: CustomPrestoTemplateProcessor
}
SQL Lab также включает функцию проверки запросов в реальном времени с подключаемыми серверными модулями. Вы можете настроить, какая реализация проверки используется с каким ядром базы данных, добавив в файл конфигурации блок, подобный следующему:
FEATURE_FLAGS = {
'SQL_VALIDATORS_BY_ENGINE': {
'presto': 'PrestoDBSQLValidator',
}
}
Доступные валидаторы и имена можно найти в sql_validators.
Где находится исходный код Jinja Python в репозитории Superset в GitHub
Исходный код шаблонизации Jinja лежит в файле:
superset/superset/jinja_context.py
Я не пробовал, но теоретически сюда можно добавить нестандартный функционал для вызова через функционал Jinja. Но это уже будет нестандартной доработкой Superset, что усложнит обновление на новые версии Superset.
Доступные макросы Jinja для использования в Superset
В этом разделе будут описаны предопределенные макросы Jinja в Superset, которые доступны для использования.
Current Username: current_username()
Макрос {{ current_username() }} возвращает имя username текущего пользователя, вошедшего в систему.
Если в конфигурации Superset включено кэширование, то по умолчанию значение username будет использоваться Superset при вычислении ключа кэша.
Ключ кэша — это уникальный идентификатор, который определяет, произойдет ли попадание в кэш в будущем, и Superset сможет получить кэшированные данные.
Вы можете отключить включение значения username в вычисление ключа кэша, добавив в свой код Jinja следующий параметр:
{{ current_username(add_to_cache_keys=False) }}
Current User ID: current_user_id()
Макрос {{ current_user_id() }} возвращает идентификатор учетной записи вошедшего в систему пользователя.
Вы можете отключить включение значения учетной записи id в расчет ключа кэша, добавив в свой код Jinja следующий параметр:
{{ current_user_id(add_to_cache_keys=False) }}
Current User Email: current_user_email()
Макрос {{ current_user_email() }} возвращает адрес электронной почты текущего пользователя, вошедшего в систему.
Вы можете отключить включение значения электронной почты в расчет ключа кэша, добавив в свой код Jinja следующий параметр:
{{ current_user_email(add_to_cache_keys=False) }}
Custom URL Parameters: url_param('custom_variable')
Макрос {{ url_param('custom_variable') }} позволяет вам определять произвольные параметры URL-адреса и ссылаться на них в вашем коде SQL.
Примеры custom параметров URL-адреса
Предположим, Вы написали SQL запрос в SQLLab интерфейсе:
SELECT count(*)
FROM ORDERS
WHERE country_code = '{{ url_param('countrycode') }}'
Предположим ваш суперсет находится на домене your-superset.ru.
Вы решили отправить одному коллеге URL-адрес SQL LAB:
https://your-superset.ru/superset/sqllab?countrycode=ES
А другому коллеге URL-адрес SQL LAB:
https://your-superset.ru/superset/sqllab?countrycode=US
У первого коллеги SQL будет отображаться как:
SELECT count(*) FROM ORDERS WHERE country_code = 'ES'
А у второго коллеги, как:
SELECT count(*) FROM ORDERS WHERE country_code = 'US'
Explicitly Including Values in Cache Key: cache_key_wrapper()
Функция {{ cache_key_wrapper() }} явно указывает Superset добавить значение в накопленный список значений, используемых при вычислении ключа кэша.
Эта функция необходима только в том случае, если вы хотите поместить возвращаемые значения собственной пользовательской функции в ключ кэша.
Обратите внимание, что эта функция обеспечивает кэширование значений user_id и username в вызовах функций current_user_id() и current_username() (если у вас включено кеширование).
Filter Values: filter_values()
Вы можете получить значение для определенного фильтра в виде списка, используя
{{ filter_values() }}
Это полезно, если:
- Вы хотите использовать компонент фильтра для фильтрации запроса, в котором имя столбца компонента фильтра не соответствует имени в операторе выбора.
- Вы хотите иметь возможность фильтровать внутри основного запроса в целях повышения производительности.
Пример с описанным кейсом:
SELECT action, count(*) as times
FROM logs
WHERE
action in {{ filter_values('action_type')|where_in }}
GROUP BY action
Здесь where_in фильтр преобразует список значений filter_values('action_type') в строку, подходящую для IN выражения.
Filters for a Specific Column: get_filters()
Макрос {{ get_filters() }} возвращает фильтры, примененные к данному столбцу.
Помимо возврата значений (аналогично тому, как это filter_values() происходит), макрос get_filters() возвращает оператор, указанный в пользовательском интерфейсе Explore.
Это полезно, если:
- Вы хотите обрабатывать больше, чем оператор IN в SQL clause.
- Вы хотите обрабатывать создание custom SQL conditions для фильтра.
- Вы хотите иметь возможность фильтровать внутри основного запроса для повышения скорости.
Пример кейса:
WITH RECURSIVE
superiors(employee_id, manager_id, full_name, level, lineage) AS (
SELECT
employee_id,
manager_id,
full_name,
1 as level,
employee_id as lineage
FROM
employees
WHERE
1=1
{# Render a blank line #}
{%- for filter in get_filters('full_name', remove_filter=True) -%}
{%- if filter.get('op') == 'IN' -%}
AND
full_name IN {{ filter.get('val')|where_in }}
{%- endif -%}
{%- if filter.get('op') == 'LIKE' -%}
AND
full_name LIKE {{ "'" + filter.get('val') + "'" }}
{%- endif -%}
{%- endfor -%}
UNION ALL
SELECT
e.employee_id,
e.manager_id,
e.full_name,
s.level + 1 as level,
s.lineage
FROM
employees e,
superiors s
WHERE s.manager_id = e.employee_id
)
SELECT
employee_id, manager_id, full_name, level, lineage
FROM
superiors
order by lineage, level
Специальные функции базы данных
Другой случай, когда шаблоны Jinja чрезвычайно полезны в контексте Superset, — это использование экзотических функций, специфичных для базы данных, которые не были реализованы в базовых спецификациях ядра базы данных.
- Например, TimescaleDB расширяет PostgreSQL, добавляя новые функциональные возможности, а также упаковывает и оптимизирует PostgreSQL для анализа данных временных рядов. TimescaleDB имеет собственную уникальную функцию time_bucket() , которая оптимизирует функцию PostgreSQL date_trunc() по умолчанию . Эта
time_bucket()функция работает намного быстрее и производительнее, чемdate_trunc()при попытке разрезать миллионы и миллиарды строк значений дат. Здесь шаблоны Jinja становятся предохранительным клапаном, который позволяет вам использовать особенности БДtime_bucket(), избегая при этом использования гораздо более медленныхdate_trunc()
Использование Jinja в Datasets в Apache Superset
todo
Описание практических кейсов по использованию Jinja в Superset
Подставление той или иной таблички в SQL FROM Statement
select * from
{% if my_table %}
{{my_table}}
{% else %}
foo
{% endif %}














8 Comments