yaes

Yet Another Expansion Syntax (pronounced ‘Yasssss Kweeeeen’) for expanding complex data (YAML / JSON) with Jinja2 templating

If a block has no control keywords, everything is emitted as is:

import yaes

block = {
    "ya": "{{ a }}"
}

values = {
    "a": "sure"
}

list(yaes.each(block, values))
# [
#     ({"ya": "{{ a }}"}, {"a": "sure"})
# ]

The behavior is the same if you send a list of blocks:

list(yaes.each([block], values))
# [
#     ({"ya": "{{ a }}"}, {"a": "sure"})
# ]

requires

If a requires keyword is present, all the keys listed must be in values for the block to emitted:

blocks = [
    {
        "name": "one",
        "requires": "a"
    },
    {
        "name": "twp",
        "requires": ["a", "b"]
    }
]

values = {
    "a": "sure"
}

list(yaes.each(blocks, values))
# [
#     ({"name": "one"}, {"a": "sure"})
# ]

Note

requires can be a str or list of str.

This is useful for modules like opengui, where we don’t want to evaluate the conditions on some fields unless other fields in those conditions actually have values.

transpose

If a transpose keyword is present, it’ll use the key pairs to transpose the values:

blocks = [
    {
        "name": "one",
        "transpose": {
            "b": "a"
        }
    }
]

values = {
    "a": "sure"
}

list(yaes.each(blocks, values))
# [
#     ({"name": "one"}, {"a": "sure", "b": "sure"})
# ]

Note

you can have multiple values to transpose

This is useful if you’re re-using a template that uses veriables and you want to replace them with your usage’s specific variables.

iterate

If a iterate keyword is present, it’ll use the key pairs to iterate new values:

blocks = [
    {
        "name": "{{ fruit }}",
        "iterate": {
            "fruit": "fruits"
        }
    }
]

values = {
    "fruits": [
        "apple",
        "pear",
        "orange"
    ]
}

list(yaes.each(blocks, values))
# [
#     (
#         {
#             "name": "{{ fruit }}"
#         },
#         {
#             "fruit": "apple",
#             "fruits": [
#                 "apple",
#                 "pear",
#                 "orange"
#             ]
#         }
#     ),
#     (
#         {
#             "name": "{{ fruit }}"
#         },
#         {
#             "fruit": "pear",
#             "fruits": [
#                 "apple",
#                 "pear",
#                 "orange"
#             ]
#         }
#     ),
#     (
#         {
#             "name": "{{ fruit }}"
#         },
#         {
#             "fruit": "orange",
#             "fruits": [
#                 "apple",
#                 "pear",
#                 "orange"
#             ]
#         }
#     )
# ]

Note

you can have multiple values to iterate, and it’ll iterate over the different pairs alphabetically by key

This is useful with opengui as you can take the values of a multi option field and use those values to create a new field for each option selected.

condition

If a condition keyword is present, it’ll only emit the block if the condition evaluates True:

blocks = [
    {
        "name": "one",
        "condition": "{? a == 1 ?}"
    },
    {
        "name": "two",
        "condition": "{? a == 2 ?}"
    }
]

values = {
    "a": 1
}

list(yaes.each(blocks, values))
# [
#     ({"name": "one"}, {"a": 1})
# ]

Note

make sure you use ‘{?’ and ‘?}’ in the condition so it renders as a boolean.

This is useful if you only want to use a block under certain conditions.

blocks

If a blocks keyword is present, it’ll expand those blocks, using the parent block as a base:

blocks = [
    {
        "base": "value",
        "blocks": [
            {
                "name": "one"
            },
            {
                "name": "two",
                "base": "override"
            }
        ]
    }
]

values = {
    "a": 1
}

list(yaes.each(blocks, values))
# [
#     (
#         {
#             "base": "value",
#             "name": "one"
#         },
#         {
#             "a": 1
#         }
#     ),
#     (
#         {
#             "base": "override",
#             "name": "two"
#         },
#         {
#             "a": 1
#         }
#     )
# ]

Note

blocks within blocks with control keywords will have those keywords evaluated

This is useful if you have a condition or iterate that you want to apply to multiple block without having to use those keywords on each block.

values

If a values keyword is present, it’ll merge those values into the values emitted:

blocks = [
    {
        "name": "one"
    },
    {
        "name": "two",
        "values": {
            "a": 2,
            "c": "{{ b }}sah"
        }
    }
]

values = {
    "a": 1,
    "b": "yes"
}

list(yaes.each(blocks, values))
# [
#     (
#         {
#             "name": "one"
#         },
#         {
#             "a": 1,
#             "b": "yes"
#         }
#     ),
#     (
#         {
#             "name": "two"
#         },
#         {
#             "a": 2,
#             "b": "yes",
#             "c": "yessah"
#         }
#     )
# ]

Note

you can have multiple pairs in values

This is useful if you want to override the existing values but at this point I don’t think even I’ve ever used it.

yaes.each(blocks, values: dict, env=None)

Short hand each function for basic usage

Go through blocks, iterating and checking conditions, yield blocks that pass

Parameters:
  • blocks (dict or list) – blocks to evaulate

  • values (dict) – values to evaluate with

  • env (Jinja2.Environment) – optional Jinja2.Environment to use for transformations

Returns:

Passing blocks

Return type:

Iterator

Usage

import yaes

values = {
    "a": 1,
    "cs": [2, 3],
    "ds": "nuts"
}

block = {
    "ya": "sure",
    "transpose": {
        "b": "a"
    },
    "iterate": {
        "c": "cs",
        "d": "ds"
    },
    "condition": "{{ c != 3 and d != 't' }}",
    "values": {"L": "{{ c + 5 }}"},
    "blocks": [
        {},
        {
            "ya": "ofcourse",
            "condition": "{{ d == 'u' }}",
        }
    ]
}

list(yaes.each(block, values))
# [
#     ({"ya": "sure"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "n", "L": "7"}),
#     ({"ya": "sure"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "u", "L": "7"}),
#     ({"ya": "ofcourse"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "u", "L": "7"}),
#     ({"ya": "sure"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "s", "L": "7"})
# ]

block = {
    "requires": "a",
}

list(yaes.each(block, {}))
# []
class yaes.Engine(env=None)

Class for expanding complex data (YAML / JSON) with Jinja2 templating

Parameters:

env (jinja2.Environment) – optional jinja2 Environment to use with transform

CONTROLS

list of control keywords

env: jinja2.Environment

Jinja2 environment

blocks(block: dict, values: dict)
Parameters:
  • block (dict) – block to evaulate

  • values (dict) – values to evaluate with

Returns:

Merged (child on top of parent) blocks

Return type:

Iterator

Usage

If just a regular block, returns a cleaned copy:

import yaes

engine = yaes.Engine()

block = {
    "ya": "sure",
    "requires": "a",
    "transpose": {
        "b": "a"
    },
    "iterate": {
        "c": "cs",
        "d": "ds"
    },
    "condition": "{{ c != 3 and d != 't' }}",
    "values": {"L": "{{ c + 5 }}"}
}

list(engine.blocks(block, {}))
# [({"ya": "sure"}, {})]

If the block has blocks, it’ll merge them onto top of the parent block after processing them:

values = {
    "a": 1,
    "cs": [2, 3],
    "ds": "nuts"
}

block = {
    "ya": "sure",
    "blocks": [
        {
            "ya": "whatever"
        },
        {
            "ya": "ofcourse",
            "requires": "a",
            "transpose": {
                "b": "a"
            },
            "iterate": {
                "c": "cs",
                "d": "ds"
            },
            "condition": "{{ c != 3 and d != 't' }}",
            "values": {"L": "{{ c + 5 }}"},
        }
    ]
}

list(engine.blocks(block, values))
# [
#     ({"ya": "whatever"}, {"a": 1, "cs": [2, 3], "ds": "nuts"}),
#     ({"ya": "ofcourse"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "n", "L": "7"}),
#     ({"ya": "ofcourse"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "u", "L": "7"}),
#     ({"ya": "ofcourse"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "s", "L": "7"})
# ]
classmethod clean(block: dict) dict
Parameters:

block (dict) – Block to clean

Return type:

dict

Usage

import yaes

engine = yaes.Engine()

block = {
    "ya": "sure",
    "requires": "a",
    "transpose": {
        "b": "a"
    },
    "iterate": {
        "c": "cs",
        "d": "ds"
    },
    "condition": "{{ c != 3 and d != 't' }}",
    "blocks": [1,2, 3],
    "values": {"L": 7}
}

engine.clean(block)
# {"ya": "sure"}
condition(block: dict, values: dict) bool

Evaludates condition in values

It’s best to use ‘{?’ and ‘?}’ as conditions with straight Jinja2 with ‘{{’ and ‘}}’ will be deprecated.

Parameters:
  • block (dict) – block to evaulate

  • values (dict) – values to evaluate with

Returns:

The evaluated condition

Return type:

bool

Usage

import yaes

engine = yaes.Engine()

engine.condition({}, {})
# True

block = {
    "condition": "{{ a == 1 }}"
}

engine.condition(block, {"a": 1})
# True

engine.condition(block, {"a": 2})
# False

block = {
    "condition": "{? a == 1 ?}"
}

engine.condition(block, {"a": 1})
# True

engine.condition(block, {"a": 2})
# False
each(blocks, values: dict)

Iterate over block(s), expanding using control key words

This is used for hihgly dynamic configurmation. Blacks are assumed to have JKinja2 templating and controls for conditions, loops, even whether a block can be evaluated. This determines what’s ready and will expand blocks based on the control keywords sent.

Parameters:
  • blocks (dict or list) – blocks to evaulate

  • values (dict) – values to evaluate with

Returns:

Passing blocks

Return type:

Iterator

Usage

import yaes

engine = yaes.Engine()

values = {
    "a": 1,
    "cs": [2, 3],
    "ds": "nuts"
}

block = {
    "ya": "sure",
    "transpose": {
        "b": "a"
    },
    "iterate": {
        "c": "cs",
        "d": "ds"
    },
    "condition": "{{ c != 3 and d != 't' }}",
    "values": {"L": "{{ c + 5 }}"},
    "blocks": [
        {},
        {
            "ya": "ofcourse",
            "condition": "{{ d == 'u' }}",
        }
    ]
}

list(engine.each(block, values))
# [
#     ({"ya": "sure"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "n", "L": "7"}),
#     ({"ya": "sure"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "u", "L": "7"}),
#     ({"ya": "ofcourse"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "u", "L": "7"}),
#     ({"ya": "sure"}, {"a": 1, "cs": [2, 3], "ds": "nuts", "b": 1, "c": 2, "d": "s", "L": "7"})
# ]

block = {
    "requires": "a",
}

list(engine.each(block, {}))
# []
iterate(block: dict, values: dict) list

Iterates values with transposition

Parameters:
  • block (dict) – block to evaulate

  • values (dict) – values to evaluate with

Returns:

The list of blocks iterated

Return type:

list

Usage

import yaes

engine = yaes.Engine()

values = {
    "a": 1,
    "cs": [2, 3],
    "ds": "nuts"
}

engine.iterate({}, values)
# [{}]

block = {
    "transpose": {
        "b": "a"
    },
    "iterate": {
        "c": "cs",
        "d": "ds"
    }
}

engine.iterate(block, values)
# [
#     {
#         "b": 1,
#         "c": 2,
#         "d": "n"
#     },
#     {
#         "b": 1,
#         "c": 2,
#         "d": "u"
#     },
#     {
#         "b": 1,
#         "c": 2,
#         "d": "t"
#     },
#     {
#         "b": 1,
#         "c": 2,
#         "d": "s"
#     },
#     {
#         "b": 1,
#         "c": 3,
#         "d": "n"
#     },
#     {
#         "b": 1,
#         "c": 3,
#         "d": "u"
#     },
#     {
#         "b": 1,
#         "c": 3,
#         "d": "t"
#     },
#     {
#         "b": 1,
#         "c": 3,
#         "d": "s"
#     }
# ]
requires(block: dict, values: dict) bool

Determines whether values are set to process a block

Parameters:
  • block (dict) – block to evaulate

  • values (dict) – values to evaluate with

Return type:

bool

Usage

import yaes

engine = yaes.Engine()

engine.requires({}, {})
# True

block = {
    "requires": "a"
}

engine.requires(block, {"a": 1})
# True

engine.requires(block, {})
# False

block = {
    "requires": ["a__b", "{[ a__b ]}"]
}

engine.requires(block, {})
# False

engine.requires(block, {"a": {"b": "c"}})
# False

engine.requires(block, {"a": {"b": "c"}, "c": "yep"})
# True
transform(template, values: dict)

Renders a Jinja2 template using values sent

If the template is a str and is enclosed by ‘{?’ and ‘?}’, it will render the template but evaluate as a bool.

If the template is a str and is enclosed by ‘{[’ and ‘]}’, it will lookup the value in valuue using overscore notation.

Else if the tempalte is a str, it will render the template in the standard Jinja2 way.

If the template is a list, it will recurse and render each item.

If the template is a dict, it will recurse each key and render each item.

Else return the template as is.

Parameters:
  • template (bool or str or list or dict) – template to use

  • values (dict) – values to use with the template

Returns:

The rendered value

Usage

import yaes

engine = yaes.Engine()

engine.transform("{{ a }}", {"a": 1})
# '1'

engine.transform(["{{ a }}"], {"a": 1})
# ['1']

engine.transform({"b": "{{ a }}"}, {"a": 1})
# {"b": '1'}

engine.transform("{{ a == 1 }}", {"a": 1})
# 'True'

engine.transform("{{ a != 1 }}", {"a": 1})
# 'False'

engine.transform(True, {})
# True

engine.transform(False, {})
# False

engine.transform("{? 1 == 1 ?}", {})
# True

engine.transform("{? 1 == 0 ?}", {})
# False

engine.transform("{[ a__b ]}", {})
# None

engine.transform("{[ a__b-c ]}", {"a": {"b-c": 3}})
# 3

engine.transform("{[ {{ first }}__{{ second }} ]}", {"first": "a", "second": "b-c", "a": {"b-c": 3}})
# 3
static transpose(block: dict, values: dict) dict

Transposes values, allows for the same value under a different name

Parameters:
  • block (dict) – block to evaulate

  • values (dict) – values to evaluate with

Returns:

The new values block transposed

Return type:

dict

Usage

import yaes

engine = yaes.Engine()

engine.transpose({"transpose": {"b": "a"}}, {"a": 1})
# {"b": 1}