【问题标题】:How to get ALL undefined variables from a Jinja2 template?如何从 Jinja2 模板中获取所有未定义的变量?
【发布时间】:2018-03-19 02:12:21
【问题描述】:

我正在尝试从 Jinja2 模板中获取所有未定义的变量。 假设我有一个像下面这样的模板。

tmpstr = """
{% for row in csv %}
sample {{row.field1}} stuff {{row.field2}} morestuff {{row.field3}}
{% endfor %}
"""

输入字典如下

cxt = {'csv': [
    {'field3': 1234, 'field4': 12314},
    {'field3': 2222, 'field4': 1213}
]}

这是我尝试渲染它的方式。

env = Environment(undefined=Undefined)
tmp = env.from_string(tmpstr)
tmpsrc = tmp.render(cxt)
print(tmpsrc)

模板期望变量 field1field2field3 存在。但是 field1field2 不存在。我的目标是找到所有缺失的变量。

Jinja2 默默地忽略缺失的变量。因此我尝试添加StrictUndefined 选项:

errs = []
try:
    env = Environment(undefined=StrictUndefined)
    tmp = env.from_string(tmpstr)
    tmpsrc = tmp.render(cxt)
except Exception as e:
    errs.append(str(e))
print(errs)

然而这一次 jinja2 抱怨 只有第一个 缺失的变量是 field1

因此我尝试了另一个选项,即DebugUndefined。 此选项不会引发异常,并且不会影响模板输出中缺少的变量占位符。因此我无法收集缺失的变量。

您能否建议我如何在 jinja2 模板中获取缺失的变量?

如果有人想尝试一下,这里是可运行的代码:

from jinja2 import BaseLoader,Environment,StrictUndefined,DebugUndefined,Undefined
tmpstr = """
{% for row in csv %}
sample {{row.field1}} stuff {{row.field2}} morestuff {{row.field3}}
{% endfor %}
"""
cxt = {'csv': [
    {'field3': 1234, 'field4': 12314},
    {'field3': 2222, 'field4': 1213}
]}
env = Environment(undefined=Undefined)
tmp = env.from_string(tmpstr)
tmpsrc = tmp.render(cxt)
print('CASE 1: undefined=Undefined')
print(tmpsrc)

errs = []
try:
    env = Environment(undefined=StrictUndefined)
    tmp = env.from_string(tmpstr)
    tmpsrc = tmp.render(cxt)
except Exception as e:
    errs.append(str(e))
print('CASE 2: undefined=StrictUndefined')
print(errs)

errs = []
try:
    env = Environment(undefined=DebugUndefined)
    tmp = env.from_string(tmpstr)
    tmpsrc = tmp.render(cxt)
except Exception as e:
    errs.append(str(e))

print('CASE 3: undefined=DebugUndefined')
print(errs)
print(tmpsrc)

【问题讨论】:

标签: python flask jinja2


【解决方案1】:

我使用jinja2.make_logging_undefined 找到了您问题的解决方案。我和你在同一条船上,一直在寻找答案。大多数答案都指向我使用已解析的模板,但是我不知道如何将上下文放入已解析的模板中。

我终于能够使用make_logging_undefined 完成这项工作。如果要查找所有未定义的变量,请确保仅使用 Undefined 基类而不是 StrictUndefined。使用StrictUndefined 将导致 jinja 在第一次遇到 undefine 时抛出异常。

只是一个免责声明:我不是 python 也不是 jinja 专家,所以代码不是最有效的,也不是最结构化的。但这符合我的目的。这只是 POC 代码。

代码如下:

import jinja2
import logging
from jinja2 import Environment, Undefined
from jinja2.exceptions import UndefinedError

def main():
    templateLoader = jinja2.FileSystemLoader( searchpath="D:\\somelocation\\" )

    logging.basicConfig()
    logger = logging.getLogger('logger')
    LoggingUndefined = jinja2.make_logging_undefined(logger=logger,base=jinja2.Undefined)

    templateEnv = jinja2.Environment( loader=templateLoader, undefined=LoggingUndefined)

    TEMPLATE_FILE = "./example1.jinja"

    template = templateEnv.get_template( TEMPLATE_FILE )

    FAVORITES = [ "chocolates", "lunar eclipses", "rabbits" ]
    # Specify any input variables to the template as a dictionary.
    templateVars = { "title" : "Test Example",
                     "description" : "A simple inquiry of function.",
                     "favorites" : FAVORITES,
                     "whatever" : "1"
                   }    
    # Finally, process the template to produce our final text.
    try:
        outputText = template.render( templateVars )
    except ( UndefinedError) as err:
        print err

if __name__ == '__main__':
    main()

example1.jinja:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8" />

  <title>{{ title }}</title>
  <meta name="description" content="{{ description }}" />
</head>

<body>

<div id="content">
  <p>Greetings visitor!  These are a list of my favorite things:</p>

  <ul>
  {% for item in favorites %}
    <li>{{ item }}</li>

  <li>My favorites: {{ favorites[1] }} </li>
  {% endfor %}
  {{ undefined_var1 }}
  {{ underfined_var2 }}
  </ul>
</div>

</body>
</html>

这是示例输出:

WARNING:logger:Template variable warning: undefined_var1 is undefined
WARNING:logger:Template variable warning: underfined_var2 is undefined

【讨论】:

  • 我终于使用 DebugUndefined 并使用正则表达式 {{(.*?)}} 在渲染模板中查找缺失的变量。但是,我会赞成您的回答,因为它使用了日志记录。谢谢。
  • 感谢您的支持。使用 make_logging_undefined 的输出是示例中所有未定义的变量。这是示例输出:WARNING:logger:Template variable warning: undefined_var1 is undefinedWARNING:logger:Template variable warning: underfined_var2 is undefined
【解决方案2】:

find_undeclared_variablesDebugUndefined 一起使用,您可以正确地引发异常,提及所有 缺少的变量:

import jinja2
from jinja2.meta import find_undeclared_variables

env = jinja2.Environment(undefined=jinja2.DebugUndefined)
template = env.from_string('foo={{ foo }}, bar={{ bar}}, baz={{ baz }}')

# Render template without passing all variables
rendered = template.render(foo=1)

# Check if rendering was done correctly
ast = env.parse(rendered)
undefined = find_undeclared_variables(ast)  # {'bar', 'baz'}
if undefined:
    raise jinja2.UndefinedError(f'The following variables are undefined: {undefined!r}')

如果您更喜欢日志记录,您可以使用 undefined 的内容将异常引发替换为您自己的日志记录调用。

PS:我对 Jinja 比较陌生,但我很惊讶这不是 env.render 的默认行为。我想知道为什么作者/维护者认为默认忽略缺失变量是一件好事......

【讨论】:

  • 如果您在模板中使用字典项并且缺少变量,则解析将失败。这是示例``` import jinja2 from jinja2.meta import find_undeclared_variables env = jinja2.Environment(undefined=jinja2.DebugUndefined) template = env.from_string('foo={{ foo }}, bar={{ bar}}, baz.prop1 ={{ baz.prop1 }} baz.prop2 ={{ baz.prop2 }}') 渲染 = template.render(foo=1,baz={'prop1':'prop1','prop3':' prop2'}) ast = env.parse(rendered) ```
【解决方案3】:

您可以简单地创建自己的“未定义”,例如以编程方式处理未定义变量的列表。这是一个例子:


missing_vars=[]
class CollectingUndefined(jinja2.Undefined):

    def _add_missing_var(self):
        missing_vars.append(self._undefined_name)

    def __iter__(self):
        self._add_missing_var()
        return super().__iter__();

    def __str__(self):
        self._add_missing_var()
        return super().__str__();

    def __len__(self):
        self._add_missing_var()
        return super().__len__();

    def __eq__(self):
        self._add_missing_var()
        return super().__eq__();

    def __ne__(self):
        self._add_missing_var()
        return super().__eq__();

    def __bool__(self):
        self._add_missing_var()
        return super().__e__bool__q__();

    def __hash__(self):
        self._add_missing_var()
        return super().__hash__();

【讨论】:

    【解决方案4】:

    关于您的第一次尝试(在此处转发)

    errs = []
    try:
        env = Environment(undefined=StrictUndefined)
        tmp = env.from_string(tmpstr)
        tmpsrc = tmp.render(cxt)
    except Exception as e:
        errs.append(str(e))
    print(errs)
    

    我认为问题在于 1) 当您应该尝试在脚本中循环时,您正在尝试在模板中循环,并且 2) 您没有在每次异常后更新 cxt。

    我需要使用自定义分隔符对模板做同样的事情(find_undeclared_variables 不起作用)

    我使用了这样的东西:

    def findAllUndefined(target):
        jinja_env = jinja2.Environment(undefined=jinja2.StrictUndefined)
        doc = DocxTemplate(target)
        context = {}
        finished = False
        while finished == False:
            try:
                doc.render(context, jinja_env)
                finished = True
            except Exception as e:
                tag = re.sub(" is undefined", "", str(e)) # extracting tag name from error message
                tag = re.sub("'", "", tag)
                context[str(tag)] = "FOUND"
        return context.keys()
    

    这个想法是每次遇到未定义的变量时,都会将标签名称插入到带有绒毛值的上下文中,然后再次尝试渲染,直到所有变量都已知并被编入目录。

    【讨论】:

      【解决方案5】:

      在我看来,实现这一目标的一个好方法是定义自己的 Undefined 类,类似于 Michael Wyraz's anwer

      class CollectUndefined(object):
          def __init__(self, undefined_cls=Undefined):
              self.undefined_cls = undefined_cls
              self.missing_vars = []
      
          def __call__(self, *args, **kwds):
              undefined = self.undefined_cls(*args, **kwds)
              self.missing_vars.append(undefined._undefined_name)
              return undefined
      
          def assert_no_missing_vars(self):
              if len(self.missing_vars) > 0:
                  raise MissingVariablesError(self.missing_vars)
      
      
      class MissingVariablesError(Exception):
          def __init__(self, missing_vars, *args):
              super().__init__(*args)
              self.missing_vars = missing_vars
      
          def __str__(self):
              return 'Missing variables: {}'.format(self.missing_vars)
      

      那么你可以这样使用它:

      env = Environment(undefined=CollectUndefind())
      tmp = env.from_string(tmpstr)
      tmpsrc = tmp.render(cxt)
      print(env.undefined.missing_vars)
      

      或者使用try-except:

      env = Environment(undefined=CollectUndefind())
      tmp = env.from_string(tmpstr)
      try:
          tmpsrc = tmp.render(cxt)
          env.undefined.assert_no_missing_vars():
      except MissingVariablesError as e:
          print(e.missing_vars)
          
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-05
        • 2018-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多