en utilisant construct_undefined dans ruamel from_yaml
Je crée une balise yaml personnalisée MyTag. Il peut contenir n'importe quel yaml valide donné - carte, scalaire, ancre, séquence, etc.
Comment implémenter la classe MyTag pour modéliser cette balise afin que ruamel analyse le contenu d'un !mytagexactement de la même manière qu'il analyserait n'importe quel yaml donné? L' MyTaginstance stocke simplement le résultat analysé du contenu yaml.
Le code suivant fonctionne et les assertions doivent montrer exactement ce qu'il doit faire et elles passent toutes.
Mais je ne suis pas sûr que cela fonctionne pour les bonnes raisons. . . Plus précisément, dans la from_yamlméthode de classe, est-ce que l'utilisation d' commented_obj = constructor.construct_undefined(node)une méthode recommandée pour y parvenir, et est-ce que la consommation de 1 et seulement 1 du générateur produit est correcte? Cela ne fonctionne pas simplement par accident?
Dois-je plutôt utiliser quelque chose comme construct_object, ou construct_mapou. . .? Les exemples que j'ai pu trouver ont tendance à savoir quel type il est en train de construire, donc utiliserait construct_mapou construct_sequencechoisirait le type d'objet à construire. Dans ce cas, je veux effectivement utiliser l'analyse ruamel habituelle / standard pour tout type inconnu qui pourrait y être, et simplement le stocker dans son propre type.
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)
Et quelques exemples de 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
Réponses
Dans YAML, vous pouvez avoir des ancres et des alias, et il est parfaitement normal qu'un objet soit un enfant de lui-même (en utilisant un alias). Si vous souhaitez vider la structure de données Python data:
data = [1, 2, 4, dict(a=42)]
data[3]['b'] = data
il se décharge dans:
&id001
- 1
- 2
- 4
- a: 42
b: *id001
et pour cela, des ancres et des alias sont nécessaires.
Lors du chargement d'une telle construction, ruamel.yaml revient dans les structures de données imbriquées, mais si le nœud de niveau supérieur n'a pas provoqué la construction d'un objet réel auquel l'ancre peut être référencée, la feuille récursive ne peut pas résoudre l'alias.
Pour résoudre cela, un générateur est utilisé, sauf pour les valeurs scalaires. Il crée d'abord un objet vide, puis récurent et met à jour ses valeurs. Dans le code appelant le constructeur, une vérification est effectuée pour voir si un générateur est retourné, et dans ce cas, elle next()est effectuée sur les données, et l'auto-récursivité potentielle est "résolue".
Parce que vous appelez construct_undefined(), vous obtenez toujours un générateur. Pratiquement, cette méthode pourrait retourner une valeur si elle détecte un nœud scalaire (qui bien sûr ne peut pas récurer), mais ce n'est pas le cas. Si tel était le cas, votre code pourrait ne pas charger le document YAML suivant:
!mytag 1
sans modifications qui testent si vous obtenez un générateur ou non, comme cela se fait dans le code de ruamel.yaml appelant les différents constructeurs pour qu'il puisse gérer les deux construct_undefinedet par exemple construct_yaml_int(qui n'est pas un générateur).