How to dump YAML with explicit references?
<p>Recursive references work great in <code>ruamel.yaml</code> or <code>pyyaml</code>: </p>

<pre><code>$ ruamel.yaml.dump(ruamel.yaml.load('&amp;A [ *A ]'))
'&amp;id001
- *id001'
</code></pre>

<p>However it (obviously) does not work on normal references: </p>

<pre><code>$ ruamel.yaml.dump(ruamel.yaml.load("foo: &amp;foo { a: 42 }\nbar: { &lt;&lt;: *foo }"))
bar: {a: 42}
foo: {a: 42}
</code></pre>

<p>I would like is to explicitly create a reference:</p>

<pre><code>data = {}
data['foo'] = {'foo': {'a': 42}}
data['bar'] = { '&lt;&lt;': data['foo'], 'b': 43 }

$ ruamel.yaml.dump(data, magic=True)
foo: &amp;foo
    a: 42
bar: 
    &lt;&lt;: *foo
    b: 43
</code></pre>

<p>This will be very useful to generate YAML output of large data structures that have lots of common keys</p>

<p>How is it possible without disputable re.replace on the output?</p>

<p>Actually the result of <code>ruamel.yaml.dump(data)</code> is</p>

<pre><code>bar:
  '&lt;&lt;': &amp;id001
    foo:
      a: 42
  b: 43
foo: *id001
</code></pre>

<p>So I need to replace <code>'&lt;&lt;'</code> with <code>&lt;&lt;</code> and maybe replace <code>id001</code> with <code>foo</code>.</p>

<p>If you want to create something like that, at least in ruamel.yaml Â¹, you should use round-trip mode, which also preserves the merges. The following doesn't throw an assertion error:</p>

<pre><code>import ruamel.yaml

yaml_str = """\
foo: &amp;xyz
  a: 42
bar:
  &lt;&lt;: *xyz
"""

data = ruamel.yaml.round_trip_load(yaml_str)
assert ruamel.yaml.round_trip_dump(data) == yaml_str
</code></pre>

<p>What this means is that <code>data</code> has enough information to recreate the merge as it was in the output. In practise however, in round-trip mode, the merge never takes place. Instead retrieving a value <code>data['foo']['bar']['a']</code> means that there is no real key <code>'bar'</code> in <code>data['foo']</code>, but that that key is subsequently looked up in the attached "merge mappings".</p>

<p>There is no public interface for this (so things might change), but by analyzing <code>data</code> and looking at <code>ruamel.yaml.comments.CommentedMap()</code> you can find that there is a <code>merge_attrib</code> (currently being the string <code>_yaml_merge</code>) and more useful that there is a method <code>add_yaml_merge()</code>. The latter takes a list of (int, CommentedMap()) tuples.</p>

<pre><code>baz = ruamel.yaml.comments.CommentedMap()
baz['b'] = 196
baz.yaml_set_anchor('klm')
data.insert(1, 'baz', baz)
</code></pre>

<p>you need to insert the <code>'baz'</code> key before the <code>'bar'</code> key of data, otherwise the mapping will reverse. After insert the new structure in the merge for <code>data['bar']</code>:</p>

<pre><code>data['bar'].add_yaml_merge([(0, baz)])
ruamel.yaml.round_trip_dump(data, sys.stdout)
</code></pre>

<p>which gives:</p>

<pre><code>foo: &amp;xyz
  a: 42
baz: &amp;klm
  b: 196
bar:
  &lt;&lt;: [*xyz, *klm]
</code></pre>

<p>( if you like to see what <code>add_yaml_merge</code> does insert</p>

<pre><code>print(getattr(data['bar'], ruamel.yaml.comments.merge_attrib))
</code></pre>

<p>before and after the call)</p>

<p>If you want to start from scratch completely you can do:</p>

<pre><code>data = ruamel.yaml.comments.CommentedMap([
    ('foo', ruamel.yaml.comments.CommentedMap([('a', 42)])),
    ])
data['foo'].yaml_set_anchor('xyz')
data['bar'] = bar = ruamel.yaml.comments.CommentedMap()
bar.add_yaml_merge([(0, data['foo'])])
</code></pre>

<p>instead of the <code>data = ruamel.yaml.round_trip_load(yaml_str)</code>.</p>

<hr>

<p>Â¹ <sub>Disclaimer: I am the author of that package.</sub></p>

python yaml pyyaml ruamel.yaml