admin 管理员组

文章数量: 1086019

I have a config file that (purposely) contains some undefined aliases:

base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val

Meanwhile I have a cli that takes --foo and --bar as arguments, where --foo can be specified multiple times. I wind up with a dict looking like:

args = {}
args["foo"] = ["foo_val_1", "foo_val_2", "foo_val_3"]
args["bar"] = "bar_val"

I'd like to dump the args to yaml, but with custom anchors for each key in arg. Something like this:

args:
  foo: &args_foo
  - foo_val_1
  - foo_val_2
  - foo_val_3
  bar: &args_bar "bar_val"

I could write my own code to generate the doc, but I'd prefer somehow tell ruamel.yaml to handle adding the anchors before serializing. The question is, is that possible, and if so, how?

The intention is to then concatenate the two files like this:

args:
  foo: &args_foo
  - foo_val_1
  - foo_val_2
  - foo_val_3
  bar: &args_bar "bar_val"
base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val

and extract "base" as the final config after loading the concatenated doc.

I have a config file that (purposely) contains some undefined aliases:

base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val

Meanwhile I have a cli that takes --foo and --bar as arguments, where --foo can be specified multiple times. I wind up with a dict looking like:

args = {}
args["foo"] = ["foo_val_1", "foo_val_2", "foo_val_3"]
args["bar"] = "bar_val"

I'd like to dump the args to yaml, but with custom anchors for each key in arg. Something like this:

args:
  foo: &args_foo
  - foo_val_1
  - foo_val_2
  - foo_val_3
  bar: &args_bar "bar_val"

I could write my own code to generate the doc, but I'd prefer somehow tell ruamel.yaml to handle adding the anchors before serializing. The question is, is that possible, and if so, how?

The intention is to then concatenate the two files like this:

args:
  foo: &args_foo
  - foo_val_1
  - foo_val_2
  - foo_val_3
  bar: &args_bar "bar_val"
base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val

and extract "base" as the final config after loading the concatenated doc.

Share Improve this question edited Mar 30 at 9:31 Marc Swingler asked Mar 30 at 9:23 Marc SwinglerMarc Swingler 3331 silver badge11 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

Since you cannnot load your config file (as it is invalid YAML), the easiest IMO is to generate a a YAML file with the anchor definitions and append the config file to it.

I recommend to first round-trip the expected output:

import sys
import ruamel.yaml

yaml_str = """\
args:
  foo: &args_foo
  - foo_val_1
  - foo_val_2
  - foo_val_3
  bar: &args_bar "bar_val"
base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val
"""

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

which gives:

args:
  foo: &args_foo
  - foo_val_1
  - foo_val_2
  - foo_val_3
  bar: &args_bar bar_val
base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val

And then analysing data, especially for the special types that need to be created (see also this answer):

print('type', type(data['args']['foo']))
print('anchor', data['args']['foo'].anchor)
print('type', type(data['args']['bar']))
print('anchor', data['args']['foo'].anchor)

which gives:

type <class 'ruamel.yamlments.CommentedSeq'>
anchor Anchor('args_foo')
type <class 'ruamel.yaml.scalarstring.PlainScalarString'>
anchor Anchor('args_foo')

Your args data structure needs some elaboration:

args = {}
args["foo"] = map = ruamel.yamlments.CommentedSeq(["foo_val_1", "foo_val_2", "foo_val_3"])
map.yaml_set_anchor('args_foo', always_dump=True)
args["bar"] = scalar = ruamel.yaml.scalarstring.DoubleQuotedScalarString("bar_val")
scalar.yaml_set_anchor('args_bar', always_dump=True)
yaml.dump(args, sys.stdout)

which gives:

foo: &args_foo
- foo_val_1
- foo_val_2
- foo_val_3
bar: &args_bar "bar_val"

The always_dump is necessary, as spurious anchors are normally not dumped.

Now you can combine the above:

import sys
import io
import ruamel.yaml
from pathlib import Path

config = Path('config.nonyaml')
config.write_text("""\
base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val
""")

yaml = ruamel.yaml.YAML()

args = {}
args["foo"] = map = ruamel.yamlments.CommentedSeq(["foo_val_1", "foo_val_2", "foo_val_3"])
map.yaml_set_anchor('args_foo', always_dump=True)
args["bar"] = scalar = ruamel.yaml.scalarstring.DoubleQuotedScalarString("bar_val")
scalar.yaml_set_anchor('args_bar', always_dump=True)
buf = io.BytesIO()

yaml.dump(dict(args=args), buf)
buf.write(config.read_bytes())

print(buf.getvalue().decode('utf-8'))

# check if buf contains loadable YAML
data = yaml.load(buf.getvalue())
print('data:', data)

which gives:

args:
  foo: &args_foo
  - foo_val_1
  - foo_val_2
  - foo_val_3
  bar: &args_bar "bar_val"
base:
  foo: *args_foo
  bar: *args_bar
  fuz: fuz_val
  buz: buz_val

data: {'args': {'foo': ['foo_val_1', 'foo_val_2', 'foo_val_3'], 'bar': 'bar_val'}, 'base': {'foo': ['foo_val_1', 'foo_val_2', 'foo_val_3'], 'bar': 'bar_val', 'fuz': 'fuz_val', 'buz': 'buz_val'}}

本文标签: Add anchors to keys in dictionary before dumping wih ruamelyamlStack Overflow