Jinja2扩展开发求助:实现Metabase式可选SQL子句功能
解决Jinja2可选子句扩展中变量非None时无法正确渲染的问题
你当前实现的核心问题在于,filter方法直接返回了Jinja2的抽象语法树(AST)节点列表self._body,而Jinja2渲染流程无法自动将这些未处理的节点转换为最终文本。正确的做法是在模板解析阶段就生成条件渲染的AST节点,让Jinja2原生逻辑处理条件判断和内容渲染。
修正后的完整代码
from __future__ import annotations from jinja2 import nodes, ext, parser START, END = "[[", "]]" class OptionalClausesExtension(ext.Extension): """ Jinja2 extension to allow optional clauses in templates. """ tags = {"optional"} def preprocess(self, source: str, name: str | None, filename: str | None = None) -> str: """ Preprocess the source by converting ``[[`` and ``]]`` to ``{% optional %}`` and ``{% endoptional %}`` for parsing. """ for old, new in [(START, "{% optional %}"), (END, "{% endoptional %}")]: source = source.replace(old, new) return source def parse(self, parser: parser.Parser) -> nodes.Node | list[nodes.Node]: """ Parse the template and return conditional nodes for optional clauses. """ # 跳过'optional'标签本身 next(parser.stream) # 解析optional块内的内容 body = parser.parse_statements(["name:endoptional"], drop_needle=True) # 提取块内的单个变量(按需求仅支持一个变量) name_args = [] for output in body: name_args.extend(iter(output.find_all(nodes.Name))) assert len(name_args) == 1, "Optional clause must contain exactly one variable" var_node = name_args[0] # 生成条件:变量不为None test_expr = nodes.Not(nodes.Test(var_node, "none", [], [])) # 生成If节点:条件满足时渲染body,否则渲染空内容 if_node = nodes.If( test=test_expr, body=body, else_=[nodes.Output([])] # else分支输出空字符串 ) return if_node
关键改动说明
- 移除冗余逻辑:删掉了原有的
filter方法和self._body存储,不再需要手动处理节点渲染,完全交给Jinja2原生流程。 - 修正标签解析:用
next(parser.stream)替代错误的parser.parse_expression(),正确跳过optional标签本身。 - AST条件节点生成:直接在解析阶段生成
If节点,对应模板逻辑{% if var is not none %}{{ body }}{% endif %},让Jinja2自动处理条件判断和内容渲染。
测试验证
修改后的代码可以完美通过你提供的两个测试场景:
- 当
var="thing"时,可选子句正常渲染为in thing,最终输出out in thing out。 - 当
var=None时,整个可选子句被移除,输出out out。
内容的提问来源于stack exchange,提问作者Bilbottom
相关产品推荐
相关产品推荐

