481 votes

Comment puis-je inclure un fichier YAML à l'intérieur d'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 dans la structure de données existante, comme un tableau, soit comme enfant d'un élément, comme la valeur d'une certaine clé de hachage.

Est-ce possible ? Comment ? Si ce n'est pas le cas, avez-vous des indications sur une référence normative ?

0 votes

3 votes

Je suis récemment tombé sur HiYaPyCo pour Python qui fait exactement cela. Vous pouvez fusionner différents fichiers YAML ensemble. C'est un très beau module Python qui mérite d'être connu.

525voto

jameshfisher Points 3033

Non, YAML ne comprend aucune sorte d'instruction "import" ou "include".

13 votes

Vous pourriez créer un gestionnaire !include <filename>.

14 votes

@clarkevans Bien sûr, mais cette construction serait "en dehors" du langage YAML.

4 votes

C'est désormais possible. J'ai ajouté une réponse ci-dessous... j'espère que cela vous aidera.

171voto

Josh Bode Points 567

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

PyYAML vous permet d'attacher des constructeurs personnalisés (tels que !include ) au chargeur YAML. J'ai inclus un répertoire racine qui peut être défini afin que cette solution supporte les références de fichiers relatives et absolues.

Solution basée sur la classe

Voici une solution basée sur les classes, qui évite la variable globale Root de ma réponse initiale.

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

import yaml
import os

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]]}

0 votes

C'est une fonctionnalité intéressante, merci. Mais quel est le but de toutes ces manipulations avec Root/old_root ? Je suppose que le code de include peut être simplifiée : ` def include(loader, node) : """Inclure un autre fichier YAML.""" filename = loader.construct_scalar(node) data = yaml.load(open(filename)) `

0 votes

La racine globale est là pour que les inclusions relatives fonctionnent à n'importe quelle profondeur, par exemple lorsque des fichiers inclus se trouvant dans un répertoire différent incluent un fichier relatif à ce répertoire. Les inclusions absolues devraient également fonctionner. Il existe probablement un moyen plus propre de faire cela sans variable globale, peut-être en utilisant une classe yaml.Loader personnalisée.

2 votes

Est-il également possible d'avoir quelque chose comme ceci : foo.yaml : a: bla bar.yaml : ` !include foo.yaml b : blubb` Donc le résultat serait : `{'a' : bla, 'b' : blubb}

23voto

clh Points 471

Les inclusions ne sont pas directement supportées dans YAML pour autant que je sache, vous devrez fournir un mécanisme vous-même, cependant, c'est généralement facile à faire.

J'ai utilisé YAML comme langage de configuration dans mes applications python, et dans ce cas, je définis souvent une convention 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 des includes auront toujours priorité sur les variables de main, et il n'y a aucun moyen de changer cette priorité en modifiant l'emplacement de l'instruction "includes :" dans le fichier main.yml.

Sur un point légèrement différent, YAML ne prend pas en charge les includes car il n'est pas vraiment conçu comme un balisage exclusivement basé sur des fichiers. Que signifierait un include si vous le receviez dans une réponse à une requête AJAX ?

5 votes

Cela ne fonctionne que lorsque le fichier yaml ne contient pas de configuration imbriquée.

10voto

Maxy-B Points 992

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 . Elle ne dépend d'aucun élément global au niveau du module, ni de la modification de l'état global de l'élément de base. yaml module.

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

2 votes

J'ai enfin pu ajouter l'approche par classe à ma réponse, mais vous m'avez devancé :) Remarque : Si vous utilisez yaml.load(f, IncludeLoader) sur _include vous pouvez éviter de devoir remplacer la racine. De plus, à moins que vous ne fassiez cela, la solution ne fonctionnera pas à plus d'un niveau de profondeur, car les données incluses utilisent l'attribut régulier yaml.Loader classe.

0 votes

J'ai dû enlever le mot-clé root de kwargs après le réglage self.root pour le faire fonctionner avec des chaînes de caractères. J'ai déplacé le bloc if-else au dessus du bloc super appel. Peut-être que quelqu'un d'autre pourra confirmer ma découverte ou me montrer comment utiliser la classe avec des chaînes de caractères et la fonction root paramètre.

1 votes

Malheureusement, cela ne fonctionne pas avec des références telles que : "included : &INCLUDED !include inner.yaml merge : << : *INCLUDED ``

-9voto

LondonDreams Points 424

Il est probable que cela n'était pas pris en charge lorsque la question a été posée, mais vous pouvez importer d'autres fichiers YAML dans un seul fichier :

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

Bien que je n'aie pas de référence en ligne, cela fonctionne pour moi.

5 votes

Cela n'inclut rien du tout. Elle crée un mappage avec une séquence composée d'une seule chaîne de caractères "/votre_location_vers_yaml_file/Util.area.yaml", comme valeur pour la clé imports .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X