Как объединить массивы YAML?

Я хотел бы объединить массивы в YAML и загрузить их через ruby ​​-

some_stuff: &some_stuff
 - a
 - b
 - c

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

Я хотел бы иметь объединенный массив как [a,b,c,d,e,f]

Я получаю сообщение об ошибке: не удалось найти ожидаемый ключ при анализе сопоставления блоков

Как объединить массивы в YAML?

7 ответов

Если целью является выполнение последовательности команд оболочки, вы можете добиться этого следующим образом:

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

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Это эквивалентно:

some_stuff: "a\nb\nc"

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

Я использовал это на своем gitlab-ci.yml (для ответа @rink.attendant.6 комментарий к вопросу).


Рабочий пример, который мы используем для поддержки requirements.txt имея частные репозитории из 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...

где requirements_test.txt содержит, например,

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

Это не сработает

  1. Слияние поддерживается только спецификациями YAML для отображений, а не для последовательностей.
  2. Вы полностью смешиваете вещи, имея ключ слияния << затем следует разделитель ключ / значение : и значение, которое является ссылкой, а затем продолжить со списком на том же уровне отступа

Это не правильно YAML:

combine_stuff:
  x: 1
  - a
  - b

Таким образом, ваш пример синтаксиса даже не будет иметь смысла в качестве предложения по расширению YAML.

Если вы хотите сделать что-то подобное, попробуйте сделать значение для combined_stuff некоторый специальный тип, который вы указываете с помощью тега YAML, чтобы экземпляр объекта для этого типа знал, как интерпретировать элементы и заменить этот один элемент всеми из some_stuff,

контекст

Этот пост предполагает следующий контекст:

  • Python 2.7
  • синтаксический анализатор Python YAML

проблема

lfender6445 желает объединить два или более списков в файле YAML, и эти проанализированные списки будут отображаться как единый список при разборе.

Решение

Это можно получить, просто назначив привязки YAML для отображений, где требуемые списки появляются в качестве дочерних элементов отображений. В приведенном ниже примере у нас есть три отображения (list_one, list_two, list_three) и три якоря и псевдонимы, которые относятся к этим сопоставлениям, где это необходимо.

Когда файл YAML загружен в программу, мы получаем список, который нам нужен, но он может потребовать небольшой модификации после загрузки (см. Подводные камни ниже).

пример

Оригинальный файл YAML

  list_one: &id001
   - а
   - б
   - с

  list_two: &id002
   - е
   - ф
   - г

  list_three: &id003
   - ч
   - я
   - j

  list_combined:
      - *id001
      - *id002
      - *id003

Результат после YAML.safe_load

## list_combined
  [
    [
      "А",
      "Б",
      "С"
    ],
    [
      "Е",
      "Е",
      "г"
    ],
    [
      "час",
      "я",
      "J"
    ]
  ]

Ловушки

  • При таком подходе создается вложенный список списков, который может не соответствовать желаемому результату, но может быть подвергнут последующей обработке с использованием метода flatten
  • обычные оговорки к якорям и псевдонимам YAML относятся к уникальности и порядку объявления

Заключение

Этот подход позволяет создавать объединенные списки с использованием псевдонима и функции привязки YAML.

Хотя выходной результат представляет собой вложенный список списков, его можно легко преобразовать с помощью flatten метод.

Смотрите также

Если вам нужно объединить только один элемент в список, вы можете сделать

fruit:
  - &banana
    name: banana
    colour: yellow

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

который дает

fruit:
  - name: banana
    colour: yellow

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

Другой способ включить объединение массивов в python - определить !flattenтег.(Здесь используется PyYAML, в отличие от ответа Антона выше. Это может быть необходимо в тех случаях, когда у вас нет контроля над тем, какой пакет используется в резервной копии, например anyconfig).

      import yaml

yaml.add_constructor("!flatten", construct_flat_list)

def flatten_sequence(sequence: yaml.Node) -> Iterator[str]:
    """Flatten a nested sequence to a list of strings
        A nested structure is always a SequenceNode
    """
    if isinstance(sequence, yaml.ScalarNode):
        yield sequence.value
        return
    if not isinstance(sequence, yaml.SequenceNode):
        raise TypeError(f"'!flatten' can only flatten sequence nodes, not {sequence}")
    for el in sequence.value:
        if isinstance(el, yaml.SequenceNode):
            yield from flatten_sequence(el)
        elif isinstance(el, yaml.ScalarNode):
            yield el.value
        else:
            raise TypeError(f"'!flatten' can only take scalar nodes, not {el}")

def construct_flat_list(loader: yaml.Loader, node: yaml.Node) -> List[str]:
    """Make a flat list, should be used with '!flatten'

    Args:
        loader: Unused, but necessary to pass to `yaml.add_constructor`
        node: The passed node to flatten
    """
    return list(flatten_sequence(node))

Это рекурсивное сглаживание использует структуру документа PyYAML, которая анализирует все массивы как SequenceNodes, и все значения как ScalarNodeс. Поведение можно проверить (и изменить) в следующей тестовой функции.

      import pytest
def test_flatten_yaml():
    # single nest
    param_string = """
    bread: &bread
      - toast
      - loafs
    chicken: &chicken
      - *bread
    midnight_meal: !flatten
      - *chicken
      - *bread
    """
    params = yaml.load(param_string)
    assert sorted(params["midnight_meal"]) == sorted(
        ["toast", "loafs", "toast", "loafs"]
    )

Вы можете объединить сопоставления, а затем преобразовать их ключи в список при следующих условиях:

  • если вы используете Jinja2 шаблонов и
  • если заказ товара не важен
some_stuff: &some_stuff
 a:
 b:
 c:

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

{{ combined_stuff | list }}

Вот как я реализую это в файле docker-compose.yml .

Часть «список слияния» находится в разделе переменных среды. Я думаю, что сложность (и интересная часть) заключается в том, что я смог использовать ссылку на *snapshots-repository-path в &es-env-base.

В общем - предпочитают использовать длинный синтаксис более короткий синтаксис , поскольку он позволяет использовать YAML *references.

      version: "3.7"

### ------------------------------------------------------------------
### Variables
### ------------------------------------------------------------------
x-variables:
  exposed-port: &exposed-port 9200
  es-base: &es-base
    image: docker.elastic.co/elasticsearch/elasticsearch:7.9.1
    ulimits:
      memlock:
        soft: -1
        hard: -1
    networks:
      - elastic
  data-path: &data-path /usr/share/elasticsearch/data      
  snapshots-repository-path: &snapshots-repository-path /usr/share/elasticsearch/backup
  volume-snapshots-repository: &volume-snapshots-repository 
    - type: volume
      source: snapshots-repository
      target: *snapshots-repository-path  
  services-es-env: &es-env-base
    "cluster.name": "es-docker-cluster"
    "cluster.initial_master_nodes": "es01,es02"
    "bootstrap.memory_lock": "true"
    "ES_JAVA_OPTS": "-Xms512m -Xmx512m"
    "ELASTIC_PASSWORD": "esbackup-password"
    "xpack.security.enabled": "true"
    "path.repo": *snapshots-repository-path
### ------------------------------------------------------------------

services:
  es01: # master
    <<: *es-base
    container_name: es01
    environment:
      <<: *es-env-base
      node.name: es01
      discovery.seed_hosts: es02
    volumes:
      - <<: *volume-snapshots-repository 
      - type: volume
        source: data01
        target: *data-path
    ports:
      - published: *exposed-port
        target: 9200
        protocol: tcp
        mode: host

  es02:
    <<: *es-base
    container_name: es02
    environment:
      <<: *es-env-base
      node.name: es02
      discovery.seed_hosts: es01
    volumes:
      - <<: *volume-snapshots-repository 
      - type: volume
        source: data02
        target: *data-path

volumes:
  data01:
    driver: local
  data02:
    driver: local
  snapshots-repository:
    driver: local

networks:
  elastic:
    driver: bridge
Другие вопросы по тегам