Tool to automatically expand YAML merges?

Viewed 5218

I'm looking for a tool or process which can easily take a YAML file which contains anchors, aliases and merge keys and expand the aliases and merges out into a flat YAML file. There are still many commonly used YAML parses which don't fully support merging.

I'd like to be able to take advantage of merging to keep things DRY, but there are instances where this needs to then be built into a more verbose "flat" YAML file so that it can be used by other tooling which relies on incomplete YAML parsers.

Example Source YAML:

default: &DEFAULT
  URL: website.com
  mode: production  
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

development:
  <<: *DEFAULT
  URL: website.local
  mode: dev

test:
  <<: *DEFAULT
  URL: test.website.qa
  mode: test

Desired output YAML:

default:
  URL: website.com
  mode: production  
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

development:
  URL: website.local
  mode: dev
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600

test:
  URL: test.website.qa
  mode: test
  site_name: Website
  some_setting: h2i8yiuhef
  some_other_setting: 3600
3 Answers

UPDATE: 2019-03-13 12:41:05

  • This answer was modified pursuant to a comment by Anthon which correctly identified limitations with PyYAML. (See Pitfalls infra).

Context

  • YAML file
  • Python for parsing the YAML

Problem

  • User jtYamlEnthusiast wishes to output a non-DRY version of a YAML file with aliases, anchors, and merge keys.

Solution(s)

  • Alternative 1: use the ruamel library promoted by Anthon infra.
  • Alternative 2: use Python pprint.pformat and simply do a load/dump round-trip transformation.

Rationale

  • the ruamel library is great if you have the discretion to install another python library besides pyyaml, and you want a high degree of control over "round-trip" YAML transformations (such as the preservation of YAML comments, for example).
  • if you do not need rigorous control over round-tripped YAML, or you are limited for some other reason to pyyaml, you can simply load and dump YAML directly, in order to obtain the "non-DRY" output.

Pitfalls

  • as of this writing PyYAML has limitations relative to the ruamel library, regarding the handling of YAML v1.1 and YAML v1.2

  • See also

Example

    ##
    import pprint
    import yaml
    ##
    myrawyaml = '''
    default: &DEFAULT
      URL: website.com
      mode: production
      site_name: Website
      some_setting: h2i8yiuhef
      some_other_setting: 3600

    development:
      <<: *DEFAULT
      URL: website.local
      mode: dev

    test:
      <<: *DEFAULT
      URL: test.website.qa
      mode: test
    '''
    ##
    pynative  =   yaml.safe_load(myrawyaml)
    vout      =   pprint.pformat(pynative)
    print(vout)                             ##=> this is non-DRY and just happens to be well-formed YAML syntax
    print(yaml.safe_load(vout))             ##=> this proves we have well-formed YAML if it loads without exception

If you for some reason have a use case where you need to write the expanded YAML back to a file as YAML, you can:

  • Use @Anthon's answer. As noted above, though, this approach might not be feasible if you can't install packages.

  • Use @dreftymac's answer. It appears that this answer has worked for some people, but it didn't work for me; by my understanding, pprint.pformat returns the argument as a string of its Python representation, and yaml.safe_load expects the Python representation itself. Of course, you could eval the string returned by pprint.pformat, but using eval on even trusted input feels icky. (Again, the answer has a couple of upvotes so maybe I'm missing something here.)

Alternatively, you can do what I did:

import json
import yaml

def expand_yml(yml):
    return yaml.dump(json.loads(json.dumps(yml)))

expand_yml(my_yml_with_aliases)

Since JSON can (with some exceptions, such as aliases) be regarded as a strict subset of YAML, this approach should generally work. However, if performance is a concern, or if you're dealing with hairier YAML, this approach might not work for you.

Related