How can I indirectly call a macro in a Jinja2 template?

jinja2, python

Solution

You can create a Jinja2 filter which gets the Macro from the current context and then evaluates the Macro. The filter is:

@contextfilter
def call_macro_by_name(context, macro_name, *args, **kwargs):
    return context.vars[macro_name](*args, **kwargs)

If your application requires, you can perform string manipulation on macro_name before looking up the Macro in context.vars.

Here is a full example:

#!/usr/bin/env python
from jinja2 import Environment, contextfilter

@contextfilter
def call_macro_by_name(context, macro_name, *args, **kwargs):
    return context.vars[macro_name](*args, **kwargs)

template_string = """\
{%- macro MyMacro(item) %}MyMacro({{ item }}){% endmacro -%}
{{ MyMacro('direct') }}
{{ 'MyMacro' | macro('indirect') }}
"""

env = Environment()
env.filters['macro'] = call_macro_by_name
template = env.from_string(template_string)
print(template.render())

which prints

MyMacro(direct)
MyMacro(indirect)

Problem

I'm trying to do something like this: ``` {% macro obj_type_1 %} stuff {% endmacro %} {% macro obj_type_2 %} stuff {% endmacro %} {{ (obj|get_type)(obj) }} ``` In this example, `get_type` is a filter that would return `obj_type_1` or `obj_type_2` -- ie, the name of the macro to call for `obj`. I don't want to mark up `obj` with configuration output because right now `obj` is used in several templates as structural data, to be rendered with different markup depending on the context. I know the syntax here is a bit tortured, but I think that's because what I want to do isn't immediately possible in Jinja templates. I'm trying to replace a big damn schwack of if/elif/else crap in some config generation code with templates, but this bit seems to be a sticking point.

Original source