Прямой ответ
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.
Рекомендации:
- Не переносите бизнес-логику в шаблоны. Условие, проверяющее права или рассчитывающее сумму, должно находиться в коде; шаблон отвечает только за представление.
- Используйте
StrictUndefined. Молчаливая подстановка пустой строки — источник конфигураций, которые «почти работают». - Следите за пробелами.
trim_blocksиlstrip_blocksубирают лишние переводы строк после блоков; без них результат содержит пустые строки, что критично для форматов вроде INI и YAML. - Проверяйте результат. Сгенерированный YAML или JSON стоит разобрать парсером до применения; синтаксическая ошибка в конфигурации обнаружится слишком поздно.
- Не экранируйте вручную при включённом автоэкранировании. Двойное экранирование даёт видимые пользователю артефакты вроде
&amp;. - Тестируйте шаблоны отдельно от кода. Шаблон — это программа, поэтому он требует проверки: рендеринг на эталонных данных, проверка обязательных переменных, проверка формата результата. Один тест на каждый шаблон стоит дешевле, чем разбор сломанной конфигурации на сервере.
- Держите данные и представление раздельно. Подготовка значений (форматирование дат, округление сумм, выбор формулировки) выполняется в коде, а шаблон получает готовые к выводу значения. Иначе логика расползается по шаблонам и перестаёт покрываться тестами.
- Избегайте вложенных условий глубже двух уровней. Сложная разметка читается хуже кода; такие места разбивают на макросы или переносят вычисление флага в код.
- Фиксируйте версию Jinja2 в зависимостях. Поведение фильтров и обработки неопределённых значений менялось между версиями, поэтому воспроизводимость сборки зависит от закреплённой версии библиотеки.
Практический сценарий: сгенерированная конфигурация сломала запуск сервиса
Контекст: конфигурация сервиса генерировалась из шаблона при развёртывании. После добавления нового окружения сервис перестал запускаться с ошибкой разбора файла.
- Диагностика. В сгенерированном файле параметр
workersоказался пустым: в значениях окружения не было ключаworkers, а Jinja2 по умолчанию подставила пустую строку. - Причина 1. Использовался режим
undefinedпо умолчанию, поэтому отсутствие переменной не вызывало ошибку. - Причина 2. Результат генерации не проверялся: файл сразу копировался в рабочую директорию.
- Действия. (а) Включили
StrictUndefined, чтобы отсутствие переменной прерывало генерацию; (б) добавили значения по умолчанию через фильтрdefaultтам, где они действительно уместны; (в) в пайплайн добавили шаг валидации сгенерированного файла парсером; (г) для обязательных параметров ввели проверку на уровне схемы значений — см. JSON Schema. - Результат. Ошибка теперь обнаруживается на этапе генерации с указанием имени переменной, а не при запуске сервиса на сервере.
Вывод: шаблонизатор удобен именно тем, что допускает неполные данные. В конфигурациях эта гибкость вредна: явная ошибка на этапе генерации дешевле, чем неработающий сервис.
FAQ
Можно ли использовать Jinja2 для генерации YAML?
Да, это распространённая практика, но с оговоркой: результат нужно проверять парсером YAML. Отступы и специальные символы в значениях легко ломают структуру, поэтому строковые значения лучше выводить через фильтр tojson или quote.
Чем Jinja2 отличается от f-строк? F-строки работают внутри кода и не поддерживают условия, циклы и наследование в читаемом виде. Jinja2 хранит шаблон отдельно, что позволяет менять представление без изменения кода и переиспользовать фрагменты.
Нужна ли песочница, если шаблоны пишут разработчики? Нет, если шаблоны поставляются вместе с кодом. Песочница нужна, когда текст шаблона задаёт пользователь или внешний сервис: без неё шаблон может выполнить произвольный код.
Как отлаживать шаблон?
Полезны режим отладки в окружении, вывод промежуточных значений через {{ var | tojson }} и проверка результата в отдельном файле. Для сложных шаблонов помогает разбиение на макросы, которые можно тестировать по отдельности.
Источники: Jinja2 docs (API, sandbox), Ansible docs (templating) — ссылки в шапке.