Примеры использования Jinja шаблонов в Apache Superset

Содержание

Что такое 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 %}

Подборка видео на тему использования Jinja в Apache Superset (YouTube)

Preset Live Demo — Jinja Templating

3.5. RT.DataVision. Создание чартов. Модификация запросов SQL при помощи jinja templating

Ivan Shamaev (Admin)
Работаю с Apache Superset с 2021 года. Веду этот блог, чтобы систематизировать свои знания и поделиться ими с другими специалистами. Подписывайтесь на мой телеграм канал @apache_superset_bi
6
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x