如何从Jinja2模板中获取所有未定义的变量?

时间:2017-10-07 11:53:51

标签: python flask jinja2

我试图从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)

4 个答案:

答案 0 :(得分:5)

我使用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

答案 1 :(得分:1)

Codablefind_undeclared_variables配合使用,可以正确地引发一个异常,其中提到所有丢失的变量:

DebugUndefined

如果您希望使用日志记录,则可以使用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}') 的内容将异常引发引发替换为您自己的日志记录调用。

PS:我是Jinja的新手,但我很惊讶这不是undefined的默认行为。我不知道为什么作者/维护者会认为默认情况下默默忽略缺少变量是一件好事...

答案 2 :(得分:1)

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

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()

这样的想法是,每次遇到未定义的变量时,将标记名称插入带有绒毛值的上下文中,然后再次尝试进行渲染,直到所有变量都已知并归类为止。

答案 3 :(得分:0)

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


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__();