Pregunta

Así que tengo dos archivos YAML, "A" y "B" y quiero que el contenido de A a insertarse en el interior B, o bien empalmados en la estructura de los datos existentes, como una matriz, o como hijo de un elemento, al igual que el valor de una determinada clave hash.

¿Es esto posible? ¿Cómo? Si no es así, cualquier puntero a una referencia normativa?

¿Fue útil?

Solución

No, YAML no incluye ningún tipo de "importación" o "incluir" declaración.

Otros consejos

Su pregunta no pide una solución Python, pero aquí es uno usando PyYAML .

PyYAML le permite adjuntar constructores personalizados (como !include) al cargador YAML. He incluido un directorio raíz que se puede configurar para que esta solución es compatible con las referencias de archivos relativos y absolutos.

Clase-solución basada

Aquí es una solución basada en la clase, que evita la variable raíz mundial de mi respuesta original.

GIST para una más robusta solución similar, Python 3 que utiliza una metaclase para registrarse el constructor personalizado.

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 ejemplo:

foo.yaml

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

bar.yaml

- 3.6
- [1, 2, 3]

Ahora los archivos se pueden cargar usando:

>>> 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 está utilizando la versión de Symfony de YAML , esto es posible, de esta manera:

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

Incluye no son compatibles directamente en YAML por lo que yo sé, usted tendrá que proporcionar un mecanismo de sí mismo Sin embargo, esto es generalmente fácil de hacer.

He utilizado YAML como un lenguaje de configuración en mis aplicaciones de pitón, y en este caso suelen definir una convención de la siguiente manera:

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

A continuación, en mi código (Python) que hago:

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

El único aspecto negativo es que las variables en los includes siempre neutralizan las variables en el principal, y no hay manera de cambiar la precedencia que cambiando donde el "incluye: declaración aparece en el archivo main.yml

.

En un punto ligeramente diferente, YAML no soporta incluye ya que no es realmente diseñado como ya exclusivamente como una marca basado en archivos para arriba. Lo que sería una media incluir si lo ha hecho en respuesta a una petición AJAX?

Ampliando la respuesta de @ Josh_Bode, aquí está mi propia solución PyYAML, que tiene la ventaja de ser una subclase autónomo de yaml.Loader. Que no depende de ningún globales a nivel de módulo, o en la modificación del estado global del módulo 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                                                      

Para los usuarios de Python, puede intentar PyYAML-incluir .

Instalar

pip install pyyaml-include

Uso

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)

Tenga en cuenta que tenemos este tipo de archivos YAML :

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • contenido 1.yaml 's:
name: "1"
  • contenido 2.yaml 's:
name: "2"

Incluir archivos por nombre

  • El nivel superior:

    Si 0.yaml era:

!include include.d/1.yaml

Vamos a llegar:

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

    Si 0.yaml era:

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

Vamos a llegar:

  file1:
    name: "1"
  file2:
    name: "2"
  • En secuencia:

    Si 0.yaml era:

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

Vamos a llegar:

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

Nota:

     

nombre de archivo puede ser absoluta (como /usr/conf/1.5/Make.yml) o relativa (como ../../cfg/img.yml).

Incluir archivos mediante comodines

Nombre de archivo puede contener comodines de tipo concha. Los datos cargados desde el archivo (s) encontrado por comodines se establecerán en una secuencia.

Si 0.yaml era:

files: !include include.d/*.yaml

Vamos a llegar:

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

Nota:

     
      
  • Para Python>=3.5, si el argumento de recursive !include etiqueta YAML es true, la “**” patrón coincidirá con todos los archivos y cero o más directorios y subdirectorios.
  •   
  • Uso del patrón de “**” en grandes árboles de directorios puede consumir una cantidad excesiva de tiempo debido a la búsqueda recursiva.
  •   

A fin de que el argumento recursive, escribiremos la etiqueta !include en modo Mapping o Sequence:

  • Los argumentos a modo de Sequence:
!include [tests/data/include.d/**/*.yaml, true]
  • Los argumentos a modo de Mapping:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

Desafortunadamente YAML no proporciona esta en su estándar.

Sin embargo, si usted está usando Ruby, hay una joya que proporciona la funcionalidad que está pidiendo al extender la biblioteca de rubí YAML: https://github.com/entwanderer/yaml_extend

Creo que la solución utilizada por @ Maxy-B se ve muy bien. Sin embargo, no tuvo éxito para mí con inclusiones anidadas. Por ejemplo, si config_1.yaml incluye config_2.yaml, que incluye config_3.yaml había un problema con el cargador. Sin embargo, si simplemente apuntar el nuevo cargador de clases a sí mismo en la carga, funciona! En concreto, si se sustituye la antigua función _Incluir con la versión ligeramente modificada:

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

Sobre la reflexión Estoy de acuerdo con los otros comentarios, que la carga anidada no es apropiado para yaml en general, como el flujo de entrada no puede ser un archivo, pero es muy útil!

Tal vez esto podría inspirar a usted, trate de alinear a las convenciones 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

Estándar YAML 1.2 no incluye esta función de forma nativa. Sin embargo muchas implementaciones proporciona alguna extensión para hacerlo.

presento una forma de lograrlo con Java y snakeyaml:1.24 (Biblioteca de Java para analizar / emitir archivos YAML) que permite la creación de una etiqueta personalizada YAML para lograr el siguiente objetivo (verá lo estoy usando para cargar conjuntos de pruebas definidos en varios archivos YAML y que hice que funcione como una lista de incluye un nodo test: objetivo):

# ... yaml prev stuff

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

# ... more yaml document

Esta es la clase de Java uno que permite el procesamiento de la etiqueta !include. Los archivos se cargan de rutas de clases (recursos Maven directorio):

/**
 * 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));
                }
            }
        }

    }

}

Symfony , su manejo de yaml de manera indirecta que permita a los archivos YAML nido. El truco es hacer uso de la opción parameters. por ejemplo:

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%"

El resultado será el mismo que:

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

Probablemente no se admite cuando se le preguntó pregunta, pero se puede importar otro archivo YAML en una sola:

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

A pesar de que no tengo ninguna referencia en línea, pero esto funciona para mí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top