Разбирать вложенные пользовательские теги yaml

У меня есть yaml с тегами, специфичными для приложения (если быть точным, из шаблона AWS Cloud Formation), который выглядит так:

example_yaml = "Name: !Join [' ', ['EMR', !Ref 'Environment', !Ref 'Purpose']]"

Я хочу разобрать его, чтобы я мог это сделать:

>>> print(result)
>>> {'Name': 'EMR {Environment} {Purpose}'}

>>> name = result['name'].format(
...    Environment='Development',
...    Purpose='ETL'
... )
>>> print(name)
>>> EMR Development ETL

В настоящее время мой код выглядит так:

import yaml
from pprint import pprint


def aws_join(loader, node):
    join_args = loader.construct_yaml_seq(node)
    delimiter = list(join_args)[0]
    joinables = list(join_args)[1]
    join_result = delimiter.join(joinables)
    return join_result

def aws_ref(loader, node):
    value = loader.construct_scalar(node)
    placeholder = '{'+value+'}'
    return placeholder

yaml.add_constructor('!Join', aws_join)
yaml.add_constructor('!Ref', aws_ref)

example_yaml = "Name: !Join [' ', ['EMR', !Ref 'Environment', !Ref 'Purpose']]"

pprint(yaml.load(example_yaml))

К сожалению, это приводит к ошибке.

...
   joinables = list(join_args)[1]
IndexError: list index out of range

Добавление print('What I am: '+str(join_args)) к aws_join показывает, что я получаю генератор:

What I am: <generator object SafeConstructor.construct_yaml_seq at 0x1082ece08>

Вот почему я попытался преобразовать генератор в список. Генератор в конечном итоге заполняется правильно, но я не успеваю его использовать. Если я изменю свою функцию aws_join на такую:

def aws_join(loader, node):
    join_args = loader.construct_yaml_seq(node)
    return join_args

Тогда окончательный результат будет выглядеть так:

{'Name': [' ', ['EMR', '{Environment}', '{Purpose}']]}

Так что необходимые части моей функции есть, но не тогда, когда они мне нужны в моей функции.


person user554481    schedule 16.08.2018    source источник


Ответы (2)


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

Поскольку вы возвращаете строку, которая не может работать с рекурсивными структурами данных, вам не нужно использовать двухэтапный процесс создания (сначала yield-ing, затем заполнение), которому следует метод construct_yaml_seq(). Но именно из-за этого двухэтапного процесса создания вы столкнулись с генератором.

construct_sequence возвращает простой список, но поскольку вы хотите, чтобы узлы под !Join были доступны при запуске обработки, обязательно укажите параметр deep=True, иначе второй элемент списка будет пустым. И поскольку construct_yaml_seq() не указывает deep=True, вы не получили фрагменты вовремя в своей функции (иначе вы могли бы фактически использовать этот метод).

import yaml
from pprint import pprint


def aws_join(loader, node):
    join_args = loader.construct_sequence(node, deep=True)
    # you can comment out next line
    assert join_args == [' ', ['EMR', '{Environment}', '{Purpose}']] 
    delimiter = join_args[0]
    joinables = join_args[1]
    return delimiter.join(joinables)

def aws_ref(loader, node):
    value = loader.construct_scalar(node)
    placeholder = '{'+value+'}'
    return placeholder

yaml.add_constructor('!Join', aws_join, Loader=yaml.SafeLoader)
yaml.add_constructor('!Ref', aws_ref, Loader=yaml.SafeLoader)

example_yaml = "Name: !Join [' ', ['EMR', !Ref 'Environment', !Ref 'Purpose']]"

pprint(yaml.safe_load(example_yaml))

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

{'Name': 'EMR {Environment} {Purpose}'}

Вы не должны использовать load(), это потенциально небезопасно, и, прежде всего: здесь нет необходимости. Зарегистрируйтесь в SafeLoader и позвоните safe_load()

person Anthon    schedule 16.08.2018

Вам нужно изменить:

def aws_join(loader, node):
    delimiter = loader.construct_scalar(node.value[0])
    value = loader.construct_sequence(node.value[1])
    return delimiter.join(value)

Тогда вы получите результат:

{'Name': 'EMR {Environment} {Purpose}'}
person Nuts    schedule 16.08.2018