PhD*_*PhD 9 python yaml jinja2 python-2.7
我有一些预处理与一些现有的.yml文件 - 但是,其中一些嵌入了Jinja模板语法:
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja.variable }}
- val1
- val2
Run Code Online (Sandbox Code Playgroud)
我想在这个文件中读取,并添加val3下myArray这样:
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja.variable }}
- val1
- val2
- val 3
Run Code Online (Sandbox Code Playgroud)
我试着手动写出jinja模板,但是他们用单引号写了: '{{ jinja.variable }}'
对于我来说,阅读这些.yml文件并修改它们的推荐方法是什么,尽管有预先存在的Jinja语法?我想向这些文件添加信息,保持其他所有相同.
我在Python 2.7+上使用PyYAML尝试了上述内容
此答案中的解决方案已使用插件机制合并到ruamel.yaml中.在这篇文章的底部有关于如何使用它的快速和脏的说明.
更新包含jinja2"code"的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 <<{
Run Code Online (Sandbox Code Playgroud)
以{%#YLL 开头的行不包含YAML,因此我们将这些作为注释(假设注释在往返时保留,见下文).由于YAML标量不能在{没有引用的情况下开始,我们将更{{改为<{.这可以通过调用以下代码完成sanitize()(它也存储使用的模式,反之在sanitize.reverse(使用存储的模式)完成.
保存您的YAML代码(块样式等)最好使用ruamel.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')
Run Code Online (Sandbox Code Playgroud)
update_one()使用Python 2.7 打印(指定要写入文件的第二个参数):
# 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 <<{
Run Code Online (Sandbox Code Playgroud)
如果既不是#{也不<{在任何原始输入中,则可以使用简单的单行函数完成清理和恢复(请参阅此文章的此版本),然后您不需要该类Sanitize
您的示例缩进了一个位置(键B)以及两个位置(序列元素),ruamel.yaml没有对输出缩进的精确控制(我不知道任何YAML解析器).缩进(默认为2)应用于两个YAML映射关于序列元素(测量到元素的开头,而不是短划线).这对重新阅读YAML没有影响,也发生在其他两个回答者的输出上(没有他们指出这个变化).
另请注意,这YAML().load()是安全的(即不加载任意潜在的恶意对象),而yaml.load()其他答案中使用的肯定是不安全的,它在文档中也是如此,甚至在YAML上的WikiPedia文章中也有提及.如果使用yaml.load(),则必须检查每个输入文件,以确保没有标记的对象可能导致光盘被擦除(或更糟).
如果您需要反复更新你的文件,并有过的Jinja2模板控制,它可能是更好的改变Jinja2的模式一次,不能回复他们,然后指定相应的block_start_string,variable_start_string(可能block_end_string和variable_end_string)的jinja2.FileSystemLoader添加加载到jinja2.Environment.
如果以上似乎很复杂,那么在a virtudalenv做:
pip install ruamel.yaml ruamel.yaml.jinja2
Run Code Online (Sandbox Code Playgroud)
假设您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')
Run Code Online (Sandbox Code Playgroud)
得到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 }}
Run Code Online (Sandbox Code Playgroud)
ruamel.yaml0.15.7实现了一个新的插件机制,ruamel.yaml.jinja2是一个插件,可以为用户透明地重写此答案中的代码.目前,还原的信息附加到YAML()实例,因此请确保yaml = YAML(typ='jinja2')对您处理的每个文件执行此操作(该信息可以附加到顶级data实例,就像YAML注释一样).
在当前格式下,您的文件是 jinja 模板,在渲染后才.yml有效。yaml这是因为 jinja 占位符语法与 yaml 语法冲突,因为大括号 ({和}) 可用于表示 yaml 中的映射。
>>> yaml.load('foo: {{ bar }}')
Traceback (most recent call last):
...
yaml.constructor.ConstructorError: while constructing a mapping
in "<string>", line 1, column 6:
foo: {{ bar }}
^
found unacceptable key (unhashable type: 'dict')
in "<string>", line 1, column 7:
foo: {{ bar }}
Run Code Online (Sandbox Code Playgroud)
解决此问题的一种方法是将 jinja 占位符替换为其他内容,将文件处理为 yaml,然后恢复占位符。
$ cat test.yml
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja_variable }}
- val1
- val2
Run Code Online (Sandbox Code Playgroud)
将文件作为文本文件打开
>>> with open('test.yml') as f:
... text = f.read()
...
>>> print text
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja_variable }}
- val1
- val2
Run Code Online (Sandbox Code Playgroud)
正则表达式r'{{\s*(?P<jinja>[a-zA-Z_][a-zA-Z0-9_]*)\s*}}'将匹配文本中的任何 jinja 占位符;jinja表达式中的命名组捕获变量名称。正则表达式与 Jinja2 用于匹配变量名称的正则表达式相同。
re.sub函数可以使用语法在其替换字符串中引用命名组\g。我们可以使用此功能将 jinja 语法替换为与 yaml 语法不冲突且尚未出现在您正在处理的文件中的语法。例如替换{{ ... }}为<< ... >>.
>>> import re
>>> yml_text = re.sub(r'{{\s*(?P<jinja>[a-zA-Z_][a-zA-Z0-9_]*)\s*}}', '<<\g<jinja>>>', text)
>>> print yml_text
A:
B:
- ip: 1.2.3.4
- myArray:
- <<jinja_variable>>
- val1
- val2
Run Code Online (Sandbox Code Playgroud)
现在将文本加载为 yaml:
>>> yml = yaml.load(yml_text)
>>> yml
{'A': {'B': [{'ip': '1.2.3.4'}, {'myArray': ['<<jinja_variable>>', 'val1', 'val2']}]}}
Run Code Online (Sandbox Code Playgroud)
添加新值:
>>> yml['A']['B'][1]['myArray'].append('val3')
>>> yml
{'A': {'B': [{'ip': '1.2.3.4'}, {'myArray': ['<<jinja_variable>>', 'val1', 'val2', 'val3']}]}}
Run Code Online (Sandbox Code Playgroud)
序列化回 yaml 字符串:
>>> new_text = yaml.dump(yml, default_flow_style=False)
>>> print new_text
A:
B:
- ip: 1.2.3.4
- myArray:
- <<jinja_variable>>
- val1
- val2
- val3
Run Code Online (Sandbox Code Playgroud)
现在恢复 jinja 语法。
>>> new_yml = re.sub(r'<<(?P<placeholder>[a-zA-Z_][a-zA-Z0-9_]*)>>', '{{ \g<placeholder> }}', new_text)
>>> print new_yml
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja_variable }}
- val1
- val2
- val3
Run Code Online (Sandbox Code Playgroud)
并将 yaml 写入磁盘。
>>> with open('test.yml', 'w') as f:
... f.write(new_yml)
...
$cat test.yml
A:
B:
- ip: 1.2.3.4
- myArray:
- {{ jinja_variable }}
- val1
- val2
- val3
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1170 次 |
| 最近记录: |