Warm tip: This article is reproduced from stackoverflow.com, please click
google-cloud-build google-cloud-platform python ruamel.yaml yaml

How to set list with strings as a yaml value while preserving quotes?

发布于 2020-04-23 12:36:20

I have code like this:

import ruamel.yaml
from ruamel.yaml.scalarstring import DoubleQuotedScalarString as dq    
yaml = ruamel.yaml.YAML()
yaml.indent(sequence=2)
yaml.preserve_quotes = True
yaml.default_flow_style=None

CF2_cloudbuild = {
            'steps':[
            {'name': dq("gcr.io/cloud-builders/gcloud"),
            'args': ["functions", "deploy", "publish_resized"],
            'timeout': dq("1600s")}
            ]
            }

with open("file.yaml", 'w') as fp:
    yaml.dump(CF2_cloudbuild, fp)

and this is the content of file.yaml:

steps:
- name: "gcr.io/cloud-builders/gcloud"
  args: [functions, deploy, publish_resized]
  timeout: "1600s"

and I need:

steps:
- name: "gcr.io/cloud-builders/gcloud"
  args: ["functions", "deploy", "publish_resized"]
  timeout: "1600s"

in order to get format compliant with the GCP documentation concerning build configuration files GCP Build configuration overview docs

How to obtain that?
When I try to use [dq("functions"), dq("deploy"), dq("publish_resized")] functionality I get:

steps:
- name: "gcr.io/cloud-builders/gcloud"
  args:
  - "functions"
  - "deploy"
  - "publish_resized"
  timeout: "1600s"

which I think is not the same as ["functions", "deploy", "publish_resized"].

Questioner
Wojtek
Viewed
52
Anthon 2020-02-09 15:46

As @Stephen Rauch indicates, the two outputs are equivalent, the one you "need" has a sequence in flow style and the one you get is a sequence in block style. Any YAML parser should load that in the same way. And if you don't explicitly add the double quotes, ruamel.yaml will add them if they are needed (e.g. to prevent the string true from being loaded as a boolean).

But since you set .default_flow_style you are right to expect a leaf node in the YAML output to be flow-style, and you might have hit on a bug in ruamel.yaml's round-tripdumper.

When ruamel.yaml loads your expected output, then it preserves

import sys
import ruamel.yaml

yaml_str = """
steps:
- name: "gcr.io/cloud-builders/gcloud"
  args: ["functions", "deploy", "publish_resized"]
  timeout: "1600s"
"""

yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True

data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

which gives:

steps:
- name: "gcr.io/cloud-builders/gcloud"
  args: ["functions", "deploy", "publish_resized"]
  timeout: "1600s"

This is because the mapping and sequence nodes are not loaded as dict resp. list, butsubclasses thereof, that keep information about their original flow/block style.

You can emulate this by constructing that subclass for your list:

from ruamel.yaml.scalarstring import DoubleQuotedScalarString as dq
from ruamel.yaml.comments import CommentedSeq

def cs(*elements):
     res = CommentedSeq(*elements)
     res.fa.set_flow_style()
     return res


CF2_cloudbuild = {
            'steps':[
            {'name': dq("gcr.io/cloud-builders/gcloud"),
            'args': cs(dq(l) for l in ["functions", "deploy", "publish_resized"]),
            'timeout': dq("1600s")}
            ]
            }

yaml.dump(CF2_cloudbuild, sys.stdout)

which gives:

steps:
- name: "gcr.io/cloud-builders/gcloud"
  args: ["functions", "deploy", "publish_resized"]
  timeout: "1600s"

But, again, if the YAML parser that cloudbuilder software uses is conformant, neither the flow style, nor any of the double quotes are necessary in your example. And you can rely on ruamel.yaml on adding the latter if they are necessary.