Come unire gli array YAML?


113

Vorrei unire gli array in YAML e caricarli tramite ruby ​​-

some_stuff: &some_stuff
 - a
 - b
 - c

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

Mi piacerebbe avere l'array combinato come [a,b,c,d,e,f]

Ricevo l'errore: non ho trovato la chiave prevista durante l'analisi di una mappatura dei blocchi

Come unisco gli array in YAML?


6
Perché vuoi farlo in YAML piuttosto che nella lingua con cui lo stai analizzando?
Patrick Collins

7
per asciugare la duplicazione in un file yaml molto grande
lfender6445

4
Questa è una pessima pratica. Dovresti leggere yaml separatamente, mettere insieme gli array in Ruby, quindi riscriverlo a yaml.
sawa

74
Come sta cercando di essere asciutto una cattiva pratica?
krak3n

13
@PatrickCollins Ho trovato questa domanda cercando di ridurre la duplicazione nel mio file .gitlab-ci.yml e sfortunatamente non ho alcun controllo sul parser utilizzato da GitLab CI :(
rink.attendant.6

Risposte:


41

Se lo scopo è eseguire una sequenza di comandi della shell, potresti essere in grado di farlo come segue:

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

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Questo è equivalente a:

some_stuff: "a\nb\nc"

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

L'ho usato sul mio gitlab-ci.yml(per rispondere a @ rink.attendant.6 commento alla domanda).


Esempio funzionante che utilizziamo per supportare la requirements.txtpresenza di repository privati ​​da gitlab:

.pip_git: &pip_git
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "ssh://git@gitlab.com"
- 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...

dove requirements_test.txtcontiene ad es

-e git+ssh://git@gitlab.com/example/example.git@v0.2.2#egg=example


3
Intelligente. Lo sto usando nella nostra pipeline Bitbucket ora. Grazie
Dariop

* Il trattino finale non è richiesto qui, è sufficiente solo il tubo alla fine. * Questa è una soluzione inferiore poiché quando il lavoro fallisce su un'istruzione multilinea molto lunga non è chiaro quale comando abbia fallito.
Mina Luke

1
@MinaLuke, inferiore a cosa? Nessuna delle risposte attuali fornisce un modo per unire due elementi usando solo yaml ... Inoltre, non c'è nulla nella domanda che affermi che l'OP desidera usarlo in CI / CD. Infine, quando viene utilizzato in CI / CD, la registrazione dipende solo dal particolare CI / CD utilizzato, non dalla dichiarazione yaml. Quindi, semmai, il CI / CD a cui ti riferisci è quello che fa un brutto lavoro. Lo yaml in questa risposta è valido e risolve il problema di OP.
Jorge Leitao

@ JorgeLeitao immagino che tu lo usi per combinare le regole. Potete fornire un esempio funzionante di gitlabci? Ho provato qualcosa in base alla tua soluzione, ma ricevo sempre un errore di convalida.
niels

@niels, ho aggiunto un esempio con un esempio funzionante di gitlabci. Nota che alcuni IDE contrassegnano questo yaml come non valido, anche se non lo è.
Jorge Leitao il

26

Aggiornamento: 2019-07-01 14:06:12

  • Nota : un'altra risposta a questa domanda è stata sostanzialmente modificata con un aggiornamento sugli approcci alternativi .
    • Quella risposta aggiornata menziona un'alternativa alla soluzione alternativa in questa risposta. È stato aggiunto alla sezione Vedere anche di seguito.

Contesto

Questo post presuppone il seguente contesto:

  • python 2.7
  • parser YAML Python

Problema

lfender6445 desidera unire due o più elenchi in un file YAML e fare in modo che quegli elenchi uniti appaiano come un unico elenco quando vengono analizzati.

Soluzione (soluzione alternativa)

Questo può essere ottenuto semplicemente assegnando ancoraggi YAML alle mappature, dove le liste desiderate appaiono come elementi figli delle mappature. Tuttavia, ci sono delle avvertenze (vedere "Insidie" infra).

Nell'esempio seguente abbiamo tre mappature ( list_one, list_two, list_three) e tre ancore e alias che si riferiscono a queste mappature ove appropriato.

Quando il file YAML viene caricato nel programma, otteniamo l'elenco che desideriamo, ma potrebbe richiedere una piccola modifica dopo il caricamento (vedere le insidie ​​di seguito).

Esempio

File YAML originale

  list_one: & id001
   - a
   - b
   - c

  list_two: & id002
   - e
   - f
   - g

  list_three: & id003
   - h
   - io
   - j

  list_combined:
      - * id001
      - * id002
      - * id003

Risultato dopo YAML.safe_load

## list_combined
  [
    [
      "un",
      "b",
      "c"
    ],
    [
      "e",
      "f",
      "g"
    ],
    [
      "h",
      "io",
      "j"
    ]
  ]

Insidie

  • questo approccio produce un elenco annidato di elenchi, che potrebbe non essere l'esatto output desiderato, ma questo può essere post-elaborato utilizzando il metodo flatten
  • i soliti avvertimenti per gli ancoraggi e gli alias YAML si applicano per l'unicità e l'ordine di dichiarazione

Conclusione

Questo approccio consente la creazione di elenchi uniti utilizzando l'alias e la funzione di ancoraggio di YAML.

Sebbene il risultato dell'output sia un elenco nidificato di elenchi, questo può essere facilmente trasformato utilizzando il flattenmetodo.

Guarda anche

Approccio alternativo aggiornato di @Anthon

Esempi del flattenmetodo


21

Questo non funzionerà:

  1. l'unione è supportata solo dalle specifiche YAML per le mappature e non per le sequenze

  2. stai mescolando completamente le cose avendo una chiave di unione << seguita dal separatore chiave / valore :e un valore che è un riferimento e poi continui con un elenco allo stesso livello di rientro

Questo non è corretto YAML:

combine_stuff:
  x: 1
  - a
  - b

Quindi la tua sintassi di esempio non avrebbe nemmeno senso come proposta di estensione YAML.

Se vuoi fare qualcosa come l'unione di più array potresti prendere in considerazione una sintassi come:

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

dove s1, s2, s3sono ancore sulle sequenze (non mostrate) che si desidera unire in una nuova sequenza e quindi avere la d, ee f allegati a questo. Ma YAML sta risolvendo prima questo tipo di strutture in profondità, quindi non è disponibile un contesto reale durante l'elaborazione della chiave di unione. Non sono disponibili array / elenchi a cui allegare il valore elaborato (la sequenza ancorata).

Puoi adottare l'approccio proposto da @dreftymac, ma questo ha l'enorme svantaggio di dover in qualche modo sapere quali sequenze annidate appiattire (ovvero conoscendo il "percorso" dalla radice della struttura dati caricata alla sequenza genitore), o che si percorra ricorsivamente la struttura dati caricata alla ricerca di array / elenchi annidati e li si appiattisca indiscriminatamente.

Una soluzione migliore IMO sarebbe quella di utilizzare i tag per caricare strutture di dati che eseguono l'appiattimento per te. Ciò consente di indicare chiaramente cosa deve essere appiattito e cosa no e ti dà il pieno controllo sul fatto che questo appiattimento venga effettuato durante il caricamento o durante l'accesso. Quale scegliere è una questione di facilità di implementazione ed efficienza nel tempo e nello spazio di archiviazione. Questo è lo stesso compromesso che deve essere fatto per implementare la funzionalità chiave di unione e non esiste un'unica soluzione che sia sempre la migliore.

Ad esempio, la mia ruamel.yamllibreria utilizza la forza bruta merge-dict durante il caricamento quando si utilizza il suo safe-loader, il che si traduce in dizionari uniti che sono normali dict di Python. Questa fusione deve essere eseguita in anticipo e duplica i dati (spazio inefficiente) ma è veloce nella ricerca del valore. Quando si utilizza il caricatore di andata e ritorno, si desidera essere in grado di eseguire il dump delle unioni non unite, quindi è necessario tenerle separate. Il dict come la struttura dati caricata come risultato del caricamento di andata e ritorno, è efficiente in termini di spazio ma più lento nell'accesso, poiché deve provare a cercare una chiave non trovata nel dict stesso nelle unioni (e questa non è memorizzata nella cache, quindi deve essere fatto ogni volta). Ovviamente tali considerazioni non sono molto importanti per file di configurazione relativamente piccoli.


Quanto segue implementa uno schema simile alla fusione per gli elenchi in Python usando oggetti con tag flatten che ricorsero al volo in elementi che sono elenchi e contrassegnati toflatten. Usando questi due tag puoi avere il file YAML:

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

(l'uso di sequenze in stile flusso e blocco è completamente arbitrario e non ha alcuna influenza sul risultato caricato).

Quando si itera sugli elementi che sono il valore per la chiave, m1questo "ricorre" nelle sequenze contrassegnate con toflatten, ma visualizza altri elenchi (con alias o meno) come un singolo elemento.

Un modo possibile con il codice Python per ottenere ciò è:

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)

quali uscite:

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

Come puoi vedere puoi vedere, nella sequenza che deve essere appiattita, puoi usare un alias per una sequenza con tag oppure puoi usare una sequenza con tag. YAML non ti permette di fare:

- !flatten *x2

, vale a dire contrassegnare una sequenza ancorata, in quanto ciò la trasformerebbe essenzialmente in una struttura dati diversa.

L'uso di tag espliciti è IMO migliore che avere un po 'di magia come con le chiavi di unione YAML <<. Se non altro ora devi passare attraverso i cerchi se ti capita di avere un file YAML con una mappatura che ha una chiave <<che non vuoi che si comporti come una chiave di unione, ad esempio quando fai una mappatura degli operatori C alle loro descrizioni in inglese (o qualche altra lingua naturale).


9

Se hai solo bisogno di unire un elemento in un elenco puoi farlo

fruit:
  - &banana
    name: banana
    colour: yellow

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

che produce

fruit:
  - name: banana
    colour: yellow

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

-4

Puoi unire le mappature quindi convertire le loro chiavi in ​​un elenco, alle seguenti condizioni:

  • se stai usando jinja2 templating e
  • se l'ordine dell'articolo non è importante
some_stuff: &some_stuff
 a:
 b:
 c:

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

{{ combined_stuff | list }}

Cosa c'è di sbagliato in questa risposta? Non mi importa dei voti negativi se vengono discussi. Terrò la risposta per le persone che possono farne uso.
sm4rk0

3
Probabilmente perché questa risposta si basa su jinja2 templating, quando la domanda chiede di farlo in yml. jinja2 richiede un ambiente Python, che è controproducente se l'OP sta cercando di DRY. Inoltre, molti strumenti CI / CD non accettano una fase di creazione di modelli.
Jorge Leitao

Grazie @JorgeLeitao. Questo ha senso. Ho imparato YAML e Jinja2 insieme durante lo sviluppo di playbook e modelli Ansible e non riesco a pensare a uno senza l'altro
sm4rk0
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.