python - 如何使用 Python 更新 .yml 文件,忽略预先存在的 Jinja 语法?

标签 python python-2.7 yaml jinja2

我对一些现有的 .yml 文件进行了一些预处理 - 但是,其中一些嵌入了 Jinja 模板语法:

A:
 B:
 - ip: 1.2.3.4
 - myArray:
   - {{ jinja.variable }}
   - val1
   - val2

我想读入这个文件,并添加 val3myArray像这样:
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 代码被 YAML 解析器接受
  • 确保可接受的可以逆转(即更改应该是唯一的,所以只有它们被逆转)
  • 保留 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_stringvariable_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/

    相关文章:

    go - 解析 yaml 返回空对象

    xml - YAML 与 XML 的比较

    python - 如何在python中将float值的精度提高到10以上?

    python - Scrapy 飞溅^ AttributeError : 'module' object has no attribute 'Spider'

    python - 我在哪里可以学习这个 Python 括号运算符?

    python - 检查 python 线程是否抛出异常

    python-2.7 - 如何在Python中将所有对象添加到空列表中?

    python - 使用 Python 2.7 将变量传递到 MySQL

    perl - 使用 Perl 就地编辑 YAML 文档

    python - 提取双引号之间的字符串