Как объединить массивы 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
Это не сработает
- Слияние поддерживается только спецификациями YAML для отображений, а не для последовательностей.
- Вы полностью смешиваете вещи, имея ключ слияния
<<
затем следует разделитель ключ / значение:
и значение, которое является ссылкой, а затем продолжить со списком на том же уровне отступа
Это не правильно 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, которая анализирует все массивы как
SequenceNode
s, и все значения как
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