Формат дампа PyYAML

Я знаю, что есть несколько вопросов об этом на SO, но я не мог найти то, что искал.

Я использую pyyaml для чтения (.load()) a .yml файла, изменения или добавления ключа, а затем снова напишите (.dump()). Проблема в том, что я хочу сохранить формат файла после дампа, но он изменяется.

Например, я редактирую клавишу en.test.index.few, чтобы сказать "Bye" вместо "Hello"

Python:

with open(path, 'r', encoding = "utf-8") as yaml_file:
    self.dict = pyyaml.load(yaml_file)

Затем, изменив ключ:

with open(path, 'w', encoding = "utf-8") as yaml_file:
    dump = pyyaml.dump(self.dict, default_flow_style = False, allow_unicode = True, encoding = None)
    yaml_file.write( dump )

YAML:

До:

en:
  test:
    new: "Bye"
    index:
      few: "Hello"
  anothertest: "Something"

После:

en:
  anothertest: Something
  test:
    index:
      few: Hello
    new: Bye

Есть ли способ сохранить тот же формат?, например, qoutes и order. Использую ли я для этого неправильный инструмент?

Я знаю, возможно, исходный файл не совсем корректен, но я не контролирую его (это файл Ruby on Rails i18n).

Большое спасибо.

Ответы

Ответ 1

Вместо этого используйте ruamel.yaml.

Борьба с библиотекой! Сказка о двух библиотеках

PyYAML эффективно мертв и уже несколько лет. Что касается сложных вопросов, официальный проект дома в http://pyyaml.org, похоже, недавно был снят. На этом сайте размещен трекер, документация и загрузки PyYAML. На момент написания статьи все исчезло. Это не что иное, как бедствие. Добро пожаловать в другой день в open-source.

ruamel.yaml поддерживается . В отличие от PyYAML, ruamel.yaml поддерживает:

  • YAML <= 1,2. PyYAML поддерживает только YAML <= 1.1. Это важно, так как YAML 1.2 намеренно прерывает обратную совместимость с YAML 1.1 в нескольких случаях. Обычно это плохо. В этом случае это делает YAML 1.2 строгим надмножеством JSON. Поскольку YAML 1.1 не является строгим надмножеством JSON, это хорошо.
  • Сохранение в кавычках. При вызове yaml.dump() для дампа словаря, загруженного предыдущим вызовом, в yaml.load():
    • PyYAML наивно игнорирует все форматирование ввода - включая комментарии, заказы, цитаты и пробелы. Отброшено, как и многие цифровые отходы, в ближайшее доступное ведро бит.
    • ruamel.yaml умело уважает все форматирование ввода. Все. Вся стилистическая энчилада. Весь литературный шебанг. Все.

Миграция библиотеки: следы следов кода

Так как ruamel.yaml является файлом PyYAML и, следовательно, соответствует API PyYAML, переход с PyYAML на ruamel.yaml в существующих приложениях обычно такой же простой, как и замена всех экземпляров этого:

# This imports PyYAML. Stop doing this.
import yaml

... с этим:

# This imports "ruamel.yaml". Always do this.
from ruamel import yaml

Что это.

Никаких других изменений не требуется. Функции yaml.load() и yaml.dump() должны продолжать вести себя так, как ожидалось, с дополнительными преимуществами теперь поддерживая YAML 1.2 и активно получая исправления ошибок.

Сохранение заворота и то, что он может сделать для вас

Для обратной совместимости с PyYaml функции yaml.load() и yaml.dump() по умолчанию не поддерживают сохранение в обратном направлении. Для этого явно передайте:

  • Необязательный параметр ключевого слова Loader=ruamel.yaml.RoundTripLoader для yaml.load().
  • Необязательный параметр Dumper=ruamel.yaml.RoundTripDumper для ключевого слова yaml.dump().

Пример любезно "заимствован" из ruamel.yaml documentation:

import ruamel.yaml

inp = """\
# example
name:
  # Yet another Great Duke of Hell. He not so bad, really.
  family: TheMighty
  given: Ashtaroth
"""

code = ruamel.yaml.load(inp, Loader=ruamel.yaml.RoundTripLoader)
code['name']['given'] = 'Astarte'  # Oh no you didn't.

print(ruamel.yaml.dump(code, Dumper=ruamel.yaml.RoundTripDumper), end='')

Сделано. Комментарии, упорядочение, цитирование и пробелы теперь будут сохранены.

TL;DR

Всегда используйте ruamel.yaml. Никогда не используйте PyYAML. ruamel.yaml живет. PyYAML - это зловонный труп, гниющий на измельченном землетрясении PyPi.

Долго жить ruamel.yaml.

Ответ 2

Первая

Для представления данных словаря используется следующий код:

mapping = list(mapping.items())
    try:
        mapping = sorted(mapping)
    except TypeError:
        pass

Именно поэтому порядок упорядочивается

Второй

Информация о том, как был представлен скалярный тип (с двойной кавычкой или нет), теряется при чтении (это основной подход библиотеки)

Резюме

Вы можете создать собственный класс на основе "Dumper" и перегрузить метод "Represent_mapping" для изменения поведения, как будет представлен словарь

Для сохранения информации о двойных кавычках для скаляра вы также должны создать собственный класс на основе "Loader", но я боюсь, что это повлияет на другие классы и будет делать это трудно.

Ответ 3

В моем случае я хочу ", если значение содержит { или }, в противном случае ничего. Например:

 en:
   key1: value is 1
   key2: 'value is {1}'

Чтобы выполнить это, скопируйте функцию represent_str() из файла representer.py в модуле PyYaml и используйте другой стиль, если строка содержит { или }:

def represent_str(self, data):
    tag = None
    style = None
    # Add these two lines:
    if '{' in data or '}' in data:
        style = '"'
    try:
        data = unicode(data, 'ascii')
        tag = u'tag:yaml.org,2002:str'
    except UnicodeDecodeError:
        try:
            data = unicode(data, 'utf-8')
            tag = u'tag:yaml.org,2002:str'
        except UnicodeDecodeError:
            data = data.encode('base64')
            tag = u'tag:yaml.org,2002:binary'
            style = '|'
    return self.represent_scalar(tag, data, style=style)

Чтобы использовать его в своем коде:

import yaml

def represent_str(self, data):
  ...

yaml.add_representer(str, represent_str)

В этом случае нет различий между клавишами и значениями и этого достаточно для меня. Если вам нужен другой стиль для клавиш и значений, выполните одно и то же с функцией represent_mapping