web-dev-qa-db-fra.com

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, ou en tant qu'enfant d'un élément, comme la valeur pour une certaine clé de hachage.

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

214
kch

Non, YAML n'inclut aucun type d'instruction "import" ou "include".

254
jameshfisher

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) au chargeur YAML. J'ai inclus un répertoire racine pouvant ê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 les classes, qui évite la variable racine globale de ma réponse d'origine.

Voir ceci Gist pour une solution Python 3 plus robuste, similaire, qui utilise une métaclasse pour inscrire 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]]}
86
Josh Bode

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

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

Les actions incluses ne sont pas directement prises en charge dans YAML, autant que je sache, vous devrez fournir un mécanisme vous-même, toutefois, cela 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 incluses incluent toujours les variables principales, et il est impossible de modifier cette priorité en modifiant l'emplacement où l'instruction "includes: apparaît dans le fichier main.yml. 

Sur un point légèrement différent, YAML ne prend pas en charge les inclus, car il n’est pas vraiment conçu aussi exclusivement comme une balise basée sur un fichier. Que signifie une inclusion si vous l'obtenez dans une réponse à une demande AJAX? 

10
clh

Pour en savoir plus sur la réponse de @ Josh_Bode, voici ma propre solution PyYAML, qui présente l’avantage d’être une sous-classe autonome de yaml.Loader. Cela ne dépend pas des globales 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                                                      
7
Maxy-B

Je pense que la solution utilisée par @ maxy-B est superbe. Cependant, cela n'a pas réussi pour moi avec des inclusions imbriquées. Par exemple, si config_1.yaml inclut config_2.yaml, ce qui inclut config_3.yaml, il y a eu un problème avec le chargeur. Cependant, si vous pointez simplement la nouvelle classe de chargeur sur elle-même, 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 réflexion, je suis d’accord avec les autres commentaires, selon lequel le chargement imbriqué n’est pas approprié pour yaml en général, car le flux d’entrée n’est peut-être pas un fichier, mais il est très utile!

1
PaddyM

Pour les utilisateurs de Python, vous pouvez essayer pyyaml-include .

Installer

pip install pyyaml-include

Usage

import yaml
from yamlinclude import YamlIncludeConstructor

YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir='/your/conf/dir')

with open('0.yaml') as f:
    data = yaml.load(f, Loader=yaml.FullLoader)

print(data)

Considérons que nous avons de tels YAML fichiers:

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • Contenu de 1.yaml:
name: "1"
  • Contenu de 2.yaml:
name: "2"

Inclure les fichiers par nom

  • Au plus haut niveau:

    Si 0.yaml était:

!include include.d/1.yaml

Nous aurons:

{"name": "1"}
  • En cartographie:

    Si 0.yaml était:

file1: !include include.d/1.yaml
file2: !include include.d/2.yaml

Nous aurons:

  file1:
    name: "1"
  file2:
    name: "2"
  • En séquence:

    Si 0.yaml était:

files:
  - !include include.d/1.yaml
  - !include include.d/2.yaml

Nous aurons:

files:
  - name: "1"
  - name: "2"

Note :

Le nom de fichier peut être absolu (comme /usr/conf/1.5/Make.yml) ou relatif (comme ../../cfg/img.yml).

Inclure les fichiers par caractères génériques

Le nom de fichier peut contenir des caractères génériques de style shell. Les données chargées à partir des fichiers trouvés par des caractères génériques seront définies dans une séquence.

Si 0.yaml était:

files: !include include.d/*.yaml

Nous aurons:

files:
  - name: "1"
  - name: "2"

Note :

  • Pour Python>=3.5, si l'argument recursive de !includeYAML tag est true, le modèle “**” correspond à tous les fichiers et à aucun ou plusieurs répertoires et sous-répertoires.
  • L'utilisation du modèle “**” dans des arborescences de répertoires volumineuses peut prendre beaucoup de temps en raison d'une recherche récursive.

Afin d'activer l'argument recursive, nous écrirons la balise !include en mode Mapping ou Sequence:

  • Arguments en mode Sequence:
!include [tests/data/include.d/**/*.yaml, true]
  • Arguments en mode Mapping:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
1
xqliang

Malheureusement, YAML ne fournit pas cela dans sa norme.

Mais si vous utilisez Ruby, une gemme fournit la fonctionnalité que vous demandez en étendant la bibliothèque Ruby YAML: https://github.com/entwanderer/yaml_extend

1
user8419486

Peut-être que cela pourrait vous inspirer, essayez de vous aligner sur les conventions de 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

0
RzR

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"
0
jxmallett

YAML 1.2 standard n'inclut pas nativement cette fonctionnalité. Néanmoins, de nombreuses implémentations fournissent une extension pour le faire.

Je présente un moyen de le réaliser avec Java et snakeyaml:1.24 (bibliothèque Java pour analyser/émettre des fichiers YAML) qui permet de créer une balise YAML personnalisée pour atteindre l'objectif suivant (vous verrez que je suis l’utiliser pour charger des suites de tests définies dans plusieurs fichiers YAML et que je l’ai fait fonctionner comme une liste d’inclusions pour un noeud cible test:):

# ... yaml prev stuff

tests: !include
  - '1.hello-test-suite.yaml'
  - '3.foo-test-suite.yaml'
  - '2.bar-test-suite.yaml'

# ... more yaml document

Voici la classe Java qui permet de traiter la balise !include. Les fichiers sont chargés à partir de classpath (répertoire de ressources Maven):

/**
 * Custom YAML loader. It adds support to the custom !include tag which allows splitting a YAML file across several
 * files for a better organization of YAML tests.
 */
@Slf4j   // <-- This is a Lombok annotation to auto-generate logger
public class MyYamlLoader {

    private static final Constructor CUSTOM_CONSTRUCTOR = new MyYamlConstructor();

    private MyYamlLoader() {
    }

    /**
     * Parse the only YAML document in a stream and produce the Java Map. It provides support for the custom !include
     * YAML tag to split YAML contents across several files.
     */
    public static Map<String, Object> load(InputStream inputStream) {
        return new Yaml(CUSTOM_CONSTRUCTOR)
                .load(inputStream);
    }


    /**
     * Custom SnakeYAML constructor that registers custom tags.
     */
    private static class MyYamlConstructor extends Constructor {

        private static final String TAG_INCLUDE = "!include";

        MyYamlConstructor() {
            // Register custom tags
            yamlConstructors.put(new Tag(TAG_INCLUDE), new IncludeConstruct());
        }

        /**
         * The actual include tag construct.
         */
        private static class IncludeConstruct implements Construct {

            @Override
            public Object construct(Node node) {
                List<Node> inclusions = castToSequenceNode(node);
                return parseInclusions(inclusions);
            }

            @Override
            public void construct2ndStep(Node node, Object object) {
                // do nothing
            }

            private List<Node> castToSequenceNode(Node node) {
                try {
                    return ((SequenceNode) node).getValue();

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The !import value must be a sequence node, but " +
                            "'%s' found.", node));
                }
            }

            private Object parseInclusions(List<Node> inclusions) {

                List<InputStream> inputStreams = inputStreams(inclusions);

                try (final SequenceInputStream sequencedInputStream =
                             new SequenceInputStream(Collections.enumeration(inputStreams))) {

                    return new Yaml(CUSTOM_CONSTRUCTOR)
                            .load(sequencedInputStream);

                } catch (IOException e) {
                    log.error("Error closing the stream.", e);
                    return null;
                }
            }

            private List<InputStream> inputStreams(List<Node> scalarNodes) {
                return scalarNodes.stream()
                        .map(this::inputStream)
                        .collect(toList());
            }

            private InputStream inputStream(Node scalarNode) {
                String filePath = castToScalarNode(scalarNode).getValue();
                final InputStream is = getClass().getClassLoader().getResourceAsStream(filePath);
                Assert.notNull(is, String.format("Resource file %s not found.", filePath));
                return is;
            }

            private ScalarNode castToScalarNode(Node scalarNode) {
                try {
                    return ((ScalarNode) scalarNode);

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The value must be a scalar node, but '%s' found" +
                            ".", scalarNode));
                }
            }
        }

    }

}
0
Gerard Bosch