pythonyamlruamel.yaml

How do I write out an anchor using ruamel.yaml?


I want to create data in python and write it to a file as a yaml-document using anchors and merges in the output.

I think this could be possible with ruamel YAML, because, as described in ruamels official examples:

This means ruamel.yaml must have an internal representation of the yaml-data that includes and understands anchors (unlike PyYAML, which only reads anchors but does not preserve them). There does not seem to be a documented way to create such anchors from python code.

The most minimal file I want to be able to create would look like this:

base: &ANCHOR
  x: 1

object:
  <<: *ANCHOR
  y: 2

Solution

  • When you round-trip your data using the following program:

    import sys  
    import ruamel.yaml
    
    yaml_str = """\ 
    base: &ANCHOR  
    x: 1
    
    object:  
    <<: \*ANCHOR  
    y: 2  
    """
    
    yaml = ruamel.yaml.YAML()  
    data = yaml.load(yaml_str)  
    yaml.dump(data, sys.stdout)
    

    you see that the output matches the input:

    base: &ANCHOR
      x: 1
    
    object:
      <<: *ANCHOR
      y: 2
    

    You could actually create a new YAML instance for dumping and still get the same output. This means that the information about anchors and merging is somewhere in the data structure under data. So you should inspect various items.

    print('anchor', data['base'].anchor)
    print('type', type(data['base'].anchor))
    print('keys', list(data['object'].keys()))
    print('merge', data['object'].merge)
    print('merge type', type(data['object'].merge))
    print('ids', id(data['object'].merge[0][1]), id(data['base']))
    

    which gives:

    anchor Anchor('ANCHOR')
    type <class 'ruamel.yaml.anchor.Anchor'>
    keys ['y', 'x']
    merge [(0, {'x': 1})]
    merge type <class 'list'>
    ids 4304434048 4304434048
    

    The above is normally an incremental process (even for me, having some knowledge about the internals). And it helps to look at the source, especially construct_mapping in constructor.py and CommentedMap in comments. py

    With the above information, lets first tackle the anchor ( you can have merges without anchor/alias, but they don't make much sense).

    import sys
    import ruamel.yaml
    
    def CM(**kw):
        return ruamel.yaml.comments.CommentedMap(**kw)
    
    common = CM(x=1)
    common.yaml_set_anchor('ANCHOR')
    data = CM(base=common, object=CM(tmp=common, y=2))
    yaml = ruamel.yaml.YAML()
    yaml.dump(data, sys.stdout)
    

    which gives:

    base: &ANCHOR
      x: 1
    object:
      tmp: *ANCHOR
      y: 2
    

    Creating the merge key, can be done by defining the CommentedMap with only the y key and adding the merge attribute:

    import sys
    import ruamel.yaml
    
    def CM(**kw):
        return ruamel.yaml.comments.CommentedMap(**kw)
    
    common = CM(x=1)
    common.yaml_set_anchor('ANCHOR')
    data = CM(base=common, object=CM(y=2))
    setattr(data['object'], ruamel.yaml.comments.merge_attrib, [(0, common)])
    yaml = ruamel.yaml.YAML()
    yaml.dump(data, sys.stdout)
    

    which gives:

    base: &ANCHOR
      x: 1
    object:
      <<: *ANCHOR
      y: 2
    
    1. There is no setter for the .merge attribute
    2. that attribute is a list of because in YAML the merge key can have have either a mapping or a list of mappings as value. IIRC the integer 0, determines the ordering.

    Pin the version of ruamel.yaml you are using. Internals like these will change without notice