Reworked templating documentation is now live, feedback welcome!

I just started reviewing the greatly improved Templating documentation and my first impression is that novices should not read it on a phone.

I imagined being a novice when I looked at the very first template example. Word-wrapping ruined the indentation and made it a confusing mess. It’s a good example but not when it gets truncated and rearranged by word-wrapping.

To be clear, it’s a pre-existing problem but it’s magnified now because the new documentation is rich with examples.

Perhaps some sort of advisory is needed such as “all examples are best viewed on a monitor to prevent word-wrapping” or something similar.


EDIT

I don’t know if this alternative is possible but perhaps there’s some sort of formatting directive that prevents the code from being word-wrapped. In other words, you need to pan horizontally to see the end of a wide line.

3 Likes

The first example, in Finding the highest or lowest value, does not work correctly.

It uses max with numeric strings so it will report 9 as being higher than 10. Plus if any of the values are unknown, that’s what will be reported as the highest value.

{% set temps = states.sensor
   | selectattr('attributes.device_class', 'eq', 'temperature')
   | list %}
{% set warmest = temps | max(attribute='state') %}
{{ warmest.name }}: {{ warmest.state }}°C

The second example is fine because it converts the numeric strings into numbers.

{% set batteries = states.sensor
   | selectattr('attributes.device_class', 'eq', 'battery')
   | rejectattr('state', 'in', ['unknown', 'unavailable'])
   | list %}
{{ batteries | map(attribute='state') | map('float') | min | round(0) }}%

Corrected versions of the first example are significantly more verbose because of the additional requirement to report the room’s name. If the goal is to keep the example simple, I suggest eliminating the room name.

If reporting the room name is non-negotiable then the example can serve to demonstrate how to use a for-loop, or the zip function, to get the correct highest temperature and the associated room’s name.

3 Likes

Amazing work. Thank you! And I think this will help a lot when pointing people to specific topics – it won’t be so overwhelming.

Does the left side menu only provide two levels?

If so, I wonder if the Template function reference should be its own top-level so that expanding that could show a condensed menu of functions. It’s just odd when you click on the function reference page and you lose context since there’s no menu.

TBH, I’d get use to this (I’d bookmark the reference page), but once you go through the tutorial-type pages one spends most of the time in the reference page. (Where the Error messages page would live is less clear.)

2 Likes

Wow. Thank you!! This was very much needed for someone like me, who has some coding/scripting experience but very little knowledge of Python, YAML and JSON. Don’t have time to go through the whole thing but just browsing through I had multiple “oh, can you do this too” moments. For example, I never knew the has value function existed, maybe it was somewhere in the old docs but I never found it.
Excellent work, much appreciated. I’m sure it can still be improved and I will raise issues on GitHub if I find any, but for now I am very happy with this.

1 Like

Thanks!

I’ve addressed it slightly different, but the essence (and fix) remain the same. You can find the PR here:

…/Frenck

                       

Blogging my personal ramblings at frenck.dev

1 Like

Hi @frenck

This is really good. I wish it had existed when I started out.

Could you include something about how to get results from actions, that produce results, into the developer tools for testing and developing?

1 Like

I get the comment… I really do :slight_smile:

I don’t know about it’s own top level, but I could do this:

1 Like

Thanks!

Yeah I’ve thought about it, but I think that will fit better in the new automation documentation that is being cooked (to also house all the new triggers & conditions).

While it involves templates, it is very specific to automations. At some point, maybe a shared tutorial between the automations & templates? Not sure to be honest.

I’ll keep it in mind for a bit

…/Frenck

                       

Blogging my personal ramblings at frenck.dev

:+1:

That is less disorienting. Thanks.

2 Likes

Nice move!

Like the layout a lot .

What I do miss is the overview like we could scroll that before, all main filters/functions in the right side .

Like eg list manipulation , I looked that up and it took me straight to the available filters/functions . Can we still find that overview somewhere?

The overview now is rather scattered and not ordered by functionality. List manipulation ea.

Edit

Seems like was mentioned already Reworked templating documentation is now live, feedback welcome! - #23 by busman

There is a list, it’s at the bottom and it’s much more organized than before. I think you just missed it.

Made an PR to improve the navigation for the function reference index, as pointed out in multiple places.

Before:

After:

…/Frenck

                       

Blogging my personal ramblings at frenck.dev

2 Likes

There seems to be a markdown parsing issue here:

at the bottom of: https://www.home-assistant.io/docs/templating/python-methods/

I see it’s an issue on more places, or was it intended like this?

It seems to me that there is an error in the description of the groupby function regarding how it works when an item does not have a grouping attribute. The description of the default argument says:

The default value to use for items that do not have the specified attribute. If not provided, items without the attribute are excluded.

However, this code

{% for device_class, entities in states
  | groupby("attributes.device_class") %}
  {{ device_class }}: {{ entities | length }} sensors
{% endfor %}

results in: UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'device_class'.

With default argument provided, it works:

{% for device_class, entities in states
  | groupby("attributes.device_class","missing") %}
  {{ device_class }}: {{ entities | length }} sensors
{% endfor %}

battery: 12 sensors

button: 5 sensors
(...)
missing: 683 sensors

I thought that maybe it’s only like that with “complex” dot access, but it’s the same with simple attribute name without dot, e.g.

{% for device_class, entities in states
  | groupby("not_existing_attribute") %}
  {{ device_class }}: {{ entities | length }} sensors
{% endfor %}

I also have doubts about this statement in the " Good to know" section:
The input must be sorted by the grouping attribute already. This filter only groups adjacent items with matching values.

I ran a test with a list of dictionaries that aren’t sorted by the grouping attribute:

{%
  set src = [
    dict(name='item 1', attributes=dict(device_class='c1',state=1)),
    dict(name='item 2', attributes=dict(device_class='c1',state=2)),
    dict(name='item 3', attributes=dict(device_class='c2',state=3)),
    dict(name='item 4', attributes=dict(device_class='c1',state=4)),
    dict(name='item 5', attributes=dict(device_class='c2',state=5)),
    dict(name='item 6', attributes=dict(device_class='c1',state=6)),
    dict(name='item 7', attributes=dict(device_class='c2',state=7)),
  ]
%}

{% for device_class, entities in src  | groupby("attributes.device_class") %}
  {{- device_class }}: {{ entities | length }} sensors [{{ entities | map(attribute='name')|join(', ') }}]
{% endfor %}

The results seem to suggest that pre-sorting is not required:

c1: 4 sensors [item 1, item 2, item 4, item 6]
c2: 3 sensors [item 3, item 5, item 7]

I also tested it with randomized order of states, e.g.:

device_class undefined: {{ states|selectattr('attributes.device_class','undefined')|list|count }} items

{% for device_class, entities in states|shuffle|groupby('attributes.device_class','missing') -%}
  {% if device_class == 'missing' -%}
    {{ device_class }}: {{ entities | length }} items
  {%- endif %}
{%- endfor %}

Result in my system:

device_class undefined: 683 items

missing: 683 items
1 Like

A few more comments (didn’t go through everything fully yet, so I might have missed things)

  • I often use single line if statements, but I don’t see any example of these in the documentation. I find them more readable in many cases. Things linke It is {{ 'dark outside' if is_state('sun.sun', 'below_horizon') else 'light outside') }}. Maybe this could be mentioned on the Template syntax page.
  • One gotcha people sometimes run into is that the template parser parses the full template result to a python native type. This can bite you in the bottoms in case an integration has a really strict type matching. Some integrations actiions only expect strings where the input can be numeric. In case a template is used the output will be converted to an integer or floating point number, causing an error. The work around in these cases is to template the entire data section of the action, so the output will be a dictionary and the actual value of the parameter won’t be converted from string to numeric.
2 Likes