¿Cómo fusionar matrices YAML?

112

Me gustaría fusionar matrices en YAML y cargarlas a través de ruby ​​-

some_stuff: &some_stuff
 - a
 - b
 - c

combined_stuff:
  <<: *some_stuff
  - d
  - e
  - f

Me gustaría tener la matriz combinada como [a,b,c,d,e,f]

Recibo el error: no encontré la clave esperada al analizar una asignación de bloque

¿Cómo fusiono matrices en YAML?

lfender6445
fuente
6
¿Por qué quiere hacer esto en YAML en lugar del idioma con el que lo está analizando?
Patrick Collins
7
para secar la duplicación en un archivo yaml muy grande
lfender6445
4
Ésta es una práctica muy mala. Debería leer yamls por separado, juntar las matrices en Ruby y luego volver a escribirlas en yaml.
Sawa
74
¿Cómo es una mala práctica intentar estar seco?
krak3n
13
@PatrickCollins Encontré esta pregunta tratando de reducir la duplicación en mi archivo .gitlab-ci.yml y, desafortunadamente, no tengo control sobre el analizador que usa GitLab CI :(
rink.attendant.6

Respuestas:

40

Si el objetivo es ejecutar una secuencia de comandos de shell, puede lograrlo de la siguiente manera:

# note: no dash before commands
some_stuff: &some_stuff |-
    a
    b
    c

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Esto es equivalente a:

some_stuff: "a\nb\nc"

combined_stuff:
  - "a\nb\nc"
  - d
  - e
  - f

He estado usando esto en mi gitlab-ci.yml(para responder @ rink.attendant.6 comentario sobre la pregunta).


Ejemplo de trabajo que usamos para admitir requirements.txttener repositorios privados de gitlab:

.pip_git: &pip_git
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "ssh://[email protected]"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts

test:
    image: python:3.7.3
    stage: test
    script:
        - *pip_git
        - pip install -q -r requirements_test.txt
        - python -m unittest discover tests

use the same `*pip_git` on e.g. build image...

donde requirements_test.txtcontiene, por ejemplo

-e git+ssh://[email protected]/example/[email protected]#egg=example

Jorge Leitao
fuente
3
Inteligente. Lo estoy usando en nuestro canal de Bitbucket ahora. Gracias
Dariop
* Aquí no se requiere el guión final, solo el tubo al final es suficiente. * Esta es una solución inferior ya que cuando el trabajo falla en una declaración de varias líneas muy larga, no está claro qué comando falló.
Mina Luke
1
@MinaLuke, ¿inferior en comparación con qué? Ninguna de las respuestas actuales proporciona una forma de fusionar dos elementos usando solo yaml ... Además, no hay nada en la pregunta que indique que el OP desea usar esto en CI / CD. Finalmente, cuando esto se usa en CI / CD, el registro solo depende del CI / CD particular usado, no de la declaración yaml. Entonces, en todo caso, el CI / CD al que te refieres es el que está haciendo un mal trabajo. El yaml en esta respuesta es válido y resuelve el problema de OP.
Jorge Leitao
@JorgeLeitao Supongo que lo usas para combinar Reglas. ¿Puede proporcionar un ejemplo funcional de gitlabci? Intenté algo basado en su solución, pero siempre obtengo un error de validación.
niels
@niels, he agregado un ejemplo con un ejemplo funcional de gitlabci. Tenga en cuenta que algunos IDE marcan este yaml como no válido, aunque no lo es.
Jorge Leitao
26

Actualización: 2019-07-01 14:06:12

  • Nota : otra respuesta a esta pregunta se editó sustancialmente con una actualización sobre enfoques alternativos .
    • Esa respuesta actualizada menciona una alternativa a la solución en esta respuesta. Se ha añadido a la sección Véase también a continuación.

Contexto

Esta publicación asume el siguiente contexto:

  • pitón 2.7
  • analizador Python YAML

Problema

lfender6445 desea fusionar dos o más listas dentro de un archivo YAML, y que esas listas fusionadas aparezcan como una lista singular cuando se analizan.

Solución (solución alternativa)

Esto se puede obtener simplemente asignando anclajes YAML a las asignaciones, donde las listas deseadas aparecen como elementos secundarios de las asignaciones. Sin embargo, hay algunas salvedades (consulte "Errores" más adelante).

En el siguiente ejemplo, tenemos tres asignaciones ( list_one, list_two, list_three) y tres anclajes y alias que se refieren a estas asignaciones cuando corresponda.

Cuando se carga el archivo YAML en el programa, obtenemos la lista que queremos, pero es posible que requiera una pequeña modificación después de la carga (vea las trampas a continuación).

Ejemplo

Archivo YAML original

  list_one: & id001
   - una
   - b
   - C

  list_two: & id002
   - e
   - f
   - g

  list_three: & id003
   - h
   - yo
   - j

  list_combined:
      - * id001
      - * id002
      - * id003

Resultado después de YAML.safe_load

## list_combined
  [
    [
      "una",
      "si",
      "C"
    ],
    [
      "mi",
      "F",
      "gramo"
    ],
    [
      "h",
      "yo",
      "j"
    ]
  ]

Trampas

Conclusión

Este enfoque permite la creación de listas fusionadas mediante el uso de la función de alias y ancla de YAML.

Aunque el resultado de salida es una lista anidada de listas, esto se puede transformar fácilmente usando el flattenmétodo.

Ver también

Enfoque alternativo actualizado por @Anthon

Ejemplos del flattenmétodo

dreftymac
fuente
21

Esto no va a funcionar:

  1. la combinación solo es compatible con las especificaciones YAML para asignaciones y no para secuencias

  2. está mezclando completamente las cosas al tener una clave de combinación << seguida del separador de clave / valor :y un valor que es una referencia y luego continúa con una lista en el mismo nivel de sangría

Esto no es correcto YAML:

combine_stuff:
  x: 1
  - a
  - b

Por lo tanto, su sintaxis de ejemplo ni siquiera tendría sentido como propuesta de extensión YAML.

Si desea hacer algo como fusionar varias matrices, es posible que desee considerar una sintaxis como:

combined_stuff:
  - <<: *s1, *s2
  - <<: *s3
  - d
  - e
  - f

donde s1, s2, s3son anclas en secuencias (no mostrados) que desea combinar en una nueva secuencia y luego tener el d, eyf adjuntas a eso. Pero YAML primero está resolviendo este tipo de estructuras en profundidad, por lo que no hay un contexto real disponible durante el procesamiento de la clave de combinación. No hay una matriz / lista disponible a la que pueda adjuntar el valor procesado (la secuencia anclada).

Puede adoptar el enfoque propuesto por @dreftymac, pero esto tiene la gran desventaja de que de alguna manera necesita saber qué secuencias anidadas aplanar (es decir, conociendo la "ruta" desde la raíz de la estructura de datos cargada hasta la secuencia principal), o que recorra de forma recursiva la estructura de datos cargada en busca de matrices / listas anidadas y las aplana todas indiscriminadamente.

En mi opinión, una mejor solución sería usar etiquetas para cargar estructuras de datos que hagan el acoplado por usted. Esto permite indicar claramente qué se debe aplanar y qué no, y le brinda un control total sobre si este aplanamiento se realiza durante la carga o durante el acceso. Cuál elegir es una cuestión de facilidad de implementación y eficiencia en tiempo y espacio de almacenamiento. Esta es la misma disyuntiva que hay que hacer para implementar la combinación de la tecla función y no hay una solución única que siempre es la mejor.

Por ejemplo, mi ruamel.yamlbiblioteca usa los dictados combinados de fuerza bruta durante la carga cuando usa su cargador seguro, lo que da como resultado diccionarios combinados que son dictados normales de Python. Esta fusión debe realizarse por adelantado y duplica los datos (espacio ineficaz) pero es rápida en la búsqueda de valor. Al usar el cargador de viaje de ida y vuelta, desea poder volcar las fusiones sin fusionar, por lo que deben mantenerse separadas. El dict como estructura de datos cargada como resultado de la carga de ida y vuelta, es eficiente en el espacio pero más lento en el acceso, ya que necesita intentar buscar una clave que no se encuentra en el dict mismo en las fusiones (y esto no se almacena en caché, por lo que debe hacerse cada vez). Por supuesto, estas consideraciones no son muy importantes para archivos de configuración relativamente pequeños.


Lo siguiente implementa un esquema similar a la fusión para listas en Python usando objetos con etiqueta flatten que sobre la marcha recurre a elementos que son listas y etiquetados toflatten. Usando estas dos etiquetas puede tener un archivo YAML:

l1: &x1 !toflatten
  - 1 
  - 2
l2: &x2
  - 3 
  - 4
m1: !flatten
  - *x1
  - *x2
  - [5, 6]
  - !toflatten [7, 8]

(el uso de secuencias de estilo de flujo frente a bloque es completamente arbitrario y no influye en el resultado cargado).

Al iterar sobre los elementos que son el valor de la clave, m1esto "recurre" a las secuencias etiquetadas contoflatten , pero muestra otras listas (con alias o no) como un solo elemento.

Una forma posible con el código Python de lograrlo es:

import sys
from pathlib import Path
import ruamel.yaml

yaml = ruamel.yaml.YAML()


@yaml.register_class
class Flatten(list):
   yaml_tag = u'!flatten'
   def __init__(self, *args):
      self.items = args

   @classmethod
   def from_yaml(cls, constructor, node):
       x = cls(*constructor.construct_sequence(node, deep=True))
       return x

   def __iter__(self):
       for item in self.items:
           if isinstance(item, ToFlatten):
               for nested_item in item:
                   yield nested_item
           else:
               yield item


@yaml.register_class
class ToFlatten(list):
   yaml_tag = u'!toflatten'

   @classmethod
   def from_yaml(cls, constructor, node):
       x = cls(constructor.construct_sequence(node, deep=True))
       return x



data = yaml.load(Path('input.yaml'))
for item in data['m1']:
    print(item)

que salidas:

1
2
[3, 4]
[5, 6]
7
8

Como puede ver, puede ver, en la secuencia que necesita acoplarse, puede usar un alias para una secuencia etiquetada o puede usar una secuencia etiquetada. YAML no te permite hacer:

- !flatten *x2

, es decir, etiquetar una secuencia anclada, ya que esto esencialmente la convertiría en una estructura de datos diferente.

Usar etiquetas explícitas es IMO mejor que tener algo de magia como con las claves de combinación YAML <<. Si nada más, ahora tiene que pasar por aros si tiene un archivo YAML con una asignación que tiene una clave <<que no desea que actúe como una clave de combinación, por ejemplo, cuando hace una asignación de operadores C a sus descripciones. en inglés (o en algún otro idioma natural).

Anthon
fuente
9

Si solo necesita combinar un elemento en una lista, puede hacerlo

fruit:
  - &banana
    name: banana
    colour: yellow

food:
  - *banana
  - name: carrot
    colour: orange

cuyos rendimientos

fruit:
  - name: banana
    colour: yellow

food:
  - name: banana
    colour: yellow
  - name: carrot
    colour: orange
Tamlyn
fuente
-4

Puede fusionar asignaciones y luego convertir sus claves en una lista, bajo estas condiciones:

  • si está utilizando plantillas jinja2 y
  • si el orden del artículo no es importante
some_stuff: &some_stuff
 a:
 b:
 c:

combined_stuff:
  <<: *some_stuff
  d:
  e:
  f:

{{ combined_stuff | list }}
sm4rk0
fuente
¿Qué hay de malo en esta respuesta? No me importan los votos negativos si se discuten. Conservaré la respuesta para las personas que puedan hacer uso de ella.
sm4rk0
3
Probablemente porque esta respuesta se basa en la plantilla jinja2, cuando la pregunta pide hacerlo en yml. jinja2 requiere un entorno Python, que es contraproducente si el OP está intentando SECAR. Además, muchas herramientas de CI / CD no aceptan un paso de plantilla.
Jorge Leitao
Gracias @JorgeLeitao. Eso tiene sentido. Aprendí YAML y Jinja2 juntos mientras desarrollaba manuales y plantillas de Ansible y no puedo pensar en uno sin otro
sm4rk0