używając construct_undefined w ruamel from_yaml
Tworzę niestandardowy tag yaml MyTag. Może zawierać dowolny prawidłowy yaml - mapę, skalar, kotwicę, sekwencję itp.
Jak zaimplementować klasę MyTag, aby modelować ten znacznik, tak aby ruamel analizował zawartość elementu !mytagdokładnie w taki sam sposób, w jaki analizowałaby dowolny podany yaml? MyTagInstancji tylko sklepy cokolwiek przeanalizowane wynikiem zawartości YAML jest.
Poniższy kod działa, a potwierdzenia powinny dokładnie zademonstrować, co powinien zrobić i wszystkie przechodzą.
Ale nie jestem pewien, czy to działa z właściwych powodów. . . W szczególności w from_yamlmetodzie klasowej, czy używa commented_obj = constructor.construct_undefined(node)się zalecanego sposobu osiągnięcia tego i czy zużycie 1 i tylko 1 z uzyskanego generatora jest prawidłowe? To nie działa przypadkowo?
Czy zamiast tego powinienem używać czegoś takiego jak construct_object, lub construct_maplub. . .? Przykłady, które udało mi się znaleźć, zwykle wiedzą, jakiego typu jest konstruowany, więc użyłbym construct_maplub construct_sequencewybrał typ obiektu do skonstruowania. W tym przypadku efektywnie chcę skorzystać ze zwykłego / standardowego parsowania ruamel dla dowolnego nieznanego typu, który może się tam znajdować, i po prostu zapisać go we własnym typie.
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)
I trochę próbki 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
Odpowiedzi
W YAML możesz mieć kotwice i aliasy, a obiekt może być dzieckiem samego siebie (używając aliasu). Jeśli chcesz zrzucić strukturę danych Pythona data:
data = [1, 2, 4, dict(a=42)]
data[3]['b'] = data
zrzuca do:
&id001
- 1
- 2
- 4
- a: 42
b: *id001
a do tego potrzebne są kotwice i aliasy.
Podczas ładowania takiej konstrukcji ruamel.yaml powraca do zagnieżdżonych struktur danych, ale jeśli węzeł najwyższego poziomu nie spowodował konstruowania rzeczywistego obiektu, do którego można się odwołać do kotwicy, liść rekurencyjny nie może rozpoznać aliasu.
Aby to rozwiązać, używany jest generator, z wyjątkiem wartości skalarnych. Najpierw tworzy pusty obiekt, a następnie powtarza i aktualizuje jego wartości. W kodzie wywołującym konstruktor sprawdzane jest, czy zwrócony jest generator iw tym przypadku next()jest wykonywana na danych, a potencjalna rekursja własna „rozwiązana”.
Ponieważ dzwonisz construct_undefined(), zawsze dostajesz generator. Praktycznie ta metoda może zwrócić wartość, jeśli wykryje węzeł skalarny (który oczywiście nie może się powtórzyć), ale tak się nie dzieje. Jeśli tak, Twój kod nie mógłby załadować następującego dokumentu YAML:
!mytag 1
bez modyfikacji, które sprawdzają, czy otrzymujesz generator, czy nie, jak jest to zrobione w kodzie ruamel.yaml wywołującym różne konstruktory, aby mógł obsłużyć oba construct_undefinedi np. construct_yaml_int(który nie jest generatorem).