menggunakan construct_undefined di ruamel from_yaml

Oct 16 2020

Saya membuat tag yaml khusus MyTag. Ini dapat berisi yaml valid yang diberikan - peta, skalar, jangkar, urutan, dll.

Bagaimana cara mengimplementasikan kelas MyTag untuk memodelkan tag ini sehingga ruamel mem-parsing konten a !mytagdengan cara yang persis sama seperti yang akan mengurai yaml yang diberikan? The MyTagMisalnya saja toko apapun hasil parsing dari isi YAML adalah.

Kode berikut berfungsi, dan pernyataan harus menunjukkan dengan tepat apa yang harus dilakukan dan semuanya lolos.

Tapi saya tidak yakin apakah itu berfungsi untuk alasan yang benar. . . Khususnya dalam from_yamlmetode kelas, apakah menggunakan commented_obj = constructor.construct_undefined(node)cara yang disarankan untuk mencapai hal ini, dan apakah mengkonsumsi 1 dan hanya 1 dari generator yang dihasilkan benar? Ini tidak hanya bekerja karena kecelakaan?

Haruskah saya menggunakan sesuatu seperti construct_object, atau construct_mapatau. . .? Contoh-contoh yang dapat saya temukan cenderung mengetahui tipe apa yang sedang dibangunnya, jadi akan menggunakan construct_mapatau construct_sequencememilih tipe objek yang akan dibangun. Dalam hal ini saya secara efektif ingin mendukung penguraian ruamel biasa / standar untuk jenis apa pun yang tidak diketahui yang mungkin ada di sana, dan hanya menyimpannya dalam tipenya sendiri.

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)

Dan beberapa contoh 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

Jawaban

Anthon Oct 16 2020 at 04:23

Di YAML Anda dapat memiliki jangkar dan alias, dan tidak masalah jika objek menjadi anak itu sendiri (menggunakan alias). Jika Anda ingin membuang struktur data Python data:

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

itu dibuang ke:

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

dan untuk itu diperlukan jangkar dan alias.

Saat memuat konstruksi seperti itu, ruamel.yaml muncul kembali ke dalam struktur data yang disarangkan, tetapi jika simpul tingkat atas tidak menyebabkan objek nyata yang akan dibangun di mana jangkar dapat dijadikan referensi, daun rekursif tidak dapat menyelesaikan alias.

Untuk mengatasinya, generator digunakan, kecuali nilai skalar. Ini pertama-tama membuat objek kosong, lalu mengulang dan memperbarui nilainya. Dalam pemanggilan kode konstruktor, pemeriksaan dilakukan untuk melihat apakah generator dikembalikan, dan dalam hal next()ini dilakukan pada data, dan potensi rekursi sendiri "diselesaikan".

Karena Anda menelepon construct_undefined(), Anda selalu mendapatkan generator. Praktisnya, metode tersebut dapat mengembalikan nilai jika mendeteksi node skalar (yang tentu saja tidak dapat muncul kembali), tetapi ternyata tidak. Jika ya, kode Anda tidak dapat memuat dokumen YAML berikut:

!mytag 1

tanpa modifikasi yang menguji apakah Anda mendapatkan generator atau tidak, seperti yang dilakukan dalam kode di ruamel.yaml memanggil berbagai konstruktor sehingga dapat menangani keduanya construct_undefineddan misalnya construct_yaml_int(yang bukan generator).