Как разобрать YAML с помощью PyYAML, если есть '!' в пределах YAML - PullRequest
0 голосов
/ 09 сентября 2018

У меня есть файл YAML, который я хотел бы проанализировать только для переменной description; однако я знаю, что восклицательные знаки в моем шаблоне CloudFormation (файл YAML) вызывают проблемы с PyYAML.

Я получаю следующую ошибку:

yaml.constructor.ConstructorError: could not determine a constructor for the tag '!Equals'

В файле много !Ref и !Equals. Как я могу игнорировать эти конструкторы и получить конкретную переменную, которую я ищу - в данном случае переменную description.

Ответы [ 2 ]

0 голосов
/ 09 сентября 2018

Если вам приходится иметь дело с документом YAML с несколькими разными тегами, и заинтересованы только в подмножестве их, вы все равно должны справиться со всеми Если элементы, в которые вы вставили, вложены в других теговых конструкциях вам, по крайней мере, нужно обрабатывать все «вложенные» теги должным образом.

Однако нет необходимости обрабатывать все теги по отдельности, вы может написать подпрограмму конструктора, которая может обрабатывать отображения, последовательности и скаляры регистрируют это в SafeLoader PyYAML, используя:

import yaml

inp = """\
MyEIP:
  Type: !Join [ "::", [AWS, EC2, EIP] ]
  Properties:
    InstanceId: !Ref MyEC2Instance
"""

description = []

def any_constructor(loader, tag_suffix, node):
    if isinstance(node, yaml.MappingNode):
        return loader.construct_mapping(node)
    if isinstance(node, yaml.SequenceNode):
        return loader.construct_sequence(node)
    return loader.construct_scalar(node)

yaml.add_multi_constructor('', any_constructor, Loader=yaml.SafeLoader)

data = yaml.safe_load(inp)
print(data)

, что дает:

{'MyEIP': {'Type': ['::', ['AWS', 'EC2', 'EIP']], 'Properties': {'InstanceId': 'MyEC2Instance'}}}

(inp также может быть файл, открытый для чтения).

Как вы видите выше, также будет работать, если в вашем коде появится неожиданный тег !Join, а также любой другой тег, такой как !Equal. Теги просто сброшены.

Поскольку в YAML нет переменных, то немного догадаться, что Вы имеете в виду «нравится анализировать только переменную описания». Если это имеет явный тег (например, !Description), вы можете отфильтровать значения, добавив 2-3 строки any_constructor, сопоставив параметр tag_suffix.

    if tag_suffix == u'!Description':
        description.append(loader.construct_scalar(node))

Однако более вероятно, что в отображении есть некоторый ключ, который является скаляром description, и что вас интересует значение, связанное с этим ключом.

    if isinstance(node, yaml.MappingNode):
        d = loader.construct_mapping(node)
        for k in d:
        if k == 'description':
            description.append(d[k])
        return d

Если вы знаете точную позицию в иерархии данных, вы можете конечно же пройдитесь по data структуре и извлеките все, что вам нужно на основе ключей или списка позиций. Особенно в этом случае вы бы лучше используя мой ruamel.yaml, это может загрузить тегированный YAML в режиме туда и обратно без дополнительные усилия (при условии выше inp):

from ruamel.yaml import YAML

with YAML() as yaml:
    data = yaml.load(inp)
0 голосов
/ 09 сентября 2018

Вы можете определить собственные конструкторы, используя пользовательские yaml.SafeLoader

import yaml

doc = '''
Conditions: 
  CreateNewSecurityGroup: !Equals [!Ref ExistingSecurityGroup, NONE]
'''

class Equals(object):
    def __init__(self, data):
        self.data = data
    def __repr__(self):
        return "Equals(%s)" % self.data

class Ref(object):
    def __init__(self, data):
        self.data = data
    def __repr__(self):
        return "Ref(%s)" % self.data

def create_equals(loader,node):
    value = loader.construct_sequence(node)
    return Equals(value)

def create_ref(loader,node):
    value = loader.construct_scalar(node)
    return Ref(value)

class Loader(yaml.SafeLoader):
    pass

yaml.add_constructor(u'!Equals', create_equals, Loader)
yaml.add_constructor(u'!Ref', create_ref, Loader)
a = yaml.load(doc, Loader)
print(a)

Выходы:

{'Conditions': {'CreateNewSecurityGroup': Equals([Ref(ExistingSecurityGroup), 'NONE'])}}
...