Как подключить фильтр в парсере PyYAML?

У меня есть файл YAML, который я хочу разобрать.

По нескольким причинам я хочу запретить использование точки . в привязках или просто заменить ее на _ во время синтаксического анализа.

Просто я хочу пойти от этого:

foo:
    bar.baz:
        - egg
        - spam

к тому, что:

foo:
    bar_baz:
        - egg
        - spam

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

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


person JohnW    schedule 14.11.2016    source источник
comment
Что вы считаете именами узлов? В YAML 1.2 есть узлы, но у них нет имени в соответствии со спецификацией (то же самое верно и для более старого YAML 1.1, который может обрабатывать PyYAML). Вы называете тег именем узла или привязкой? Или вы говорите о сопоставлении ключей как имен узлов? Было бы хорошо, если бы вы привели небольшой пример YAML-файла (желательно с версией, трансформированной вручную). Все это (привязка, тег, ключ) может быть изменено на лету, но требует другого программирования.   -  person Anthon    schedule 14.11.2016
comment
Обновлено. Проблема проста, я просто надеюсь, что крючок тоже будет. :)   -  person JohnW    schedule 14.11.2016
comment
Это ключ пар ключ-значение в сопоставлении Node. Loader потребуется альтернатива Constructor, и это не так просто сделать правильно.   -  person Anthon    schedule 14.11.2016


Ответы (1)


Не существует простого механизма замены ключа в виде какого-то хука, через который передается каждый ключ сопоставления (в любом случае, вам может понадобиться больше контекста, чем просто наличие ключа). Существуют различные подходы к решению этой проблемы:

  • вы можете создать новый Loader, который будет иметь свой собственный подкласс Constructor, который выполняет преобразование ключей сопоставления. Это IMO правильное решение, поскольку оно не влияет на загрузку других YAML. Тем не менее, это также один из самых сложных правил
  • вы можете добавить новый конструктор для отображений в используемый вами загрузчик, тем самым переопределив существующий. Это повлияет на всю будущую загрузку дальнейших файлов YAML, если вы не сделаете ничего особенного.
  • вы можете обернуть существующий конструктор сопоставления, загрузить свой YAML и переместить оригинал обратно. Это не влияет на загрузку других файлов YAML.

Последнее из них может быть выполнено с помощью:

import sys
import ruamel.yaml

yaml_str = """\
foo:
    bar.baz:
        - egg
        - spam
"""


def alt_construct_mapping(self, *args, **kw):
    """replace keys with dot"""
    m = self.org_construct_mapping(*args, **kw)
    for k in m:
        if '.' in k:
            m[k.replace('.', '_')] = m.pop(k)
    return m

# backup up the constructor
ruamel.yaml.constructor.BaseConstructor.org_construct_mapping = \
    ruamel.yaml.constructor.BaseConstructor.construct_mapping

# replace the constructor
ruamel.yaml.constructor.BaseConstructor.construct_mapping = alt_construct_mapping


data = ruamel.yaml.safe_load(yaml_str)
ruamel.yaml.round_trip_dump(data, sys.stdout)

# put original constructor back
ruamel.yaml.constructor.BaseConstructor.construct_mapping = \
    ruamel.yaml.constructor.BaseConstructor.org_construct_mapping

который дает:

foo:
  bar_baz:
  - egg
  - spam

Это было сделано с помощью ruamel.yaml, расширенной версии PyYAML, автором которой я являюсь. автор. Для PyYAML это должно работать так же хорошо, пока в вашем YAML нет конструкций YAML версии 1.2, замените ruamel.yaml на yaml, а round_trip_load/dump на safe_load/dump.

person Anthon    schedule 14.11.2016