Basic template question

I was creating a template for a sensor, when i found a weird behaviour.
Basically, i was looping through an array and checking, if any element would match my criterea. If so, i set a boolean flag to true and checked it after the loop.

I simplified my code to the one below (don’t worry, i did much more there, this is just a stripped down example: )

{% set found = false %}
---- Init finished ---

{% for i in ['foo', 'bar', 'asdf'] %}
  {% if "ar" in i %}
    {% set found = true %}
  {% endif %}
  {{ found }}
{% endfor %}

{% if found == false %}
  Warning
{% endif %}

I assumed, that this would print :

---- Init finished —
False
True
True

as it found the variable once and i only change it to true, i never change it back. Yet i received this output:

---- Init finished —
False
True
False
Warning

Why does it change my found variable back to false? Am i missing something obviously? I recently started using Home Assistant…

It’s not set back to “false” it always has been “false”. Variables are not in a global scope, it is only local to that function you use it in.

In your example the outer-found is always “false”, as you only change the inner-found to true. Is that understandable? I hope, as I’m struggling to explain it better.

In the end, what you’re doing here is using two different variables. :slight_smile: Not cool, I know, but you’ll have to work around that in another way. :slight_smile:

EDIT:

Here is the part from the Jinja Docs:

Scoping Behavior

Please keep in mind that it is not possible to set variables inside a block and have them show up outside of it. This also applies to loops. The only exception to that rule are if statements which do not introduce a scope. As a result the following template is not going to do what you might expect:

{% set iterated = false %}
{% for item in seq %}
{{ item }}
{% set iterated = true %}
{% endfor %}
{% if not iterated %} did not iterate {% endif %}

It is not possible with Jinja syntax to do this. Instead use alternative constructs like the loop else block or the special loop variable:

{% for item in seq %}
{{ item }}
{% else %}
did not iterate
{% endfor %}

As of version 2.10 more complex use cases can be handled using namespace objects which allow propagating of changes across scopes:

{% set ns = namespace(found=false) %}
{% for item in items %}
{% if item.check_something() %}
{% set ns.found = true %}
{% endif %}
* {{ item.title }}
{% endfor %}
Found item having something: {{ ns.found }}

Note that the obj.attr notation in the set tag is only allowed for namespace objects; attempting to assign an attribute on any other object will raise an exception.

2 Likes

This is a scope issue as Patrick explained. You will need to use namespace() to pull the values out of the for loop.

{% set ns = namespace(find = []) %}
{% set found = false %}
---- Init finished ----

{%- for i in ['foo', 'bar', 'asdf'] %}
  {%- if "ar" in i %}
    {%- set found = true %}
  {%- endif %}
  {{- found }}  
  {% set ns.find = ns.find + [found] %}
{% endfor %}

{% if true not in ns.find %}
  Warning
{% endif %}
2 Likes

Alternative:

{% if ['foo', 'bar', 'asdf'] | select('search', 'ar') | list | count == 0 %}
  Warning
{% endif %}
3 Likes

Thank you all! That was a bit counter intuitive, but now i know :slight_smile: