解析嵌套的自定义Yaml标签

时间:2018-08-16 18:29:32

标签: python yaml pyyaml

我有一些带有特定于应用程序的标签的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}']]}

因此功能所需的部分就在那儿,而当我在功能中需要它们时就不存在。

2 个答案:

答案 0 :(得分:2)

您很近,但是问题是您正在使用方法 construct_yaml_seq()。该方法实际上是已注册 常规YAML序列的构造函数(最终使 Python列表),然后调用construct_sequence()方法来处理 传入的节点,这也是您应该做的。

当您返回无法处理递归数据的字符串时 结构,您无需使用两步创建过程(首先 yield-先填写,然后填写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()

答案 1 :(得分:1)

您需要更改:

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}'}