自动扩展YAML合并的工具?

时间:2017-07-07 10:25:16

标签: python yaml reformatting

我正在寻找一个工具或流程,可以轻松获取包含锚点,别名和合并键的YAML文件,并展开别名并合并为一个平面YAML文件。仍有许多常用的YAML解析不完全支持合并。

我希望能够利用合并来保持干燥,但有些情况下需要将其构建成更详细的“平坦”。 YAML文件,以便它可以被依赖于不完整的YAML解析器的其他工具使用。

示例来源YAML:

default: &DEFAULT
  URL: website.com
  mode: production  
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

development:
  <<: *DEFAULT
  URL: website.local
  mode: dev

test:
  <<: *DEFAULT
  URL: test.website.qa
  mode: test

所需的输出YAML:

default:
  URL: website.com
  mode: production  
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

development:
  URL: website.local
  mode: dev
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

test:
  URL: test.website.qa
  mode: test
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

3 个答案:

答案 0 :(得分:5)

如果您的系统上安装了python,则可以pip install ruamel.yaml.cmd¹然后执行:

yaml merge-expand input.yaml output.yaml

(将output.yaml替换为-以写入stdout)。这实现了合并扩展,保留了关键顺序和注释。

以上实际上是使用ruamel.yaml¹的几行代码 所以如果你有Python(2.7或3.4+)并使用pip install ruamel.yaml安装它并将以下内容保存为expand.py

import sys
from ruamel.yaml import YAML

yaml = YAML(typ='safe')
yaml.default_flow_style=False
with open(sys.argv[1]) as fp:
    data = yaml.load(fp)
with open(sys.argv[2], 'w') as fp:
    yaml.dump(data, fp)

你已经可以做了:

python expand.py input.yaml output.yaml

这将使你在语义上等同于你所请求的YAML(在output.yaml映射的键被排序,在这个程序输出中它们不是)。

以上假设您的YAML中没有任何标签,也不关心保留任何评论。通过使用标准YAML()实例的修补版本,可以保留大部分内容和密钥排序。修补是必要的,因为标准YAML()实例也保留了往返的合并,这正是您不想要的:

import sys
from ruamel.yaml import YAML, SafeConstructor

yaml = YAML()

yaml.Constructor.flatten_mapping = SafeConstructor.flatten_mapping
yaml.default_flow_style=False
yaml.allow_duplicate_keys = True
# comment out next line if you want "normal" anchors/aliases in your output
yaml.representer.ignore_aliases = lambda x: True  

with open(sys.argv[1]) as fp:
    data = yaml.load(fp)
with open(sys.argv[2], 'w') as fp:
    yaml.dump(data, fp)

使用此输入:

default: &DEFAULT
  URL: website.com
  mode: production
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

development:
  <<: *DEFAULT
  URL: website.local     # local web
  mode: dev

test:
  <<: *DEFAULT
  URL: test.website.qa
  mode: test

将提供此输出(请注意,合并的键的注释会重复):

default:
  URL: website.com
  mode: production
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

development:
  URL: website.local     # local web
  mode: dev

  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

test:
  URL: test.website.qa
  mode: test
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600  # an hour?

以上是本答案开头提到的yaml merge-expand命令。

¹免责声明:我是该套餐的作者。

答案 1 :(得分:2)

更新:2019-03-13 12:41:05

  • 此答案根据Anthon的评论进行了修改,该评论正确地确定了PyYAML的限制。 (见下文陷阱)。

上下文

  • YAML文件
  • 用于解析YAML的Python

问题

  • 用户jtYamlEnthusiast希望输出带有别名,锚点和合并键的non-DRY版本的YAML文件。

将(S)

  • 备选方案1:使用Anthon infra推广的ruamel库。
  • 备选方案2:使用Python pprint.pformat并简单地进行加载/转储往返转换。

原理

  • ruamel库很棒,如果你有权在pyyaml之外安装另一个python库,并且你想要高度控制&#34;往返&#34; YAML转换(例如保存YAML注释)。
  • 如果您不需要对往返YAML进行严格控制,或者由于某些其他原因而限制为pyyaml,您可以直接加载和转储YAML,以获得&#34;非DRY&#34 ;输出。

陷阱

  • 截至撰写本文时PyYAML相对于ruamel库存在限制,涉及YAML v1.1和YAML v1.2的处理

  • 另见

实施例

    ##
    import pprint
    import yaml
    ##
    myrawyaml = '''
    default: &DEFAULT
      URL: website.com
      mode: production
      site_name: Website
      some_setting: h2i8yiuhef
      some_other_setting: 3600

    development:
      <<: *DEFAULT
      URL: website.local
      mode: dev

    test:
      <<: *DEFAULT
      URL: test.website.qa
      mode: test
    '''
    ##
    pynative  =   yaml.safe_load(myrawyaml)
    vout      =   pprint.pformat(pynative)
    print(vout)                             ##=> this is non-DRY and just happens to be well-formed YAML syntax
    print(yaml.safe_load(vout))             ##=> this proves we have well-formed YAML if it loads without exception

答案 2 :(得分:0)

如果由于某种原因有一个用例,需要将扩展​​的YAML作为YAML写回到文件中,则可以:

  • 使用@Anthon的答案。如上所述,但是,如果您无法安装软件包,则此方法可能不可行。

  • 使用@dreftymac的答案。看来,这个答案对某些人有用,但对我却不起作用。根据我的理解,pprint.pformat将参数作为其Python表示形式的字符串返回,并且yaml.safe_load期望Python表示形式本身。当然,您可以eval pprint.pformat返回的字符串,但是即使在受信任的输入上使用eval也会感到讨厌。 (同样,答案有几个不赞成,所以也许我在这里遗漏了一些东西。)

或者,您可以执行我的操作:

import json
import yaml

def expand_yml(yml):
    return yaml.dump(json.loads(json.dumps(yml)))

expand_yml(my_yml_with_aliases)

由于JSON(除了某些例外,例如别名)可以被视为YAML的严格子集,因此这种方法通常应该有效。但是,如果性能是一个问题,或者您要处理的是毛发较大的YAML,则此方法可能不适合您。