Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Jinja2: шаблоны для конфигураций и отчётов

Jinja2: генерация текста, конфигураций и отчётов

Jinja2: синтаксис шаблонов, наследование и макросы, песочница, рендеринг конфигураций и правила безопасности.

Прямой ответ

Jinja2 — шаблонизатор для Python: текст с подстановками, условиями и циклами превращается в готовый документ. Он применяется не только в веб-интерфейсах, но и для генерации конфигурационных файлов, отчётов, писем и инфраструктуры.

Три типа разметки:

  • {{ выражение }} — подстановка значения.
  • {% инструкция %} — логика: условия, циклы, включение других шаблонов.
  • {# комментарий #} — не попадает в результат.
{# шаблон конфигурации сервиса #}
[service]
name = {{ service.name }}
workers = {{ service.workers | default(4) }}
{% if service.tls.enabled %}
tls_cert = {{ service.tls.cert_path }}
{% endif %}

[upstreams]
{% for u in service.upstreams %}
{{ u.name }} = {{ u.host }}:{{ u.port }}
{% endfor %}
from jinja2 import Environment, FileSystemLoader, StrictUndefined

env = Environment(
    loader=FileSystemLoader("templates"),
    undefined=StrictUndefined,      # обращение к несуществующей переменной — ошибка
    trim_blocks=True, lstrip_blocks=True,
    keep_trailing_newline=True,
)
tmpl = env.get_template("service.conf.j2")
print(tmpl.render(service=cfg))

Ключевая настройка — undefined. По умолчанию обращение к несуществующей переменной даёт пустую строку, что приводит к молчаливой генерации неполной конфигурации. StrictUndefined превращает это в ошибку на этапе рендеринга.

Вторая важная часть — фильтры: default, join, tojson, quote, upper, indent. Они заменяют предварительную подготовку данных в коде:

ALLOWED_HOSTS = {{ hosts | tojson }}
GREETING = {{ user.name | default("гость") | capitalize }}

Практика: где применять и как не навредить

1. Генерация конфигураций. Один шаблон и набор значений на окружение заменяют ручное редактирование файлов. Важно: результат должен проверяться — синтаксис сгенерированного файла валидируют до применения. О подходах к инфраструктуре как коду — в статье про Terraform.

2. Отчёты и письма. HTML или текст с данными из базы. Практика: шаблон письма хранится отдельно от кода, а данные передаются как структура, прошедшая валидацию — см. JSON Schema.

3. Наследование шаблонов. Базовый шаблон задаёт каркас, дочерние заполняют блоки:

{# base.html.j2 #}
<html><body>
{% block content %}{% endblock %}
</body></html>

{# report.html.j2 #}
{% extends "base.html.j2" %}
{% block content %}<h1>{{ title }}</h1>{% endblock %}

4. Макросы — переиспользуемые фрагменты.

{% macro field(label, value) -%}
<tr><th>{{ label }}</th><td>{{ value }}</td></tr>
{%- endmacro %}

{{ field("Заказ", order.id) }}
{{ field("Сумма", order.total) }}

5. Автоматическое экранирование обязательно для HTML. Без него пользовательские данные вставляются как есть, что даёт межсайтовый скриптинг. В Jinja2 автоэкранирование включается явно или через выбор расширений; в веб-фреймворках оно обычно включено по умолчанию.

env = Environment(loader=loader, autoescape=select_autoescape(["html", "xml"]))

6. Шаблоны из недоверенного источника требуют песочницы. Если текст шаблона приходит от пользователя, обычное окружение позволяет выполнить произвольный код через атрибуты объектов. Для таких случаев предусмотрен SandboxedEnvironment.

Рекомендации:

  1. Не переносите бизнес-логику в шаблоны. Условие, проверяющее права или рассчитывающее сумму, должно находиться в коде; шаблон отвечает только за представление.
  2. Используйте StrictUndefined. Молчаливая подстановка пустой строки — источник конфигураций, которые «почти работают».
  3. Следите за пробелами. trim_blocks и lstrip_blocks убирают лишние переводы строк после блоков; без них результат содержит пустые строки, что критично для форматов вроде INI и YAML.
  4. Проверяйте результат. Сгенерированный YAML или JSON стоит разобрать парсером до применения; синтаксическая ошибка в конфигурации обнаружится слишком поздно.
  5. Не экранируйте вручную при включённом автоэкранировании. Двойное экранирование даёт видимые пользователю артефакты вроде &amp;amp;.
  6. Тестируйте шаблоны отдельно от кода. Шаблон — это программа, поэтому он требует проверки: рендеринг на эталонных данных, проверка обязательных переменных, проверка формата результата. Один тест на каждый шаблон стоит дешевле, чем разбор сломанной конфигурации на сервере.
  7. Держите данные и представление раздельно. Подготовка значений (форматирование дат, округление сумм, выбор формулировки) выполняется в коде, а шаблон получает готовые к выводу значения. Иначе логика расползается по шаблонам и перестаёт покрываться тестами.
  8. Избегайте вложенных условий глубже двух уровней. Сложная разметка читается хуже кода; такие места разбивают на макросы или переносят вычисление флага в код.
  9. Фиксируйте версию Jinja2 в зависимостях. Поведение фильтров и обработки неопределённых значений менялось между версиями, поэтому воспроизводимость сборки зависит от закреплённой версии библиотеки.

Практический сценарий: сгенерированная конфигурация сломала запуск сервиса

Контекст: конфигурация сервиса генерировалась из шаблона при развёртывании. После добавления нового окружения сервис перестал запускаться с ошибкой разбора файла.

  1. Диагностика. В сгенерированном файле параметр workers оказался пустым: в значениях окружения не было ключа workers, а Jinja2 по умолчанию подставила пустую строку.
  2. Причина 1. Использовался режим undefined по умолчанию, поэтому отсутствие переменной не вызывало ошибку.
  3. Причина 2. Результат генерации не проверялся: файл сразу копировался в рабочую директорию.
  4. Действия. (а) Включили StrictUndefined, чтобы отсутствие переменной прерывало генерацию; (б) добавили значения по умолчанию через фильтр default там, где они действительно уместны; (в) в пайплайн добавили шаг валидации сгенерированного файла парсером; (г) для обязательных параметров ввели проверку на уровне схемы значений — см. JSON Schema.
  5. Результат. Ошибка теперь обнаруживается на этапе генерации с указанием имени переменной, а не при запуске сервиса на сервере.

Вывод: шаблонизатор удобен именно тем, что допускает неполные данные. В конфигурациях эта гибкость вредна: явная ошибка на этапе генерации дешевле, чем неработающий сервис.

FAQ

Можно ли использовать Jinja2 для генерации YAML? Да, это распространённая практика, но с оговоркой: результат нужно проверять парсером YAML. Отступы и специальные символы в значениях легко ломают структуру, поэтому строковые значения лучше выводить через фильтр tojson или quote.

Чем Jinja2 отличается от f-строк? F-строки работают внутри кода и не поддерживают условия, циклы и наследование в читаемом виде. Jinja2 хранит шаблон отдельно, что позволяет менять представление без изменения кода и переиспользовать фрагменты.

Нужна ли песочница, если шаблоны пишут разработчики? Нет, если шаблоны поставляются вместе с кодом. Песочница нужна, когда текст шаблона задаёт пользователь или внешний сервис: без неё шаблон может выполнить произвольный код.

Как отлаживать шаблон? Полезны режим отладки в окружении, вывод промежуточных значений через {{ var | tojson }} и проверка результата в отдельном файле. Для сложных шаблонов помогает разбиение на макросы, которые можно тестировать по отдельности.

Источники: Jinja2 docs (API, sandbox), Ansible docs (templating) — ссылки в шапке.