【问题标题】:Loading and dumping multiple yaml files with ruamel.yaml (python)使用 ruamel.yaml (python) 加载和转储多个 yaml 文件
【发布时间】:2018-10-23 07:23:52
【问题描述】:

使用 python 2 (atm) 和 ruamel.yaml 0.13.14 (RedHat EPEL)

我目前正在编写一些代码来加载 yaml 定义,但它们被拆分为多个文件。用户可编辑部分包含例如。

users:
  xxxx1:
    timestamp: '2018-10-22 11:38:28.541810'
    << : *userdefaults
  xxxx2:
    << : *userdefaults
    timestamp: '2018-10-22 11:38:28.541810'

默认值存储在另一个不可编辑的文件中:

userdefaults: &userdefaults
    # Default values for user settings
    fileCountQuota: 1000
    diskSizeQuota: "300g"

我可以通过加载并连接字符串来一起处理这些,然后通过merged_data = list(yaml.load_all("{}\n{}".format(defaults_data, user_data), Loader=yaml.RoundTripLoader)) 运行它们,这会正确解决所有问题。 (不使用 RoundTripLoader 时出现无法解析引用的错误,这是正常的)

现在,我想通过 python 代码进行一些更新(例如更新时间戳),为此我只需要写回用户部分。这就是事情变得多毛的地方。到目前为止,我还没有找到一种方法来编写该 yaml 文档,而不是两者兼而有之。

【问题讨论】:

    标签: python yaml ruamel.yaml


    【解决方案1】:

    首先,除非您的默认文件中有多个文档,否则您 不必使用load_all,因为您不必将两个文档连接成一个 多文档流。如果您使用带有文档结尾的格式字符串 标记 ("{}\n...\n{}") 或带有指令结束标记 ("{}\n---\n{}") 根据 YAML 规范:

    别名节点使用不支持的锚是错误的 以前出现在文档中。

    锚点必须在文档中,而不仅仅是在流中(可以由多个 文件)。


    我尝试了一些恶作剧,预先填充了已经表示的字典 锚定节点数:

    import sys
    import datetime
    from ruamel import yaml
    
    def load():
        with open('defaults.yaml') as fp:
            defaults_data = fp.read()
        with open('user.yaml') as fp:
            user_data = fp.read()
        merged_data = yaml.load("{}\n{}".format(defaults_data, user_data), 
                                Loader=yaml.RoundTripLoader)
        return merged_data
    
    class MyRTDGen(object):
        class MyRTD(yaml.RoundTripDumper):
            def __init__(self, *args, **kw):
                pps = kw.pop('pre_populate', None)
                yaml.RoundTripDumper.__init__(self, *args, **kw)
                if pps is not None:
                    for pp in pps:
                        try:
                            anchor = pp.yaml_anchor()
                        except AttributeError:
                            anchor = None
                        node = yaml.nodes.MappingNode(
                            u'tag:yaml.org,2002:map', [], flow_style=None, anchor=anchor)
                        self.represented_objects[id(pp)] = node
    
        def __init__(self, pre_populate=None):
            assert isinstance(pre_populate, list)
            self._pre_populate = pre_populate 
    
        def __call__(self, *args, **kw):
            kw1 = kw.copy()
            kw1['pre_populate'] = self._pre_populate
            myrtd = self.MyRTD(*args, **kw1)
            return myrtd
    
    
    def update(md, file_name):
        ud = md.pop('userdefaults')
        MyRTD = MyRTDGen([ud])
        yaml.dump(md, sys.stdout, Dumper=MyRTD)
        with open(file_name, 'w') as fp:
            yaml.dump(md, fp, Dumper=MyRTD)
    
    md = load()
    md['users']['xxxx2']['timestamp'] = str(datetime.datetime.utcnow())
    update(md, 'user.yaml')
    

    由于基于 PyYAML 的 API 需要类而不是对象,因此您需要 使用一个类生成器,它实际上添加了要预先填充的数据元素 飞来飞去yaml.load()

    但这不起作用,因为一个节点只有在它被写入后才会用锚写出来 确定使用了锚(即有第二个参考)。所以实际上 第一个合并键被写为锚。虽然我很熟悉 使用代码库,我无法让它在合理的时间内正常工作。

    因此,我只依赖于只有一个键匹配的事实 users.yaml 的第一个键在组合更新的转储的根级别 在此之前归档并删除任何内容。

    import sys
    import datetime
    from ruamel import yaml
    
    with open('defaults.yaml') as fp:
        defaults_data = fp.read()
    with open('user.yaml') as fp:
        user_data = fp.read()
    merged_data = yaml.load("{}\n{}".format(defaults_data, user_data), 
                            Loader=yaml.RoundTripLoader)
    
    # find the key
    for line in user_data.splitlines():
        line = line.split('# ')[0].rstrip()  # end of line comment, not checking for strings
        if line and line[-1] == ':' and line[0] != ' ':
            split_key = line
            break
    
    merged_data['users']['xxxx2']['timestamp'] = str(datetime.datetime.utcnow())
    
    buf = yaml.compat.StringIO()
    yaml.dump(merged_data, buf, Dumper=yaml.RoundTripDumper)
    document = split_key + buf.getvalue().split('\n' + split_key)[1]
    sys.stdout.write(document)
    

    给出:

    users:
      xxxx1:
        <<: *userdefaults
        timestamp: '2018-10-22 11:38:28.541810'
      xxxx2:
        <<: *userdefaults
        timestamp: '2018-10-23 09:59:13.829978'
    

    我必须创建一个 virtualenv 以确保我可以使用 ruamel.yaml==0.13.14 运行上述内容。 那个版本是从我还年轻的时候开始的(我不会声称自己是无辜的)。 从那时起,该库已经发布了超过 85 个版本。

    我可以理解您可能无法运行任何东西,但 目前 Python2 无法编译/使用更新的版本。但是什么 你真正应该做的是安装virtualenv(可以使用 EPEL 完成,但也可以不 进一步“污染”您的系统安装),为 您正在开发的代码并安装最新版本的ruamel.yaml(和 你的其他图书馆)在那里。如果需要,您也可以这样做 要将您的软件分发到其他系统,只需在其中安装 virtualenv。

    我拥有/opt/util 下的所有实用程序,并管理 virtualenvutils一个 virtualenv 的包装器。

    【讨论】:

      【解决方案2】:

      要写入用户部分,您必须手动拆分 yaml.dump() 多文件输出的输出并将适当的部分写回用户 yaml 文件。

      import datetime
      import StringIO
      
      import ruamel.yaml
      
      yaml = ruamel.yaml.YAML(typ='rt')
      data = None
      
      with open('defaults.yaml', 'r') as defaults:
          with open('users.yaml', 'r') as users:
              raw = "{}\n{}".format(''.join(defaults.readlines()), ''.join(users.readlines()))
              data = list(yaml.load_all(raw))
      
      data[0]['users']['xxxx1']['timestamp'] = datetime.datetime.now().isoformat()
      
      with open('users.yaml', 'w') as outfile:
          sio = StringIO.StringIO()
          yaml.dump(data[0], sio)
          out = sio.getvalue()
          outfile.write(out.split('\n\n')[1]) # write the second part here as this is the contents of users.yaml
      

      【讨论】:

      • 虽然方法没问题,但这不适用于 0.13.14,因为 OP 表明正在使用。也不需要使用load_all,转换为列表然后只使用第一个元素,除非defaults.yaml文件中有多个文档(然后更新data[0]不起作用)。
      猜你喜欢
      • 1970-01-01
      • 2019-12-08
      • 2019-03-27
      • 2018-11-27
      • 2020-05-30
      • 2016-03-07
      • 1970-01-01
      • 2019-05-16
      • 1970-01-01
      相关资源
      最近更新 更多