Reputation: 18889
Is there a way to use placeholders in YAML like this:
foo: &FOO
<<propname>>:
type: number
default: <<default>>
bar:
- *FOO
propname: "some_prop"
default: "some default"
Upvotes: 104
Views: 326455
Reputation: 365
With Yglu Structural Templating, your example can be written:
foo: !()
!? $.propname:
type: number
default: !? $.default
bar:
!apply .foo:
propname: "some_prop"
default: "some default"
Disclaimer: I am the author of Yglu.
Upvotes: 10
Reputation: 32430
yaml.load
Consider the following example YAML. It is well-formed YAML syntax, however it uses (non-standard) curly-brace placeholders with embedded expressions.
The embedded expressions do not produce the desired result in YAML, because they are not part of the native YAML specification. Nevertheless, they are used in this example only to help illustrate what is available with standard YAML and what is not.
part01_customer_info:
cust_fname: "Homer"
cust_lname: "Himpson"
cust_motto: "I love donuts!"
cust_email: [email protected]
part01_government_info:
govt_sales_taxrate: 1.15
part01_purchase_info:
prch_unit_label: "Bacon-Wrapped Fancy Glazed Donut"
prch_unit_price: 3.00
prch_unit_quant: 7
prch_product_cost: "{{prch_unit_price * prch_unit_quant}}"
prch_total_cost: "{{prch_product_cost * govt_sales_taxrate}}"
part02_shipping_info:
cust_fname: "{{cust_fname}}"
cust_lname: "{{cust_lname}}"
ship_city: Houston
ship_state: Hexas
part03_email_info:
cust_email: "{{cust_email}}"
mail_subject: Thanks for your DoughNutz order!
mail_notes: |
We want the mail_greeting to have all the expected values
with filled-in placeholders (and not curly-braces).
mail_greeting: |
Greetings {{cust_fname}} {{cust_lname}}!
We love your motto "{{cust_motto}}" and we agree with you!
Your total purchase price is {{prch_total_cost}}
Below is an inline image that illustrates the example with colored regions in green, yellow and red.
The substitutions marked in GREEN are readily available in standard YAML, using anchors, aliases, and merge keys.
The substitutions marked in YELLOW are technically available in standard YAML, but not without a custom type declaration, or some other binding mechanism.
The substitutions marked in RED are not available in standard YAML. Yet there are workarounds and alternatives; such as through string formatting or string template engines (such as python's str.format
).
Templates with variable placeholders is a frequently-requested YAML feature.
Routinely, developers want to cross-reference content in the same YAML file or transcluded YAML file(s).
YAML supports anchors and aliases, but this feature does not support arbitrary placement of placeholders and expressions anywhere in the YAML text. They only work with YAML nodes.
YAML also supports custom type declarations, however these are less common, and there are security implications if you accept YAML content from potentially untrusted sources.
There are YAML extension libraries, but these are not part of the native YAML spec.
sprintf
or str.format
style functionality from the hosting languageUpvotes: 205
Reputation: 884
I wanted to achieve templating in yaml
files as well and I found dreftymac's answer really helpful as a starting point. After researching and coding for few hours this is my answer, please let me know if/how I can improve this.
I am not doing anything too special, I try to leverage python's string templating syntax and abuse the string format method a little. So it's all python's string templating and substitution that is doing the magic here. I have modified the way dreftymac's answer templated his yaml
file to use as an example.
part01_customer_info:
cust_fname: "Homer"
cust_lname: "Himpson"
cust_motto: "I love donuts!"
cust_email: [email protected]
part01_government_info:
govt_sales_taxrate: 1.15
part01_purchase_info:
prch_unit_label: "Bacon-Wrapped Fancy Glazed Donut"
prch_unit_price: 3.00
prch_unit_quant: 7
prch_product_cost: "eval!#{part01_purchase_info[prch_unit_price]} * {part01_purchase_info[prch_unit_quant]}"
prch_total_cost: "eval!#{part01_purchase_info[prch_product_cost]} * {part01_government_info[govt_sales_taxrate]}"
part02_shipping_info:
cust_fname: "{part01_customer_info[cust_fname]}"
cust_lname: "{part01_customer_info[cust_lname]}"
ship_city: Houston
ship_state: Hexas
part03_email_info:
cust_email: "{part01_customer_info[cust_email]}"
mail_subject: Thanks for your DoughNutz order!
mail_notes: |
We want the mail_greeting to have all the expected values
with filled-in placeholders (and not curly-braces).
mail_greeting: |
Greetings {part01_customer_info[cust_fname]} {part01_customer_info[cust_lname]}!
We love your motto "{part01_customer_info[cust_motto]}" and we agree with you!
Your total purchase price is {part01_purchase_info[prch_total_cost]}
I have changed
{{}}
to{}
and addedeval!#
which is an identifier
from pprint import pprint
import yaml
EVAL_IDENTIFIER = "eval!#"
def eval_math_expr(val):
if val.startswith(EVAL_IDENTIFIER):
val = val.replace(EVAL_IDENTIFIER, "")
val = eval(val)
return val
def str_template_substitute(full, val=None, initial=True):
val = val or full if initial else val
if isinstance(val, dict):
for k, v in val.items():
val[k] = str_template_substitute(full, v, False)
elif isinstance(val, list):
for idx, i in enumerate(val):
val[idx] = str_template_substitute(full, i, False)
elif isinstance(val, str):
# NOTE:
# Templating shouldn't be confused or tasked with extra work.
# I am attaching evaluation to string substitution here,
# just to prove this can be done.
val = eval_math_expr(val.format(**full))
return val
data = yaml.load(open('./data.yml'))
str_template_substitute(data)
pprint(data)
Note: This function is pretty powerful as this can work on dictionaries which is what JSON/YAML and many other formats convert to in python.
Upvotes: 3