Rezney
Rezney

Reputation: 371

Replacing strings in YAML using Python

I have the following YAML:

instance:
  name: test
  flavor: x-large
  image: centos7

tasks:
  centos-7-prepare:
    priority: 1
    details::
      ha: 0
      args:
        template: &startup
          name: startup-centos-7
          version: 1.2
        timeout: 1800

  centos-7-crawl:
    priority: 5
    details::
      ha: 1
      args:
        template: *startup
        timeout: 0

The first task defines template name and version, which is then used by other tasks. Template definition should not change, however others especially task name will.

What would be the best way to change template name and version in Python?

I have the following regex for matching (using re.DOTALL):

template:.*name: (.*?)version: (.*?)\s

However did not figure out re.sub usage so far. Or is there any more convenient way of doing this?

Upvotes: 1

Views: 3006

Answers (2)

Evan Snapp
Evan Snapp

Reputation: 543

I would parse the yaml file into a dictionary, and the edit the field and write the dictionary back out to yaml.

See this question for discussion on parsing yaml in python How can I parse a YAML file in Python but I think you would end up with something like this.

from ruamel.yaml import YAML
from io import StringIO

yaml=YAML(typ='safe')
yaml.default_flow_style = False

#Parse from string
myConfig = yaml.load(doc)
#Example replacement code
for task in myConfig["tasks"]:
    if myConfig["tasks"][task]["details"]["args"]["template"]["name"] == "&startup":
        myConfig["tasks"][task]["details"]["args"]["template"]["name"] = "new value"
#Convert back to string
buf = StringIO()
yaml.dump(myConfig, buf)
updatedYml = buf.getvalue()

Upvotes: 2

Anthon
Anthon

Reputation: 76578

For this kind of round-tripping (load-modify-dump) of YAML you should be using ruamel.yaml (disclaimer: I am the author of that package).

If your input is in input.yaml, you can then relatively easily find the name and version under key template and update them:

import sys
import ruamel.yaml

def find_template(d):
    if isinstance(d, list):
        for elem in d:
            x = find_template(elem)
            if x is not None:
                return x
    elif isinstance(d, dict):
        for k in d:
            v = d[k]
            if k == 'template':
                if 'name' in v and 'version' in v:
                    return v
            x = find_template(v)
            if x is not None:
                return x
    return None


yaml = ruamel.yaml.YAML()
# yaml.indent(mapping=4, sequence=4, offset=2)
yaml.preserve_quotes = True

with open('input.yaml') as ifp:
    data = yaml.load(ifp)
template = find_template(data)
template['name'] = 'startup-centos-8'
template['version'] = '1.3'

yaml.dump(data, sys.stdout)

which gives:

instance:
  name: test
  flavor: x-large
  image: centos7

tasks:
  centos-7-prepare:
    priority: 1
    'details:':
      ha: 0
      args:
        template: &startup
          name: startup-centos-8
          version: '1.3'
        timeout: 1800

  centos-7-crawl:
    priority: 5
    'details:':
      ha: 1
      args:
        template: *startup
        timeout: 0

Please note that the (superfluous) quotes that I inserted in the input, as well as the comment and the name of the alias are preserved.

Upvotes: 3

Related Questions