I'm trying to create a YAML file from some inputs passed to a Jinja template. I want certain input chained keys be optional and used in the templating if present, but otherwise ignored. In the example below, override.source.property
may or may not exist in the input file, and override
is a top level key when it is present. Is there a way to optionally retrieve a.certain.key
by its full chained path via Jinja templating?
# without_override.yaml
name: blah
# with_override.yaml
name: blah
overrides:
source:
property: something
# template.yaml.jinja
name: {{ name }}
source.property: {{ overrides.source.property or "property of " + name }}
source.property3: {{ overrides.source.property | default("property of " + name) }}
{# is there a way to provide a full path and return a value if it exists?
# top level document reference?
source.property2: {{ self.get("overrides.source.property") or "property of " + name }}
#}
# renderer.py
import yaml
import sys
from jinja2 import Environment, StrictUndefined, ChainableUndefined
def render_jinja(template, context):
# jinja_env = Environment(extensions=["jinja2.ext.do"], undefined=StrictUndefined)
jinja_env = Environment(extensions=["jinja2.ext.do"], undefined=ChainableUndefined)
template_obj = jinja_env.from_string(template)
return template_obj.render(**context).strip()
if __name__ == "__main__":
with open(sys.argv[1]) as f:
config = yaml.safe_load(f.read())
with open("template.yaml.jinja") as f:
template = f.read()
print(render_jinja(template, config))
# python renderer.py with_override.yaml
# using StrictUndefined or ChainedUndefined RETURNS
name: blah
source.property: something
source.property3: something
# python renderer.py without_override.yaml
# with ChainedUndefined RETURNS
name: blah
source.property: property of blah
source.property3: property of blah
# with StrictUndefined ERRORS
jinja2.exceptions.UndefinedError: 'overrides' is undefined
You are doing what you are asking when you use the paramater undefined=ChainableUndefined
. Without that parameter passed to the Jinja2 environment, the line below would simply raise an exception (jinja2.exceptions.UndefinedError: 'overrides' is undefined
) when trying to access a variable that is undefined.
source.property: {{ overrides.source.property | default("property of " + name) }}
You can even chain several default()
until a variable is defined or variables that evaluate to false.
{{ overrides.source.property | default(defaults.source.property) | default("property of " + name) }}
If you need to do something more complex you can always use Python itself and pass the processed dict to the template renderer. Like set default values and then check if there is an overridden value:
default_values = {
"source": {
"property": config.get("overrides", {})
.get("source", {})
.get("property", "something")
}
}