Ruamel.yaml: 在不使用标签的情况下将 yaml 反序列化为 python class 实例

Ruamel.yaml: deserialize yaml into python class instaces without using tags

假设我们有一个包含 .yaml 个文件的文件夹,其中包含 kubernetes 对象,比方说,部署、配置映射和 HPA。

./file1.yaml # {'kind': 'Deployment', ... }, {'kind': 'ConfigMap', ...}
./file2.yaml # {'kind': 'ConfigMap', ... }, {'kind': 'HorizontalPodAutoscaler', ... }

我需要将它们反序列化为适当 class 的实例,但与常规反序列化方法不同,我想避免依赖 YAML 标签,而是通过 YAML 正文进行选择(这就是为什么我有对 register_class() 方法的怀疑)。有一个键 'kind' 应该可以识别正确的 class 实例。

最终目标是解析、修改和转储这些对象(保留注释和格式,所以那些classes将是一个子class CommentedMap 或类似的东西)。

有没有办法ruamel.yaml我怎么把YAML解析成

from ruamel.yaml.comments import CommentedMap

class KubeObjectBase(CommentedMap):

    def some_additional_func(self):
        pass

class Deployment(KubeObjectBase):

    def deployment_method(self):
        pass

class ConfigMap(KubeObjectBase):
    pass

我不完全确定 YAML 文件的实际外观。在你的例子中 # 之后的部分 YAML 不正确,所以我编了一些东西。

这不会影响处理以获得您想要的内容。只要你有有效的、可加载的 YAML, 只是递归数据并替换条目。

您需要以某种方式将 kind 的值映射到您的实际 classes。如果没有 那么多 classes 只是为 class 字典创建一个字符串,如果你有很多,你应该 扫描您的 Python 文件并自动创建该地图(从 class 名称 或来自某些 class 属性):

import sys
import ruamel.yaml
FA = ruamel.yaml.comments.Format.attrib
from pathlib import Path

file1 = Path('file1.yaml')
file1.write_text("""\
- {'kind': 'Deployment', a: 1}
- kind: ConfigMap
  b:
    kind: Deployment
    c: 3
    x: 42
""")
file2 = Path('file2.yaml')
file2.write_text("""\
[
{'kind': 'ConfigMap', d: 4}, 
{'kind': 'HorizontalPodAutoscaler', e: 5},
]
""")

    
kob_map = {}
class KubeObjectBase(ruamel.yaml.comments.CommentedMap):
    def some_additional_func(self):
        pass

    def __repr__(self):
        return f"{self.__class__.__name__}({', '.join([f'{k}: {v}' for k, v in self.items()])})"

class Deployment(KubeObjectBase):
    def deployment_method(self):
        pass
kob_map['Deployment'] = Deployment


class ConfigMap(KubeObjectBase):
    pass
kob_map['ConfigMap'] = ConfigMap


class HorizontalPodAutoscaler(KubeObjectBase):
    pass
kob_map['HorizontalPodAutoscaler'] = HorizontalPodAutoscaler

yaml = ruamel.yaml.YAML()
for v in kob_map.values():
    yaml.Representer.add_representer(v, yaml.Representer.represent_dict)


def un_kind(d, map):
    if isinstance(d, dict):
        for k, v in d.items():
            un_kind(v, map)
            try:
                if 'kind' in v:
                    # typ = map[v.pop('kind')]
                    typ = nv = map[v['kind']]
                    d[k] = typ(v)
                    setattr(nv, FA, v.fa)
                    setattr(nv, '_comment_attrib', v.ca)
            except TypeError:
                pass
    elif isinstance(d, list):
        for idx, elem in enumerate(d):
            un_kind(elem, map)
            try:
                if 'kind' in elem:
                    # typ = map[elem.pop('kind')]
                    typ = map[elem['kind']]
                    d[idx] = nv = typ(elem)
                    setattr(nv, FA, elem.fa)
                    setattr(nv, '_comment_attrib', elem.ca)
            except TypeError:
                pass


for fn in Path('.').glob('*.yaml'):
    data = yaml.load(fn)
    print(f'{fn}:')
    un_kind(data, kob_map)
    print(list(data))
    yaml.dump(data, sys.stdout)

给出:

file1.yaml:
[Deployment(kind: Deployment, a: 1), ConfigMap(kind: ConfigMap, b: Deployment(kind: Deployment, c: 3, x: 42))]
- {kind: Deployment, a: 1}
- kind: ConfigMap
  b:
    kind: Deployment
    c: 3
    x: 42
file2.yaml:
[ConfigMap(kind: ConfigMap, d: 4), HorizontalPodAutoscaler(kind: HorizontalPodAutoscaler, e: 5)]
[{kind: ConfigMap, d: 4}, {kind: HorizontalPodAutoscaler, e: 5}]