utilizzando construct_undefined in ruamel from_yaml

Oct 16 2020

Sto creando un tag yaml personalizzato MyTag. Può contenere qualsiasi yaml valido: mappa, scalare, ancoraggio, sequenza ecc.

Come posso implementare la classe MyTag per modellare questo tag in modo che ruamel analizzi il contenuto di !mytaga esattamente nello stesso modo in cui analizzerebbe un dato yaml? L' MyTagistanza memorizza semplicemente qualunque sia il risultato analizzato del contenuto di yaml.

Il codice seguente funziona e le asserzioni dovrebbero dimostrare esattamente cosa dovrebbe fare e passano tutte.

Ma non sono sicuro che funzioni per i giusti motivi. . . In particolare nel from_yamlmetodo di classe, si utilizza commented_obj = constructor.construct_undefined(node)un modo consigliato per ottenere ciò e consumare 1 e solo 1 dal generatore prodotto è corretto? Non funziona solo per caso?

Dovrei invece usare qualcosa come construct_object, o construct_mapo. . .? Gli esempi che sono stato in grado di trovare tendono a sapere che tipo sta costruendo, quindi userei construct_mapo construct_sequencesceglierei quale tipo di oggetto costruire. In questo caso, voglio effettivamente ripassare il solito / standard ruamel parsing per qualsiasi tipo sconosciuto potrebbe esserci, e memorizzarlo nel suo tipo.

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)

E un po 'di yaml campione:

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

Risposte

Anthon Oct 16 2020 at 04:23

In YAML puoi avere ancore e alias, ed è perfettamente normale che un oggetto sia figlio di se stesso (usando un alias). Se vuoi eseguire il dump della struttura dati di Python data:

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

scarica in:

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

e per questo sono necessari ancore e pseudonimi.

Quando si carica un tale costrutto, ruamel.yaml ricorre alle strutture dati annidate, ma se il nodo di livello superiore non ha causato la costruzione di un oggetto reale a cui l'ancora può essere fatto riferimento, la foglia ricorsiva non può risolvere l'alias.

Per risolvere questo problema, viene utilizzato un generatore, ad eccezione dei valori scalari. Prima crea un oggetto vuoto, quindi ricorre e aggiorna i suoi valori. Nella chiamata di codice al costruttore viene effettuato un controllo per vedere se viene restituito un generatore, e in tal caso next()viene eseguito sui dati, e la potenziale auto-ricorsione "risolta".

Perché chiami construct_undefined(), ottieni sempre un generatore. Praticamente quel metodo potrebbe restituire un valore se rileva un nodo scalare (che ovviamente non può ricorrere), ma non lo fa. In tal caso, il codice non potrebbe caricare il seguente documento YAML:

!mytag 1

senza modifiche che testano se si ottiene o meno un generatore, come si fa nel codice in ruamel.yaml chiamando i vari costruttori in modo che possa gestire entrambi construct_undefinede eg construct_yaml_int(che non è un generatore).