Jinja: перезаписываемый автоэскейп

Созданный на 15 окт. 2015  ·  11Комментарии  ·  Источник: pallets/jinja

На работе мы используем Jinja для рендеринга шаблонов как YAML. Мы хотели бы настроить все расширения {{ variable }} чтобы сделать что-то разумное для значений Python, таких как None (что в YAML равно null ) и пустая строка (которая кодируется как '' в YAML). Похоже, что для этого идеально подошел бы механизм автоэскейпинга Jinja. К сожалению, я не вижу способа настроить функцию автоэскейпинга, чтобы не было автоэскейпа в HTML. Вместо этого мне пришлось отредактировать Markup и escape в jinja2.runtime . Было бы неплохо, если бы для этого существовал официально разрешенный метод, например, переопределив что-то в окружении.

Все 11 Комментарий

Не уверен, что автоэскейп - правильный способ сделать это.

Я лучше воспользуюсь фильтром. tojson кажется хорошим кандидатом, поскольку yaml - это надмножество json.


Кроме того: есть ли причина, по которой вы строите YAML-шаблоны на основе строк? Сериализация словаря Python в виде YAML кажется намного чище. Единственный случай, когда этого может быть недостаточно, - это когда вы хотите включить комментарии. Но в этом случае вы можете использовать движок YAML, который обрабатывает неразрушающее чтение / запись, поэтому вы просто загружаете шаблон YAML с вашими комментариями и пустыми элементами, а затем обновляете данные и повторно сериализуете их как YAML.

Смысл использования автоэкранирования в том, что я не хочу накладывать фильтр на каждое расширение отдельной переменной.

Наш вариант использования - писать файлы конфигурации для наших микросервисов. У нас есть «шаблон», включенный в каждую службу, и мы хотим заполнить его конфигурацией среды, специфичной для этого шаблона. Я согласен с тем, что использование YAML напрямую является более чистым, но мы не смогли найти общий «движок», который знает, как заменять переменные, местонахождение которых ему еще не известно. (Существуют псевдонимы и ссылки YAML, но на самом деле они не подходят для этого - они больше для сериализации циклических структур.) Есть способы, которые я могу придумать, чтобы заставить это работать с помощью YAML, но они вроде тоже хакерский. Эта настройка также дает больше контроля над видами замен, которые мы можем выполнять, хотя на практике мы не используем это ни для чего.

Вы можете использовать плагин jinja, чтобы применить фильтр ко всем переменным. Это не так уж сложно, если вам не нужно использовать блоки i18n. Возможно, вы сможете почерпнуть некоторые идеи из того, что я написал некоторое время назад: https://github.com/indico/indico/blob/master/indico/web/flask/templating.py#L187

Это очень полезно, спасибо! Мне кажется, что автоэскейп по-прежнему подходит для решения этой проблемы, потому что вы можете, например, пометить переменные как |safe . Но я признаю, что концептуально это не совсем похоже на автоматическое экранирование, потому что мы не просто гарантируем, что переменные являются действительными YAML, но и форматируем их определенным образом. Напишу расширение.

@glasserc удалось ли вам использовать это расширение? У меня похожая проблема, не могли бы вы указать на решение / поделиться некоторыми идеями?

Первой частью моего решения было расширение, которое пропускает все вызовы переменных через фильтр. Это минимум, который мне нужен, чтобы все заработало.

import jinja2.ext
from jinja2.lexer import Token

class YAMLEverythingExtension(jinja2.ext.Extension):
    """
    Insert a `|yaml` filter at the end of every variable substitution.

    This will ensure that all injected values are converted to YAML.
    """
    def filter_stream(self, stream):
        # This is based on https://github.com/indico/indico/blob/master/indico/web/flask/templating.py.
        for token in stream:
            if token.type == 'variable_end':
                yield Token(token.lineno, 'pipe', '|')
                yield Token(token.lineno, 'name', 'yaml')
            yield token

Это может быть умнее, например, он может попытаться пропустить вставку фильтра, если он явно видит фильтр safe или yaml в блоке переменных. Но у меня никогда не было на это времени.

Вторая часть - это сам фильтр. Простое использование yaml.dump было недостаточно изощренным, поэтому мне пришлось немного покопаться во внутренностях yaml.

import cStringIO
import yaml

def yaml_filter(val):
    """Serialize some value in isolation, not as part of any document.

    We can't just use yaml.dump because that outputs an entire document, including newlines, which isn't helpful for
    inserting into a YAML document."""
    if isinstance(val, jinja2.Undefined):
        val._fail_with_undefined_error()
    stream = cStringIO.StringIO()
    dumper = yaml.dumper.Dumper(stream)
    dumper.open()
    node = dumper.represent_data(val)
    dumper.serialize(node)
    # The serialized node tends to have a \n at the end.  The template might not
    # want a \n inserted here, e.g. if two variables are on the same line, so
    # strip.
    return stream.getvalue().strip()

Это связывает все вместе, включая обеспечение доступности фильтра данного имени в среде:

from jinja2 import loaders
from jinja2 import Environment, StrictUndefined

def get_environment():
    """Create a standard Jinja environment that has everything in it.
    """
    jinja_env = Environment(extensions=(YAMLEverythingExtension,),
                            # some other options that we use at work
                            loader=loaders.FileSystemLoader(['.', '/']),
                            undefined=StrictUndefined)
    jinja_env.filters["yaml"] = yaml_filter
    return jinja_env

Это по-прежнему отлично подходит для экранирования шаблонов, отличных от HTML. Я использую Jinja2 для создания JSON и хочу использовать экранирование JSON вместо экранирования HTML.

Использование шаблонов Jinja или любого другого языка шаблонов на основе строк или строковых операций вообще для генерации JSON просто неправильно. Я понимаю, почему вы сделали это для YAML, поскольку он также предназначен для удобства человека, но JSON должен быть сгенерирован, например, из словаря Python, а не из шаблона Jinja.

(при этом мне любопытно, почему вы хотите это сделать и что вы пытаетесь сделать: p)

Думаю, в этом есть смысл. :-) Закрою # 571.

Возможно, JSON не годится для генерации Jinja, но меня очень интересует такая функция, потому что я создаю LaTeX с помощью jinja. Я добился большей части пути, изменив block_start_string и block_end_string и другие в Environment . На данный момент я установил autoescape = False , но в идеале я хотел бы передать что-то, чтобы самому выполнить экранирование (возможно, как регулярное выражение).

Это также относится к генерации открытого текста и уценки. До сих пор Jinja был отличным инструментом для создания шаблонов документов в формате, отличном от HTML.

Я искал использование jinja для генерации SQL-запросов, потому что использование str.format() может привести к беспорядку, если вам нужно несколько частей условного запроса.

Есть уже библиотек , чтобы сделать это, а именно дзиндзя-Vanish , что позволяет избежать пользовательских функций и jinjasql .
Первый должен отключить постоянную оценку при компиляции, что кажется немного плохим. Другой
использует aproach фильтра, чтобы добавить специальный фильтр ко всем выражениям переменных, что нормально, но также кажется неправильным.

Я не вдавался в подробности, но, похоже, реализовать это не так уж и сложно. Можно ли это рассмотреть или это выходит за рамки библиотеки?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги