Comment puis-je inclure un fichier YAML dans un autre?

J’ai donc deux fichiers YAML, “A” et “B” et je veux que le contenu de A soit inséré dans B, soit épissé dans la structure de données existante, comme un tableau, soit comme un enfant, comme la valeur pour une certaine clé de hachage.

Est-ce possible? Comment? Si non, des repères à une référence normative?

Non, YAML n’inclut aucun type de déclaration “import” ou “include”.

Votre question ne demande pas de solution Python, mais en voici une qui utilise PyYAML .

PyYAML vous permet d’attacher des constructeurs personnalisés (tels que !include Include) au chargeur YAML. J’ai inclus un répertoire racine qui peut être défini pour que cette solution prenne en charge les références de fichier relatives et absolues.

Solution basée sur la classe

Voici une solution basée sur des classes, qui évite la variable racine globale de ma réponse d’origine.

Voir cette liste pour une solution Python 3 similaire, plus robuste, qui utilise une métaclasse pour enregistrer le constructeur personnalisé.

 import yaml import os.path class Loader(yaml.SafeLoader): def __init__(self, stream): self._root = os.path.split(stream.name)[0] super(Loader, self).__init__(stream) def include(self, node): filename = os.path.join(self._root, self.construct_scalar(node)) with open(filename, 'r') as f: return yaml.load(f, Loader) Loader.add_constructor('!include', Loader.include) 

Un exemple:

foo.yaml

 a: 1 b: - 1.43 - 543.55 c: !include bar.yaml 

bar.yaml

 - 3.6 - [1, 2, 3] 

Maintenant, les fichiers peuvent être chargés en utilisant:

 >>> with open('foo.yaml', 'r') as f: >>> data = yaml.load(f, Loader) >>> data {'a': 1, 'b': [1.43, 543.55], 'c': [3.6, [1, 2, 3]]} 

Si vous utilisez la version de Symfony de YAML , cela est possible, comme ceci:

 imports: - { resource: sub-directory/file.yml } - { resource: sub-directory/another-file.yml } 

à ma connaissance, les inclusions ne sont pas directement sockets en charge par yaml, mais vous devrez fournir un mécanisme vous-même, mais cela est généralement facile à faire.

J’ai utilisé le yaml comme langage de configuration dans mes applications python et, dans ce cas, définit souvent une convection comme celle-ci:

 >>> main.yml < << includes: [ wibble.yml, wobble.yml] 

Puis dans mon code (python) je fais:

 import yaml cfg = yaml.load(open("main.yml")) for inc in cfg.get("includes", []): cfg.update(yaml.load(open(inc))) 

Le seul inconvénient est que les variables dans les include écrasent toujours les variables dans main, et il n'y a aucun moyen de changer cette priorité en changeant où "include: instruction apparaît dans le fichier main.yml.

Sur un point légèrement différent, YAML ne prend pas en charge le fait qu’il n’a pas été conçu de manière aussi exclusive qu’un marquage de fichier. Que signifie une inclusion si vous l'avez en réponse à une requête ajax?

En développant la réponse de @ Josh_Bode, voici ma propre solution PyYAML, qui a l’avantage d’être une sous-classe autonome de yaml.Loader . Cela ne dépend d’aucun globals au niveau du module, ni de la modification de l’état global du module yaml .

 import yaml, os class IncludeLoader(yaml.Loader): """ yaml.Loader subclass handles "!include path/to/foo.yml" directives in config files. When constructed with a file object, the root path for includes defaults to the directory containing the file, otherwise to the current working directory. In either case, the root path can be overridden by the `root` keyword argument. When an included file F contain its own !include directive, the path is relative to F's location. Example: YAML file /home/frodo/one-ring.yml: --- Name: The One Ring Specials: - resize-to-wearer Effects: - !include path/to/invisibility.yml YAML file /home/frodo/path/to/invisibility.yml: --- Name: invisibility Message: Suddenly you disappear! Loading: data = IncludeLoader(open('/home/frodo/one-ring.yml', 'r')).get_data() Result: {'Effects': [{'Message': 'Suddenly you disappear!', 'Name': 'invisibility'}], 'Name': 'The One Ring', 'Specials': ['resize-to-wearer']} """ def __init__(self, *args, **kwargs): super(IncludeLoader, self).__init__(*args, **kwargs) self.add_constructor('!include', self._include) if 'root' in kwargs: self.root = kwargs['root'] elif isinstance(self.stream, file): self.root = os.path.dirname(self.stream.name) else: self.root = os.path.curdir def _include(self, loader, node): oldRoot = self.root filename = os.path.join(self.root, loader.construct_scalar(node)) self.root = os.path.dirname(filename) data = yaml.load(open(filename, 'r')) self.root = oldRoot return data 

Malheureusement, YAML ne fournit pas cela dans sa norme.

Mais si vous utilisez Ruby, il existe un joyau fournissant les fonctionnalités que vous demandez en étendant la bibliothèque YAML ruby: https://github.com/entwanderer/yaml_extend

Avec Symfony , sa gestion de yaml vous permettra indirectement d’imbriquer des fichiers yaml. L’astuce consiste à utiliser l’option parameters . par exemple:

common.yml

 parameters: yaml_to_repeat: option: "value" foo: - "bar" - "baz" 

config.yml

 imports: - { resource: common.yml } whatever: thing: "%yaml_to_repeat%" other_thing: "%yaml_to_repeat%" 

Le résultat sera le même que:

 whatever: thing: option: "value" foo: - "bar" - "baz" other_thing: option: "value" foo: - "bar" - "baz" 

Je pense que la solution utilisée par @ maxy-B est géniale. Cependant, cela n’a pas réussi pour moi avec des inclusions nestedes. Par exemple, si config_1.yaml inclut config_2.yaml, qui inclut config_3.yaml, il y a eu un problème avec le chargeur. Cependant, si vous pointez simplement la nouvelle classe du chargeur sur elle-même en charge, cela fonctionne! Plus précisément, si nous remplaçons l’ancienne fonction _include par la version très légèrement modifiée:

 def _include(self, loader, node): oldRoot = self.root filename = os.path.join(self.root, loader.construct_scalar(node)) self.root = os.path.dirname(filename) data = yaml.load(open(filename, 'r'), loader = IncludeLoader) self.root = oldRoot return data 

Après reflection, je suis d’accord avec les autres commentaires, le chargement nested n’est pas approprié pour le yaml en général, car le stream d’entrée n’est peut-être pas un fichier, mais c’est très utile!

Peut-être que cela pourrait vous inspirer, essayez de vous aligner sur les conventions JBB:

https://docs.openstack.org/infra/jenkins-job-builder/definition.html#inclusion-tags

- job: name: test-job-include-raw-1 builders: - shell: !include-raw: include-raw001-hello-world.sh

Probablement il n’a pas été supporté quand la question a été posée mais vous pouvez importer d’autres fichiers YAML en un seul:

 imports: [/your_location_to_yaml_file/Util.area.yaml] 

Bien que je n’aie aucune référence en ligne, cela fonctionne pour moi.