ruamelfrom_yamlでconstruct_undefinedを使用する

Oct 16 2020

カスタムyamlタグMyTagを作成しています。マップ、スカラー、アンカー、シーケンスなど、任意の有効なyamlを含めることができます。

クラスMyTagを実装してこのタグをモデル化し、ruamelが任意のyamlを解析するの!mytagとまったく同じ方法でaのコンテンツを解析するようにするにはどうすればよいですか?MyTagYAMLの内容の解析された結果が何であれ、インスタンスは、ちょうど店。

次のコードは機能し、アサートはそれが何をすべきかを正確に示す必要があり、それらはすべて合格します。

しかし、それが正しい理由で機能しているかどうかはわかりません。。。特にfrom_yamlクラスメソッドではcommented_obj = constructor.construct_undefined(node)、これを達成するための推奨される方法を使用しており、生成されたジェネレーターから1つだけを消費していますか?偶然に働いているだけではありませんか?

代わりに、、construct_objectまたはのようなものを使用するconstruct_map必要があります。。。?私が見つけた例は、それが構築しているタイプを知っている傾向があるので、構築するオブジェクトのタイプを使用するconstruct_mapかconstruct_sequence、選択します。この場合、私は事実上、そこにある可能性のある未知のタイプの通常/標準のルアメル解析を便乗させて、それを独自のタイプに格納したいと思います。

import ruamel.yaml
from ruamel.yaml.comments import CommentedMap, CommentedSeq, TaggedScalar


class MyTag():
    yaml_tag = '!mytag'

    def __init__(self, value):
        self.value = value

    @classmethod
    def from_yaml(cls, constructor, node):
        commented_obj = constructor.construct_undefined(node)
        flag = False
        for data in commented_obj:
            if flag:
                raise AssertionError('should only be 1 thing in generator??')
            flag = True

        return cls(data)


with open('mytag-sample.yaml') as yaml_file:
    yaml_parser = ruamel.yaml.YAML()
    yaml_parser.register_class(MyTag)
    yaml = yaml_parser.load(yaml_file)

custom_tag_with_list = yaml['root'][0]['arb']['k2']
assert type(custom_tag_with_list) is MyTag
assert type(custom_tag_with_list.value) is CommentedSeq
print(custom_tag_with_list.value)

standard_list = yaml['root'][0]['arb']['k3']
assert type(standard_list) is CommentedSeq
assert standard_list == custom_tag_with_list.value

custom_tag_with_map = yaml['root'][1]['arb']
assert type(custom_tag_with_map) is MyTag
assert type(custom_tag_with_map.value) is CommentedMap
print(custom_tag_with_map.value)

standard_map = yaml['root'][1]['arb_no_tag']
assert type(standard_map) is CommentedMap
assert standard_map == custom_tag_with_map.value

custom_tag_scalar = yaml['root'][2]
assert type(custom_tag_scalar) is MyTag
assert type(custom_tag_scalar.value) is TaggedScalar

standard_tag_scalar = yaml['root'][3]
assert type(standard_tag_scalar) is str
assert standard_tag_scalar == str(custom_tag_scalar.value)

そしていくつかのサンプルyaml:

root:
  - item: blah
    arb:
      k1: v1
      k2: !mytag
        - one
        - two
        - three-k1: three-v1
          three-k2: three-v2
          three-k3: 123 # arb comment
          three-k4: 
            - a
            - b
            - True
      k3:
        - one
        - two
        - three-k1: three-v1
          three-k2: three-v2
          three-k3: 123 # arb comment
          three-k4: 
            - a
            - b
            - True
  - item: argh
    arb: !mytag
            k1: v1
            k2: 123
            # blah line 1
            # blah line 2
            k3:
              k31: v31
              k32: 
                - False
                - string here
                - 321
    arb_no_tag:
      k1: v1
      k2: 123
      # blah line 1
      # blah line 2
      k3:
        k31: v31
        k32: 
          - False
          - string here
          - 321
  - !mytag plain scalar
  - plain scalar
  - item: no comment
    arb:
      - one1
      - two2

回答

Anthon Oct 16 2020 at 04:23

YAMLでは、アンカーとエイリアスを使用できます。オブジェクトを(エイリアスを使用して)それ自体の子にすることはまったく問題ありません。Pythonデータ構造をダンプしたい場合data:

data = [1, 2, 4, dict(a=42)]
data[3]['b'] = data

ダンプ先:

&id001
- 1
- 2
- 4
- a: 42
  b: *id001

そのためには、アンカーとエイリアスが必要です。

このようなコンストラクトをロードすると、ruamel.yamlはネストされたデータ構造に再帰しますが、トップレベルノードによって、アンカーを参照できる実際のオブジェクトが構築されていない場合、再帰リーフはエイリアスを解決できません。

これを解決するために、スカラー値を除いてジェネレーターが使用されます。最初に空のオブジェクトを作成し、次にその値を繰り返して更新します。コンストラクターを呼び出すコードでは、ジェネレーターが返されるかどうかを確認するためのチェックが行われ、その場合next()はデータに対して実行され、潜在的な自己再帰が「解決」されます。

を呼び出すのでconstruct_undefined()、常にジェネレーターを取得します。実際には、そのメソッドはスカラーノード(もちろん再帰できません)を検出した場合に値を返すことができますが、そうではありません。もしそうなら、あなたのコードは次のYAMLドキュメントをロードできませんでした:

!mytag 1

ruamel.yamlのコードで行われているように、ジェネレーターを取得するかどうかをテストする変更なしで、さまざまなコンストラクターを呼び出して、construct_undefinedとeg construct_yaml_int(ジェネレーターではない)の両方を処理できるようにします。