2025, Oct 15 15:18

Как сохранить якоря, псевдонимы и слияния YAML в ruamel.yaml

Разбираем проблему с якорями и алиасами в ruamel.yaml при слиянии YAML: почему появляются id001 и смещаются якоря, как сохранить структуру и комментарии

Якоря и псевдонимы в YAML — удобный способ избежать дублирования, но они становятся хрупкими, когда инструменты неожиданно переписывают их. Если вы используете ruamel.yaml для изменения документа, в котором задействованы слияния и якоря, вы можете столкнуться с автосгенерированными именами псевдонимов вроде id001 или с тем, что якоря смещаются на другие узлы. Разберёмся, почему так происходит и как сохранить якоря нетронутыми, продолжая редактировать документ.

Постановка задачи

Допустим, нужно расширить YAML‑конфигурацию, сохранив комментарии и структуру. Следующий фрагмент Python добавляет нового клиента в отображение clients:

import ruamel.yaml
parser = ruamel.yaml.YAML()
parser.preserve_quotes = True
parser.indent(mapping=2, sequence=4, offset=2)
config_text = """---
# Some config file..
# This is the main clients config
clients:
  foobar1:
    name: foo Bar 1
    auth: false
    count: 1290
  barboz:
    name: BarraBoz
    auth: true
    count: 19
uids_default: &uids_default
  - attribute_email: mail
    datafile: vars1
uids:
  - <<: *uids_default
    skip: true
  - <<: *uids_default
    issuer: http://foo.bar
"""
new_client = {"adfs": 124423423, "123": "adsfadsfasdf", "name": "Dirk Van helst"}
doc = parser.load(config_text)
doc["clients"].insert(len(doc["clients"]), "vanhelst", new_client)
with open("/tmp/out.yml", "w") as out_f:
    parser.dump(doc, out_f)

Запись добавляется, комментарии сохраняются, но при этом в выводе появляются автоматически созданные псевдонимы и меняются места якорей — например, исходный якорь заменяется на id001 и переносится с последовательности на вложенное отображение.

Что именно идёт не так

В документе один якорь и два псевдонима. Якорь привязан к последовательности, которая содержит ровно одно отображение. Эту последовательность затем используют как значение ключа слияния <<. Когда ruamel.yaml сохраняет слияния, она «сплющивает» одноэлементную последовательность, из-за чего якорь переносится с последовательности на находящееся внутри неё отображение. Такое поведение связано с обработкой ключа слияния, которая не справляется с псевдонимами якорных последовательностей. Если бы в последовательности было два элемента, запись слияния выводилась бы как список псевдонимов после ключа слияния, например:

  - <<: [*id0001, *id0002]

В текущем виде одноэлементная последовательность сплющивается, и создаётся впечатление, что якорь был «потроган».

Решение

Уберите ненужную последовательность вокруг uids_default, если вы не используете этот список. Привяжите якорь напрямую к отображению и оставьте слияние, указывающее на это отображение. С такой правкой ruamel.yaml сохраняет якоря и псевдонимы как ожидается.

import sys
import ruamel.yaml
parser = ruamel.yaml.YAML()
parser.preserve_quotes = True
parser.indent(mapping=2, sequence=4, offset=2)
fixed_yaml = """# Some config file..
# This is the main clients config
clients:
  foobar1:
    name: foo Bar 1
    auth: false
    count: 1290
  barboz:
    name: BarraBoz
    auth: true
    count: 19
uids_default: &uids_default
    attribute_email: mail
    datafile: vars1
uids:
  - <<: *uids_default
    skip: true
  - <<: *uids_default
    issuer: http://foo.bar
"""
new_client = {"adfs": 124423423, "123": "adsfadsfasdf", "name": "Dirk Van helst"}
doc = parser.load(fixed_yaml)
doc["clients"].insert(len(doc["clients"]), "vanhelst", new_client)
parser.dump(doc, sys.stdout)

Результирующий YAML сохраняет якоря и слияния без изменений:

# Некоторый конфигурационный файл..
# Это основная конфигурация клиентов
clients:
  foobar1:
    name: foo Bar 1
    auth: false
    count: 1290
  barboz:
    name: BarraBoz
    auth: true
    count: 19
  vanhelst:
    adfs: 124423423
    '123': adsfadsfasdf
    name: Dirk Van helst
uids_default: &uids_default
  attribute_email: mail
  datafile: vars1
uids:
  - <<: *uids_default
    skip: true
  - <<: *uids_default
    issuer: http://foo.bar

Если вам действительно нужно слить несколько отображений, привяжите к каждому из них свой якорь и передайте последовательность псевдонимов как значение ключа слияния <<.

Почему это важно

Конфигурации, полагающиеся на якоря YAML, псевдонимы и ключ слияния, часто используются в автоматизации, CI‑конвейерах и коде, где важна стабильная структура. Тихая замена якорей на псевдонимы вида id001 или их перенос может «заШумить» диффы и сделать их хрупкими, а порой и запутать инструменты, ожидающие конкретную форму. Привязка слияний к якорным отображениям, а не к одноэлементным последовательностям, помогает избежать подобных переписок.

Замечания и планы

Это поведение считается багом в обработке слияний ruamel.yaml для псевдонимов якорных последовательностей. Ожидается исправление в следующем релизе (> 0.18.14). Кроме того, обратите внимание на выбор расширения файла: вы всё ещё используете .yml; загляните в FAQ по YAML на yaml.org и следуйте его рекомендации.

Заключение

При работе с ruamel.yaml и слияниями избегайте ситуации, когда якорится одноэлементная последовательность, а затем сливается именно она. Привязывайте якорь к самому отображению, чтобы ключ слияния указывал на отображение, а не на список. Если нужно слить несколько отображений, используйте разные якоря и передавайте после << последовательность псевдонимов. Так вы сохраните стабильность якорей, комментарии и убережёте ruamel.yaml от появления псевдонимов вида id001 при сериализации.

Статья основана на вопросе на StackOverflow от Dick Visser и ответе Anthon.