TL:DR The YAML - {{ name }}
is not valid Python because it uses a mapping/dictionary as a key for another dictionary. It is a compact way to write - { { name: null } : null }
See @Anthon's answer for how to handle this.
At first glance, using ruamel.yaml
(not the derivative ruamel_yaml
package) to generate the following output
- {{ name }}: "{% include 'ethnicity.jinja2' with context %}"
does not seem possible because this is not valid YAML it specifies a mapping as a key to another mapping, which in Python would mean a dictionary would need to be the key of another dictionary, and this is not allowed because dictionaries are not hashable.
Given a file name template.yml
that contains
- {{ name }}: "{% include 'ethnicity.jinja2' with context %}"
age: 43
hobbies: "{% include 'hobbies.jinja2' with context %}"
I tried creating a round trip like so
import io [9/48]
from ruamel.yaml import YAML
import ruamel.yaml
def round_trip(path, yaml_type="safe"):
yaml=YAML(typ=yaml_type)
with open(path) as doc:
yaml_obj = yaml.load(doc)
yaml.default_flow_style = False
yaml_fd = io.StringIO()
yaml.dump(yaml_obj, yaml_fd)
return yaml_fd.getvalue(), yaml_obj
## YAML types from https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree/main.py#l53
# 'rt'/None -> RoundTripLoader/RoundTripDumper, (default)
# 'safe' -> SafeLoader/SafeDumper,
# 'unsafe' -> normal/unsafe Loader/Dumper
# 'base' -> baseloader
TYPES = ["safe", "rt", "base", "unsafe"]
PATH = "template.yml"
for t in TYPES:
print(t, "==========\n", sep="\n")
try:
out, obj = round_trip(PATH, t)
except ruamel.yaml.constructor.ConstructorError as err:
print(str(err), end="\n\n")
continue
print(obj, end="\n\n")
print(type(obj), end="\n\n")
which generated this output
safe
==========
while constructing a mapping
in "template.yml", line 1, column 3
found unhashable key
in "template.yml", line 1, column 4
rt
==========
[ordereddict([(ordereddict([(ordereddict([('name', None)]), None)]), "{% include 'ethnicity.jinja2' with context %}"), ('age', 43), ('hobbies', "{% include 'hobbies.jinja2' with context %}")])]
<class 'ruamel.yaml.comments.CommentedSeq'>
base
==========
while constructing a mapping
in "template.yml", line 1, column 3
found unhashable key
in "template.yml", line 1, column 4
unsafe
==========
while constructing a mapping
in "template.yml", line 1, column 3
found unhashable key
in "template.yml", line 1, column 4
Hence, using YAML(typ="rt")
did not raise an error when parsing the input.
I tried recreating the output from scratch with
>>> fd = io.StringIO()
>>> yaml.dump(ordereddict([(CommentedKeyMap(ordereddict([(CommentedKeyMap(ordereddict(name=None)), None)])), "value")]), fd)
>>> print(fd.getvalue())
!!omap
- {{name: null}: null}: value
which showed that it was trying to interpret {{ name }}
as an implicit mapping with default values of null
. This is allowed in the YAML spec, as a mapping can be a key for another mapping object. However, this is not allowed in plain Python because a dictionary is not hashable and therefore cannot be the key in another dictionary. In otherwords, this is invalid
# Raises TypeError: unhashable type: 'dict'
map_of_maps = {{"name": "val1"}: "val2"}
ruamel.yaml
gets around this by wrapping the mapping in a CommentedKeyMap
class, which is hashable.
I also tried parsing your expected YAML file with the Online YAML Parser, but that tool only supports YAML 1.1 as pointed out by @Anthon in the comments. (It also gave an error).