ruamel.yaml 自定义标记的自定义 CommentedMapping

ruamel.yaml custom CommentedMapping for custom tags

我有一个带有自定义标签的 YAML 文件,如下所示:

flow123d_version: 3.1.0
problem: !modulek.Coupling_Sequential
  description: Simple dual porosity test - steady flow, simple transport
  mesh:
    mesh_file: ../00_mesh/square_1x1_40el.msh
  flow_equation: !Flow_Darcy_MH
    nonlinear_solver:
      linear_solver: !Petsc
        a_tol: 1.0e-07

到目前为止,我的代码可以加载它并将其转储回来。我的问题是我希望能够检查每个自定义 ! 并检查其他文件是否正确。让我们看看我的文件的第二行。你可以看到我的第一个自定义标签,它由 module.class_name 组成,我需要检查它们。我想将 'modulek' 解析为模块,将 'Coupling_Sequential' 解析为 class_name。我的代码看起来像这样。

import types
import re

import ruamel.yaml as ruml
from ruamel.yaml.comments import CommentedMap, CommentedSeq

CommentsTag = ruml.comments.Tag


class CommentedScalar:
    """
    Class to store all scalars with their tags
    """
    original_constructors = {}

    def __str__(self):
        return str(self.value)

    def __repr__(self):
        return str(self.value)

    @classmethod
    def to_yaml(cls, dumper, data):
        representer = dumper.yaml_representers[type(data.value).__mro__[0]]
        node = representer(dumper, data.value)
        if data.tag.value is None:
            tag = node.tag
        elif data.tag.value.startswith(u'tag:yaml.org,2002'):
            tag = node.tag
        else:
            tag = data.tag.value
        # print("val: ", data.value, "repr: ", node.value, "tag: ", tag)
        return dumper.represent_scalar(tag, node.value)

    def __init__(self, tag, value):
        complete_tag = tag.split('.')
        self.tag.value = tag
        self.value = value
        # self.module.value = complete_tag
        self.module = '.'.join(complete_tag[:len(complete_tag) - 1])
        self.class_name = complete_tag[-1]

    @property
    def tag(self):
        # type: () -> Any
        if not hasattr(self, CommentsTag.attrib):
            setattr(self, CommentsTag.attrib, CommentsTag())
        return getattr(self, CommentsTag.attrib)

def construct_any_tag(self, tag_suffix, node):
    if tag_suffix is None:
        orig_tag = None
    else:
        orig_tag = "!" + tag_suffix
    if isinstance(node, ruml.ScalarNode):

        implicit_tag = self.composer.resolver.resolve(ruml.ScalarNode, node.value, (True, None))
        if implicit_tag in self.yaml_constructors:
            # constructor = CommentedScalar.original_constructors[implicit_tag]
            constructor = self.yaml_constructors[implicit_tag]
        else:
            constructor = self.construct_undefined

        data = constructor(self, node)
        if isinstance(data, types.GeneratorType):
            generator = data
            data = next(generator)  # type: ignore

        scal = CommentedScalar(orig_tag, data)
        yield scal

    elif isinstance(node, ruml.SequenceNode):
        for seq in self.construct_yaml_seq(node):
            seq.yaml_set_tag(orig_tag)
            yield seq
    elif isinstance(node, ruml.MappingNode):
        for map in self.construct_yaml_map(node):
            map.yaml_set_tag(orig_tag)
            yield map
    else:
        for dummy in self.construct_undefined(node):
            yield dummy


def represent_commented_seq(cls, data):
    if data.tag.value is None:
        tag = u'tag:yaml.org,2002:seq'
    else:
        tag = data.tag.value
    return cls.represent_sequence(tag, data)

def get_yaml_serializer():
    """
    Get YAML serialization/deserialization object with proper
    configuration for conversion.
    :return: Confugured instance of ruamel.yaml.YAML.
    """
    yml = ruml.YAML(typ='rt')
    yml.indent(mapping=2, sequence=4, offset=2)
    yml.width = 120
    yml.representer.add_representer(CommentedScalar, CommentedScalar.to_yaml)
    yml.representer.add_representer(CommentedSeq, represent_commented_seq)
    yml.representer.add_representer(CommentedMap, CommentedMapping.to_yaml)
    yml.constructor.add_multi_constructor("!", construct_any_tag)
    return yml


def get_node_tag(node):
    if hasattr(node, "tag"):
        tag = node.tag.value
        if tag and len(tag) > 1 and tag[0] == '!' and tag[1] != '!':
            return tag
    return ""


def load_yaml(path):
    yml = get_yaml_serializer()
    with open(path, 'r') as stream:
        data = yml.load(stream)
    return data


def write_yaml(data, path):
    yml = get_yaml_serializer()
    with open(path, 'w')as stream:
        yml.dump(data, stream)

我正在考虑编写与 CommentedScalar 类似的 "CommentedMapping",但我被困在这里,找不到任何工作。

@classmethod
def to_yaml(cls, dumper, data):
    ...
    ...
    return ???

总结
如果有人将我推向正确的方向,我会很高兴。我什至不知道这样做是否正确。

您的 YAML 输入中所有明确标记的节点都是映射节点,因此您的 CommentedScalar 永远不会 创建(使用此输入)。

由于 ruamel.yaml 在往返模式下已经可以加载和转储您的 YAML,您最好还是步行 加载的数据并检查标签属性。

然而,可以加载那些映射节点,但不能像您那样使用 yield(而且您 复杂节点(映射、序列)只需要 yield,简单节点不需要。

import sys
import ruamel.yaml

yaml_str = """\
flow123d_version: 3.1.0
problem: !modulek.Coupling_Sequential
  description: Simple dual porosity test - steady flow, simple transport
  mesh:
    mesh_file: ../00_mesh/square_1x1_40el.msh
  flow_equation: !Flow_Darcy_MH
    nonlinear_solver:
      linear_solver: !Petsc
        a_tol: 1.0e-07
"""

yaml = ruamel.yaml.YAML()

@yaml.register_class
class MyMap(ruamel.yaml.comments.CommentedMap):
    def __init__(self, tag):
        ruamel.yaml.comments.CommentedMap.__init__(self)
        self._tag = tag + "@@"  # just to make clear things were changed here

    @classmethod
    def to_yaml(cls, representer, data):
        return representer.represent_mapping(data._tag, data)


def construct_any_tag(self, tag_suffix, node):
    if tag_suffix is None:
        orig_tag = None
    else:
        orig_tag = "!" + tag_suffix
    if isinstance(node, ruamel.yaml.nodes.MappingNode):
        data = MyMap(orig_tag)
        yield data
        state = ruamel.yaml.constructor.SafeConstructor.construct_mapping(self, node, deep=True)
        data.update(state)
    else:
        raise NotImplementedError


yaml = ruamel.yaml.YAML()
yaml.constructor.add_multi_constructor("!", construct_any_tag)
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

给出:

flow123d_version: 3.1.0
problem: !modulek.Coupling_Sequential@@
  description: Simple dual porosity test - steady flow, simple transport
  mesh:
    mesh_file: ../00_mesh/square_1x1_40el.msh
  flow_equation: !Flow_Darcy_MH@@
    nonlinear_solver:
      linear_solver: !Petsc@@
        a_tol: 1.0e-07