我对一些现有的 .yml 文件进行了一些预处理 - 但是,其中一些嵌入了 Jinja 模板语法:
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja.variable }}
- val1
- val2
我想读入这个文件,并添加
val3
下 myArray
像这样:A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja.variable }}
- val1
- val2
- val 3
我尝试手动写出 jinja 模板,但它们被单引号包围:
'{{ jinja.variable }}'
尽管使用预先存在的 Jinja 语法,我推荐的读取此类 .yml 文件并对其进行修改的方法是什么?我想向这些文件添加信息,保持其他所有内容相同。
我在 Python 2.7+ 上使用 PyYAML 尝试了上述操作
最佳答案
此答案中的解决方案已使用插件机制合并到 ruamel.yaml 中。在这篇文章的底部有关于如何使用它的快速而肮脏的说明。
更新包含 jinja2“代码”的 YAML 文件包含三个方面:
让我们首先通过添加 jinja2 变量定义和 for 循环并添加一些注释(
input.yaml
)使您的示例更加真实:# trying to update
{% set xyz = "123" }
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja.variable }}
- val1
- val2 # add a value after this one
{% for d in data %}
- phone: {{ d.phone }}
name: {{ d.name }}
{% endfor %}
- {{ xyz }}
# #% or ##% should not be in the file and neither <{ or <<{
以
{%
开头的行不包含 YAML,因此我们会将它们放入注释中(假设在往返过程中保留注释,请参见下文)。由于 YAML 标量不能以 {
开头如果没有被引用,我们将更改 {{
至 <{
.这是通过调用 sanitize()
在以下代码中完成的(它还存储使用的模式,相反在 sanitize.reverse
中完成(使用存储的模式)。最好使用
ruamel.yaml
来保存您的 YAML 代码(块样式等)。 (免责声明:我是那个包的作者),这样你就不必担心输入中的流样式元素会像相当粗糙的 default_flow_style=False
那样被破坏成块样式。其他答案使用的。 ruamel.yaml
还保留注释,包括最初在文件中的注释,以及临时插入以“注释掉”以 %{
开头的 jinja2 结构的注释。 .结果代码:
import sys
from ruamel.yaml import YAML
yaml = YAML()
class Sanitize:
"""analyse, change and revert YAML/jinja2 mixture to/from valid YAML"""
def __init__(self):
self.accacc = None
self.accper = None
def __call__(self, s):
len = 1
for len in range(1, 10):
pat = '<' * len + '{'
if pat not in s:
self.accacc = pat
break
else:
raise NotImplementedError('could not find substitute pattern '+pat)
len = 1
for len in range(1, 10):
pat = '#' * len + '%'
if pat not in s:
self.accper = pat
break
else:
raise NotImplementedError('could not find substitute pattern '+pat)
return s.replace('{{', self.accacc).replace('{%', self.accper)
def revert(self, s):
return s.replace(self.accacc, '{{').replace(self.accper, '{%')
def update_one(file_name, out_file_name=None):
sanitize = Sanitize()
with open(file_name) as fp:
data = yaml.load(sanitize(fp.read()))
myArray = data['A']['B'][1]['myArray']
pos = myArray.index('val2')
myArray.insert(pos+1, 'val 3')
if out_file_name is None:
yaml.dump(data, sys.stdout, transform=sanitize.revert)
else:
with open(out_file_name, 'w') as fp:
yaml.dump(data, out, transform=sanitize.revert)
update_one('input.yaml')
使用 Python 2.7 打印(为
update_one()
指定第二个参数以写入文件):# trying to update
{% set xyz = "123" }
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja.variable }}
- val1
- val2 # add a value after this one
- val 3
{% for d in data %}
- phone: {{ d.phone }}
name: {{ d.name }}
{% endfor %}
- {{ xyz }}
# #% or ##% should not be in the file and neither <{ or <<{
如果两者都不是
#{
也不是 <{
位于任何原始输入中,然后可以使用简单的单行函数完成清理和恢复(请参阅 this versions of this post ),然后您不需要类 Sanitize
您的示例缩进了一个位置(键
B
)以及两个位置(序列元素), ruamel.yaml
对输出缩进没有很好的控制(我不知道有任何 YAML 解析器可以)。缩进(默认为 2)适用于序列元素的两个 YAML 映射(测量到元素的开头,而不是破折号)。这对重新阅读 YAML 没有影响,并且也发生在其他两个回答者的输出上(他们没有指出这一变化)。另请注意
YAML().load()
是安全的(即不加载任意潜在的恶意对象),而 yaml.load()
如其他答案中所用 绝对不安全 ,它在文档中是这么说的,甚至在 WikiPedia article on YAML 中也提到了.如果您使用 yaml.load()
,您必须检查每个输入文件,以确保没有可能导致您的光盘被删除(或更糟)的标记对象。如果您需要重复更新文件,并控制 jinja2 模板,最好更改一次 jinja2 的模式而不是还原它们,然后指定适当的
block_start_string
, variable_start_string
(以及可能的 block_end_string
和 variable_end_string
)到 jinja2.FileSystemLoader
作为加载程序添加到 jinja2.Environment
.如果以上看起来很复杂,那么在 virtualenv 中执行以下操作:
pip install ruamel.yaml ruamel.yaml.jinja2
假设你有
input.yaml
从你可以运行之前:import os
from ruamel.yaml import YAML
yaml = YAML(typ='jinja2')
with open('input.yaml') as fp:
data = yaml.load(fp)
myArray = data['A']['B'][1]['myArray']
pos = myArray.index('val2')
myArray.insert(pos+1, 'val 3')
with open('output.yaml', 'w') as fp:
yaml.dump(data, fp)
os.system('diff -u input.yaml output.yaml')
获取
diff
输出:--- input.yaml 2017-06-14 23:10:46.144710495 +0200
+++ output.yaml 2017-06-14 23:11:21.627742055 +0200
@@ -8,6 +8,7 @@
- {{ jinja.variable }}
- val1
- val2 # add a value after this one
+ - val 3
{% for d in data %}
- phone: {{ d.phone }}
name: {{ d.name }}
ruamel.yaml
0.15.7 实现了新的插件机制和 ruamel.yaml.jinja2
是一个插件,它为用户透明地重新包装了这个答案中的代码。目前回滚信息附在YAML()
例如,请确保您这样做 yaml = YAML(typ='jinja2')
对于您处理的每个文件(该信息可以附加到顶级 data
实例,就像 YAML 注释一样)。
关于python - 如何使用 Python 更新 .yml 文件,忽略预先存在的 Jinja 语法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44422304/