Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lambda as argument to jinja2 filter?

I'd like to have a custom filter in jinja2 like this:

{{ my_list|my_real_map_filter(lambda i: i.something.else)|some_other_filter }}

But when I implement it, I get this error:

TemplateSyntaxError: expected token ',', got 'i'

It appears jinja2's syntax does not allow for lambdas as arguments? Is there some nice workaround? For now, I'm creating the lambda in python then passing it to the template as a variable, but I'd rather be able to just create it in the template.

like image 818
Lee Avatar asked Jun 19 '14 15:06

Lee


People also ask

How do you pass two arguments in Lambda?

Just like a normal function, a Lambda function can have multiple arguments with one expression. In Python, lambda expressions (or lambda forms) are utilized to construct anonymous functions. To do so, you will use the lambda keyword (just as you use def to define normal functions).

How do you use a Jinja filter?

Jinja2 filter is something we use to transform data held in variables. We apply filters by placing pipe symbol | after the variable followed by name of the filter. Filters can change the look and format of the source data, or even generate new data derived from the input values.


2 Answers

No, you cannot pass general Python expression to filter in Jinja2 template

The confusion comes from jinja2 templates being similar to Python syntax in many aspects, but you shall take it as code with completely independent syntax.

Jinja2 has strict rules, what can be expected at which part of the template and it generally does not allow python code as is, it expect exact types of expressions, which are quite limited.

This is in line with the concept, that presentation and model shall be separated, so template shall not allow too much logic. Anyway, comparing to many other templating options, Jinja2 is quite permissible and allows quite a lot of logic in templates.

like image 83
Jan Vlcinsky Avatar answered Sep 19 '22 17:09

Jan Vlcinsky


I have a workaround, I'm sorting a dict object:

registers = dict(
    CMD = dict(
        address = 0x00020,
        name = 'command register'),
    SR = dict(
        address = 0x00010,
        name = 'status register'),
)

I wanted to loop over the register dict, but sort by address. So I needed a way to sort by the 'address' field. To do this, I created a custom filter and pass the lambda expression as a string, then I use Python's builtin eval() to create the real lambda:

def my_dictsort(value, by='key', reverse = False):

    if by == 'key':
        sort_by = lambda x: x[0].lower() # assumes key is a str

    elif by == 'value':
        sort_by = lambda x: x[1]

    else:
        sort_by = eval(by)   # assumes lambda string, you should error check

    return sorted(value, key = sort_by, reverse = reverse)

With this function, you can inject it into the jinja2 environment like so:

env = jinja2.Environment(...)
env.filters['my_dictsort'] = my_dictsort
env.globals['lookup'] = lookup            # queries a database, returns dict

And then call it from your template:

{% for key, value in lookup('registers') | my_dict_sort("lambda x:x[1]['address']") %}
{{"""\
    static const unsigned int ADDR_{key} = 0x0{address:04X}; // {name}
""" | format(key = key, address = value['address'], name = value['name']) 
}}
{% endfor %}

Output:

static const unsigned int ADDR_SR = 0x00010; // status register
static const unsigned int ADDR_CMD = 0x00020; // command register

So you can pass a lambda as a string, but you'll have to add a custom filter to do it.

like image 32
Nick Avatar answered Sep 19 '22 17:09

Nick