используя construct_undefined в ruamel from_yaml
Я создаю собственный тег yaml MyTag. Он может содержать любой заданный допустимый yaml - карту, скаляр, привязку, последовательность и т. Д.
Как реализовать класс MyTag для моделирования этого тега, чтобы ruamel анализировал содержимое a !mytagточно так же, как он будет анализировать любой заданный yaml? MyTagЭкземпляра просто хранит независимо от разобранного результата содержания YAML является.
Следующий код работает, и утверждения должны точно демонстрировать, что они должны делать, и все они проходят.
Но я не уверен, работает ли он по правильным причинам. . . В частности, в from_yamlметоде класса используется commented_obj = constructor.construct_undefined(node)рекомендуемый способ достижения этой цели и потребляет 1 и только 1 из полученного генератора правильно? Это не просто случайно работает?
Должен ли я вместо этого использовать что-то вроде construct_object, или construct_mapили. . .? Примеры, которые мне удалось найти, как правило, знают, какой тип он конструирует, поэтому нужно либо использовать, construct_mapлибо construct_sequenceвыбрать тип объекта для создания. В этом случае я действительно хочу использовать обычный / стандартный синтаксический анализ ruamel для любого неизвестного типа, который там может быть, и просто сохранить его в своем собственном типе.
import ruamel.yaml
from ruamel.yaml.comments import CommentedMap, CommentedSeq, TaggedScalar
class MyTag():
yaml_tag = '!mytag'
def __init__(self, value):
self.value = value
@classmethod
def from_yaml(cls, constructor, node):
commented_obj = constructor.construct_undefined(node)
flag = False
for data in commented_obj:
if flag:
raise AssertionError('should only be 1 thing in generator??')
flag = True
return cls(data)
with open('mytag-sample.yaml') as yaml_file:
yaml_parser = ruamel.yaml.YAML()
yaml_parser.register_class(MyTag)
yaml = yaml_parser.load(yaml_file)
custom_tag_with_list = yaml['root'][0]['arb']['k2']
assert type(custom_tag_with_list) is MyTag
assert type(custom_tag_with_list.value) is CommentedSeq
print(custom_tag_with_list.value)
standard_list = yaml['root'][0]['arb']['k3']
assert type(standard_list) is CommentedSeq
assert standard_list == custom_tag_with_list.value
custom_tag_with_map = yaml['root'][1]['arb']
assert type(custom_tag_with_map) is MyTag
assert type(custom_tag_with_map.value) is CommentedMap
print(custom_tag_with_map.value)
standard_map = yaml['root'][1]['arb_no_tag']
assert type(standard_map) is CommentedMap
assert standard_map == custom_tag_with_map.value
custom_tag_scalar = yaml['root'][2]
assert type(custom_tag_scalar) is MyTag
assert type(custom_tag_scalar.value) is TaggedScalar
standard_tag_scalar = yaml['root'][3]
assert type(standard_tag_scalar) is str
assert standard_tag_scalar == str(custom_tag_scalar.value)
И пример ямла:
root:
- item: blah
arb:
k1: v1
k2: !mytag
- one
- two
- three-k1: three-v1
three-k2: three-v2
three-k3: 123 # arb comment
three-k4:
- a
- b
- True
k3:
- one
- two
- three-k1: three-v1
three-k2: three-v2
three-k3: 123 # arb comment
three-k4:
- a
- b
- True
- item: argh
arb: !mytag
k1: v1
k2: 123
# blah line 1
# blah line 2
k3:
k31: v31
k32:
- False
- string here
- 321
arb_no_tag:
k1: v1
k2: 123
# blah line 1
# blah line 2
k3:
k31: v31
k32:
- False
- string here
- 321
- !mytag plain scalar
- plain scalar
- item: no comment
arb:
- one1
- two2
Ответы
В YAML у вас могут быть якоря и псевдонимы, и вполне нормально, чтобы объект был дочерним по отношению к самому себе (используя псевдоним). Если вы хотите сбросить структуру данных Python data:
data = [1, 2, 4, dict(a=42)]
data[3]['b'] = data
он сбрасывается в:
&id001
- 1
- 2
- 4
- a: 42
b: *id001
а для этого нужны якоря и псевдонимы.
При загрузке такой конструкции ruamel.yaml рекурсивно переходит во вложенные структуры данных, но если узел верхнего уровня не привел к созданию реального объекта, на который можно сделать ссылку привязкой, рекурсивный лист не может разрешить псевдоним.
Чтобы решить эту проблему, используется генератор, за исключением скалярных значений. Сначала он создает пустой объект, затем рекурсирует и обновляет его значения. В коде, вызывающем конструктор, выполняется проверка, возвращается ли генератор, и в этом случае next()выполняется с данными, и потенциальная саморекурсия «разрешается».
Поскольку вы звоните construct_undefined(), вы всегда получаете генератор. Практически этот метод может возвращать значение, если обнаруживает скалярный узел (который, конечно, не может рекурсивно обрабатываться), но это не так. Если бы это было так, ваш код не смог бы загрузить следующий документ YAML:
!mytag 1
без модификаций, которые проверяют, получите ли вы генератор или нет, как это сделано в коде в ruamel.yaml, вызывая различные конструкторы, чтобы он мог обрабатывать оба construct_undefinedи, например construct_yaml_int(который не является генератором).