How to extend a dict in Jinja?

Hi, I want to use the „do“ statement in jinja templating, particularly in a markdown card for lovelace.
It seems like the jinja2.ext.do extension has to be used. How can I add this extension?

Updated, more appropriate question: How to extend a dict without do statement?

1 Like

It’s not possible without forking home assistant.

Why do you want this anyways? Chances are, you don’t need it. Can you explain your use case?

Hi, thanks for your answer! I want to add new key/value pairs to dictionaries. Precise use case is sorting dates and corresponding events. Sure, it should somehow be possible without dicts, but this seemed to me most elegant. I figured out how to sort given dicts by key or by value. But I can’t define a static dict, I want to add entries based on conditions. I tried some things for adding key/value pairs, but all failed. Here is what I tried from this site so far and what errors I got:

{% set data  =  {} %}
{%- set _ = data.update({'new_key': new_value}) -%}

Gives: SecurityError: access to attribute 'update' of 'dict' object is unsafe.

{% set _ = data.__setitem__('new_key', new_value) %}

Gives: SecurityError: access to attribute '__setitem__' of 'dict' object is unsafe.

Similarly, I could start with a full dict and delete items using data.pop(), but this gives me the same SecurityError.

Then I found a python-like option using ‘do’:

{%- do data['new_key'] = new_value -%} 

Or in the same flavour as the first attempt, without getting a returned value:

{%- do data.update({'new_key': new_value}) -%}

The 'do' statements give me: "TemplateSyntaxError: Encountered unknown tag 'do'." So my idea was to add this extension, but unfortunately, this is not possible.

How is it possible to add key/value pairs to dicts? Or why isn’t it? Why is a SecurityError thrown? It seems that manipulating dictionaries is blocked. Can I unblock those commands manually?

make a list of kevalue pairs:

{% set kvps = [('x', 1), ('y',1)] %}

then create your dictionary. You will not be able to update an existing dictionary with .update({})

{% set data = dict.from_keys(kvps) %}

Your best option to ‘update’ an existing dictionary (current) with another dictionaries values (update)…

{# make current a list of tuples #}
{% set current = current.items() | list %}
{# make update a list of tuples #}
{% set update = update.items() | list %}
{# remove keys from current that are in update #}
{% set current = current | rejectattr('0', 'eq', update | map(attribute='0') | list) | list %}
{{ dict.from_keys(current + update) }}

EDIT: This method restricts you from using dictionary keys that start with a number and have whitespace. Keep that in mind.

7 Likes

Thank you so much, that worked! I do not necessarily need to change the dict once it is created (but I learned a lot from your example). Instead, I can iteratively build the list and then create a dict from it, like this:

{% set kvps = [('x', 4), ('y',2)] %}
{% set kvps = kvps + [('z', 1)] %}
{% set data = dict.from_keys(kvps) %}
2 Likes

Actually, I don’t need a dict at all. One can also sort the list by key or by value. In case anybody needs it some day:

Sorting the list of key-value pairs by key:

{%- for key, value in kvps|sort(attribute='0') -%}
  {{ key }}: {{ value }}
{% endfor %}

Gives

x: 4
y: 2
z: 1

Sorting the list of key-value pairs by value:

{%- for key, value in kvps|sort(attribute='1') -%}
  {{ key }}: {{ value }}
{% endfor %}

Gives

z: 1
y: 2
x: 4
2 Likes

This can be easily done by creating a new dict in this way:

{% set my_dict = dict(a=1, b='b') %}
{% set my_dict = dict(my_dict.items(), a="one", c=3.14) %}

Which correctly generates:

{'a': 'one', 'b': 'b', 'c': 3.14}
1 Like

That method does not allow for variable key names unless you provide 2 dictionaries.

EDIT: 2 dict method…

{% set my_dict = dict(my_dict.items(), **otherdict) %}
1 Like

thanks! after a long search for a solution, this finally helped me solve my problem.

for anyone coming across the same problem: any of these two can be used to filter the attributes of an entity in a template, to only return the ones required.

for example on a weather entity:

{{ dict(states.weather.weather.attributes|items|rejectattr('0', 'in', 'supported_features, precipitation_unit, wind_speed_unit, visibility_unit, pressure_unit, temperature_unit, friendly_name, attribution')) }}

definitely not the most elegant solution, but it was the only one that worked for me.