在工作中,我们使用 Jinja 将模板呈现为 YAML。 我们想自定义所有{{ variable }}
扩展来对 Python 值做一些有意义的事情,比如None
(在 YAML 中是null
)和空字符串(它被编码为''
在 YAML 中)。 看起来 Jinja 自动转义机制将是完美的。 不幸的是,我没有看到任何方法可以将自动转义功能配置为不自动转义为 HTML。 相反,我不得不在jinja2.runtime
对Markup
和escape
进行猴子补丁。 如果有官方认可的方法来执行此操作,例如通过覆盖环境中的某些内容,那就太好了。
不确定自动转义是否是执行此操作的正确方法。
我宁愿使用过滤器。 tojson
似乎是一个不错的选择,因为 yaml 是 json, 的超集。
除此之外:您为什么要使用基于字符串的模板来构建 YAML? 将 Python 字典序列化为 YAML 似乎更清晰。 我可以看到这可能不够的唯一情况是当您想要包含评论时。 但在这种情况下,您可以使用处理非破坏性读/写的 YAML 引擎,因此您只需加载带有注释和空项目的 YAML 模板,然后更新数据并将其重新序列化为 YAML。
使用自动转义的要点是我不想在每个变量扩展上都放置过滤器。
我们的用例是为我们的微服务编写配置文件。 我们有一个包含在每个服务中的“模板”,我们希望用特定于该模板的每个环境配置来填充它。 我同意直接使用 YAML 更干净,但我们找不到一个通用的“引擎”,它知道如何替换它不知道位置的变量。 (有 YAML 别名和引用,但它们并不真正适合于此——它们更多地用于序列化循环结构。)我可以想到使用 YAML 使其工作的方法,但它们是也有点hacky。 这种设置还可以更好地控制我们可以执行的替换类型,尽管在实践中我们并没有真正将其用于任何事情。
您可以使用 jinja 插件将过滤器应用于所有变量。 只要您不必使用 i18n 块,这并不太难。 也许你可以从我前段时间写的一些东西中得到一些想法: https :
这很有帮助,谢谢! 我觉得自动转义仍然是解决这个问题的好方法,因为您可以将变量标记为|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 dict 而不是从 Jinja 模板生成。
(也就是说,我很好奇您为什么要这样做以及您要做什么:p)
我想这是有道理的。 :-) 我会关闭#571。
也许Jinja 生成JSON 不太好,但是我对这种功能很感兴趣,因为我正在使用jinja 生成LaTeX。 我已经通过修改得到多数的方式有block_start_string
和block_end_string
在和其他Environment
。 现在,我已经设置了autoescape = False
,但理想情况下我想传递一些东西来做自己的转义(可能像一个正则表达式)。
这也适用于生成纯文本和降价。 迄今为止,Jinja 一直是用于模板化非 html 文档的出色工具。
我正在研究使用 jinja 来生成 SQL 查询,因为如果您需要多个条件查询部分,使用str.format()
会变得混乱。
已经有库可以做到这一点,即jinja-vanish允许自定义转义函数和jinjasql 。
第一个必须在编译时禁用常量评估,这似乎有点糟糕。 另一个
使用过滤器方法为所有变量表达式添加一个特殊过滤器,这可以但也感觉不对。
我没有深入研究它,但实施它似乎并不难。 可以考虑这样做还是超出了图书馆的范围?