1

I am trying to use anchored data passed thru a constructor in an alias however the alias wants to use the pre-constructor data.

I've taken inspiration from anthon's Is there a way to construct an object using PyYAML construct_mapping after all nodes complete loading? but still found no joy.

Below is some sample code:

class L2D(dict):
    def __repr__(self):
        return('L2D({})'.format(dict.__repr__(self)))

def l2d_constructor(loader, node):
    print("constructing")
    instance = L2D.__new__(L2D)
    yield instance
    state = loader.construct_sequence(node, deep=True)
    instance.__init__(state)

yaml.add_constructor(u'!l2d', l2d_constructor)

print(yaml.load('''
a: !l2d
  - [e, f]
  - [g, h]
'''))

print("============")

print(yaml.load('''
a: &other !l2d
  - [e, f]
  - [g, h]
b:
  <<: *other
  a: b
  c: d
'''))

The first load works but while I'd expect the second loads output to be

constructing
{'a': L2D({'g': 'h', 'e': 'f'}), 'b': {'a': 'b', 'g': 'h', 'e': 'f', 'c': 'd'}}

instead I get

constructing
Traceback (most recent call last):
  File "test2.py", line 41, in <module>
    '''))
  File "/tmp/tmp.1oRXCix7X3/venv/lib/python3.5/site-packages/ruamel/yaml/main.py", line 86, in load
    return loader.get_single_data()
  File "/tmp/tmp.1oRXCix7X3/venv/lib/python3.5/site-packages/ruamel/yaml/constructor.py", line 56, in get_single_data
    return self.construct_document(node)
  File "/tmp/tmp.1oRXCix7X3/venv/lib/python3.5/site-packages/ruamel/yaml/constructor.py", line 65, in construct_document
    for dummy in generator:
  File "/tmp/tmp.1oRXCix7X3/venv/lib/python3.5/site-packages/ruamel/yaml/constructor.py", line 494, in construct_yaml_map
    value = self.construct_mapping(node)
  File "/tmp/tmp.1oRXCix7X3/venv/lib/python3.5/site-packages/ruamel/yaml/constructor.py", line 265, in construct_mapping
    self.flatten_mapping(node)
  File "/tmp/tmp.1oRXCix7X3/venv/lib/python3.5/site-packages/ruamel/yaml/constructor.py", line 240, in flatten_mapping
    % subnode.id, subnode.start_mark)
ruamel.yaml.constructor.ConstructorError: while constructing a mapping
  in "<unicode string>", line 8, column 3:
      <<: *other
      ^ (line: 8)
expected a mapping for merging, but found sequence
  in "<unicode string>", line 5, column 5:
      - [e, f]
        ^ (line: 5)

The constructing print suggests that the constructor has done it's work but my suspicion is that the alias it trying to get the data from the unaltered yaml tree rather then the resulting data from the constructor.

Is there any way I can make this work?

Community
  • 1
  • 1
tommyvn
  • 713
  • 5
  • 12

1 Answers1

0

In order to use the merge feature of YAML your anchored "type" needs to be a mapping (Python dict) and the key/value pairs of that mapping are inserted in the other mapping at the point where you do:

<<: *other

Your anchored type is a sequence, and that is not allowed when using the merge feature.

You should review the merge documentation, where you can see that the anchored type always are mappings.

Anthon
  • 69,918
  • 32
  • 186
  • 246
  • I understand how the mapping feature works, the `!l2d` tag and `l2d_constructor` function changes the `a` sequence to a dict as you can see in the resulting python object. My hope was that the constructor function's mutation from sequence to mapping would mean I could treat the `a` value like a mapping in the yaml but I'm increasingly thinking that by that stage it's too late in the parsing, that `<<` is processed before `!l2d` and the corresponding constructor function. – tommyvn Feb 24 '17 at 20:33
  • Yes, the `<<` interpreting is done by the parser, it takes the sequence not what the tag made out of that. One thing you could do, is change the `<<` into something else that is unique (e.g. `>>`) and tag the mapping to load a dict like object, that interprets the value for `>>`. Or you could replace the merge facility in `constructor.py` to do the right thing. – Anthon Feb 24 '17 at 21:39