在 Python 中加载用 Jinja2 嵌套的 YAML
Posted
技术标签:
【中文标题】在 Python 中加载用 Jinja2 嵌套的 YAML【英文标题】:Load YAML nested with Jinja2 in Python 【发布时间】:2015-11-05 04:07:14 【问题描述】:我有一个 YAML 文件 (all.yaml
),如下所示:
...
var1: val1
var2: val2
var3: var1-var2.txt
...
如果我像这样在 Python 中加载它:
import yaml
f = open('all.yaml')
dataMap = yaml.safe_load(f)
f.close()
print(dataMap["var3"])
输出是var1-var2.txt
而不是val1-val2.txt
。
是否可以用值替换嵌套的变量?
我尝试加载它:
import jinja2
templateLoader = jinja2.FileSystemLoader( searchpath="/path/to/dir" )
templateEnv = jinja2.Environment( loader=templateLoader )
TEMPLATE_FILE = "all.yaml"
template = templateEnv.get_template( TEMPLATE_FILE )
不再抛出异常,现在我被卡住了,不得不研究如何进行。
【问题讨论】:
您显然需要在某些时候使用 Jinja2。到目前为止,您尝试过什么? 我尝试用 import jinja2 templateLoader = jinja2.FileSystemLoader( searchpath="/" ) templateEnv = jinja2.Environment( loader=templateLoader ) TEMPLATE_FILE = "all.yaml" template = templateEnv.get_template ( TEMPLATE_FILE ) 但这会抛出 TemplateNotFound-Exception 最好更新您的答案,而不是试图将代码示例留在 cmets 中。TemplateNotFound
异常应该比较容易解决;您的文件 all.yml
不太可能位于 /
,但这是您告诉 Jinja2 查找的位置。
是的,这就是问题所在。我更新了问题。谢谢
为什么这个问题被否决了?
【参考方案1】:
我不相信你可以使用:
yaml.load
或
yaml.safe_load
在包含jinja2
变量作为值的文件上。 variable
将尝试被 yaml 解释为字典。
【讨论】:
注意: 这可以通过引用占位符来避免,以便 YAML 将其解释为纯标量值(字符串)。将variable
更改为"variable"
。【参考方案2】:
首先定义一个Undefined
类并加载yaml 以获取已知值。然后再次加载它并使用已知值进行渲染。
#!/usr/bin/env python
import yaml
from jinja2 import Template, Undefined
str1 = '''var1: val1
var2: val2
var3: var1-var2.txt
'''
class NullUndefined(Undefined):
def __getattr__(self, key):
return ''
t = Template(str1, undefined=NullUndefined)
c = yaml.safe_load(t.render())
print t.render(c)
运行它:
$ ./test.py
var1: val1
var2: val2
var3: val1-val2.txt
【讨论】:
【参考方案3】:YAML 规范中没有标量部分的替换/替换。
您想在该级别上执行的任何操作都必须在您的应用程序中完成。对我和 YAML 来说,var1
只是一个嵌套映射。 var1
是 var1: null: null
的缩写。之后,-
就不允许了。
但是,您的帖子存在多个问题:
您使用的 PyYAML 仅支持旧的 (2005) YAML 1.1。因此,您不能像在 YAML 1.2 中那样使用显式文档开头 (---
) 来拥有多个文档(即以 ...
结尾)
如果您只在文件中使用var1
,PyYAML 无法加载它,因为它将 YAML 映射加载为 Python 字典,而 Python 不允许字典的可变键。就像你尝试在 Python 中得到 TypeError
一样:dict(var1=None): None
因此,您至少应该将输入文件 all.yaml
更改为:
---
var1: val1
var2: val2
var3: 'var1-var2.txt'
...
让它在 YAML 中加载。
你必须加载这个文件两次:
一次由 PyYAML 获取可用于呈现模板的值 曾作为 jinja2 的模板在渲染模板后,您在 PyYAML 中再次加载该(字符串)并且您拥有所需的值。
鉴于上面在当前目录和这个程序中指定的更正的all.yaml
:
import yaml
import jinja2
YAML_FILE = 'all.yaml'
with open(YAML_FILE) as fp:
dataMap = yaml.safe_load(fp)
env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath='.'))
template = env.get_template(YAML_FILE)
data = yaml.safe_load(template.render(**dataMap))
print(data["var3"])
将打印您想要的内容:
val1-val2.txt
【讨论】:
您绝对可以将 YAML 模块和 Jinja2 模块结合起来做一些像 OP 要求的事情。 Ansible 是对 YAML 值执行 Jinja2 模板处理的工具的一个示例。可以肯定的是,您不能单独使用yaml
模块执行此操作,但我认为这不是 OP 所要求的。
@larsks 你是对的。另一个示例工具是cookiecutter
当然,你总是可以用 is 有效的 YAML 替换无效的 YAML,然后加载它。然而,OP 声明他的第一个示例 (all.yaml
) 是 YAML,但事实并非如此。您首先必须扩展该 jinja2 模板,然后解析扩展的结果,希望届时它被 YAML 解析器理解的内容替换。
这是一个完全准确的说法。恰好看起来与 YAML 语法相似的未处理 Jinja 模板与格式正确的 YAML 不同。经处理的 Jinja 模板决不能保证生成格式良好的 YAML。一个挑战是将这种情况传达给尚未使用 YAML(更不用说为其开发模块)的潜在 YAML 用户,同时尽量减少他们感到困惑或被吓跑的机会。【参考方案4】:
这是一种可能的解决方案:
-
使用
yaml
模块解析您的 YAML 文档
遍历 YAML 文档中的键,将每个值视为 Jinja2 模板,将 YAML 文档的键作为参数传递到该模板。
例如:
import yaml
from jinja2 import Template
with open('sample.yml') as fd:
data = yaml.load(fd)
for k, v in data.items():
t = Template(v)
data[k] = t.render(**data)
print yaml.safe_dump(data, default_flow_style=False)
这在您的特定示例中可以正常工作,但不会对嵌套数据结构等有用的东西(事实上,它可能会爆炸)。
【讨论】:
当 YAML 文件包含类似 val: 1.5 的内容时,它会抛出 AttributeError: 'float' object has no attribute 'iter_fields' 哦,当然。这是一个例子,不是一个强大的解决方案! :) 嘿,-1 人,有什么可以改进的吗?我知道这是一个较旧的,但我很乐意修复它。干杯!以上是关于在 Python 中加载用 Jinja2 嵌套的 YAML的主要内容,如果未能解决你的问题,请参考以下文章