diff --git a/homeassistant/helpers/template/extensions/state.py b/homeassistant/helpers/template/extensions/state.py index fafea05e141a39..4133789782c3ab 100644 --- a/homeassistant/helpers/template/extensions/state.py +++ b/homeassistant/helpers/template/extensions/state.py @@ -1,6 +1,5 @@ """State functions for Home Assistant templates.""" -import collections.abc from collections.abc import Iterable import logging from typing import TYPE_CHECKING, Any @@ -162,7 +161,7 @@ def expand(self, *args: Any) -> Iterable[State]: continue elif isinstance(entity, State): entity_id = entity.entity_id - elif isinstance(entity, collections.abc.Iterable): + elif isinstance(entity, Iterable): search += entity continue else: diff --git a/tests/helpers/template/test_init.py b/tests/helpers/template/test_init.py index 3f3e884599483e..8a5e4dbf00083f 100644 --- a/tests/helpers/template/test_init.py +++ b/tests/helpers/template/test_init.py @@ -84,16 +84,6 @@ def test_invalid_template(hass: HomeAssistant) -> None: tmpl.async_render() -def test_invalid_entity_id(hass: HomeAssistant) -> None: - """Test referring states by entity id.""" - with pytest.raises(TemplateError): - render(hass, '{{ states["big.fat..."] }}') - with pytest.raises(TemplateError): - render(hass, '{{ states.test["big.fat..."] }}') - with pytest.raises(TemplateError): - render(hass, '{{ states["invalid/domain"] }}') - - def test_raise_exception_on_error(hass: HomeAssistant) -> None: """Test raising an exception on error.""" with pytest.raises(TemplateError): @@ -599,19 +589,6 @@ def test_state_with_unit_and_rounding_options( assert tpl2.async_render() == output2_2 -def test_length_of_states(hass: HomeAssistant) -> None: - """Test fetching the length of states.""" - hass.states.async_set("sensor.test", "23") - hass.states.async_set("sensor.test2", "wow") - hass.states.async_set("climate.test2", "cooling") - - result = render(hass, "{{ states | length }}") - assert result == 3 - - result = render(hass, "{{ states.sensor | length }}") - assert result == 2 - - def test_render_complex_handling_non_template_values(hass: HomeAssistant) -> None: """Test that we can render non-template fields.""" assert template.render_complex( @@ -707,21 +684,6 @@ async def test_demo_template(hass: HomeAssistant) -> None: assert "sun" in result -async def test_slice_states(hass: HomeAssistant) -> None: - """Test iterating states with a slice.""" - hass.states.async_set("sensor.test", "23") - - result = render( - hass, - ( - "{% for states in states | slice(1) -%}{% set state = states | first %}" - "{{ state.entity_id }}" - "{%- endfor %}" - ), - ) - assert result == "sensor.test" - - async def test_lifecycle(hass: HomeAssistant) -> None: """Test that we limit template render info for lifecycle events.""" hass.states.async_set("sun.sun", "above", {"elevation": 50, "next_rising": "later"}) @@ -833,63 +795,6 @@ async def test_template_errors(hass: HomeAssistant) -> None: render(hass, "{{ utcnow() | random }}") -async def test_state_attributes(hass: HomeAssistant) -> None: - """Test state attributes.""" - hass.states.async_set("sensor.test", "23") - - result = render(hass, "{{ states.sensor.test.last_changed }}") - assert result == str(hass.states.get("sensor.test").last_changed) - - result = render(hass, "{{ states.sensor.test.object_id }}") - assert result == hass.states.get("sensor.test").object_id - - result = render(hass, "{{ states.sensor.test.domain }}") - assert result == hass.states.get("sensor.test").domain - - result = render(hass, "{{ states.sensor.test.context.id }}") - assert result == hass.states.get("sensor.test").context.id - - result = render(hass, "{{ states.sensor.test.state_with_unit }}") - assert result == 23 - - result = render(hass, "{{ states.sensor.test.invalid_prop }}") - assert result == "" - - with pytest.raises(TemplateError): - render(hass, "{{ states.sensor.test.invalid_prop.xx }}") - - -async def test_unavailable_states(hass: HomeAssistant) -> None: - """Test watching unavailable states.""" - - for i in range(10): - hass.states.async_set(f"light.sensor{i}", "on") - - hass.states.async_set("light.unavailable", "unavailable") - hass.states.async_set("light.unknown", "unknown") - hass.states.async_set("light.none", "none") - - result = render( - hass, - ( - "{{ states | selectattr('state', 'in', ['unavailable','unknown','none']) " - "| sort(attribute='entity_id') | map(attribute='entity_id') | list | join(', ') }}" - ), - ) - assert result == "light.none, light.unavailable, light.unknown" - - result = render( - hass, - ( - "{{ states.light " - "| selectattr('state', 'in', ['unavailable','unknown','none']) " - "| sort(attribute='entity_id') | map(attribute='entity_id') | list " - "| join(', ') }}" - ), - ) - assert result == "light.none, light.unavailable, light.unknown" - - async def test_no_result_parsing(hass: HomeAssistant) -> None: """Test if templates results are not parsed.""" hass.states.async_set("sensor.temperature", "12") diff --git a/tests/helpers/template/test_states.py b/tests/helpers/template/test_states.py index ced395af370836..384b278374bfce 100644 --- a/tests/helpers/template/test_states.py +++ b/tests/helpers/template/test_states.py @@ -6,11 +6,14 @@ from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant +from homeassistant.exceptions import TemplateError from homeassistant.helpers import template from homeassistant.helpers.json import json_dumps from homeassistant.helpers.template import states as template_states from homeassistant.util import dt as dt_util +from .helpers import render + from tests.common import async_fire_time_changed @@ -76,3 +79,98 @@ async def test_lru_increases_with_many_entities(hass: HomeAssistant) -> None: assert template_states.CACHED_TEMPLATE_NO_COLLECT_LRU.get_size() == int( round(mock_entity_count * template_states.ENTITY_COUNT_GROWTH_FACTOR) ) + + +def test_invalid_entity_id(hass: HomeAssistant) -> None: + """Test referring states by entity id.""" + with pytest.raises(TemplateError): + render(hass, '{{ states["big.fat..."] }}') + with pytest.raises(TemplateError): + render(hass, '{{ states.test["big.fat..."] }}') + with pytest.raises(TemplateError): + render(hass, '{{ states["invalid/domain"] }}') + + +def test_length_of_states(hass: HomeAssistant) -> None: + """Test fetching the length of states.""" + hass.states.async_set("sensor.test", "23") + hass.states.async_set("sensor.test2", "wow") + hass.states.async_set("climate.test2", "cooling") + + result = render(hass, "{{ states | length }}") + assert result == 3 + + result = render(hass, "{{ states.sensor | length }}") + assert result == 2 + + +async def test_slice_states(hass: HomeAssistant) -> None: + """Test iterating states with a slice.""" + hass.states.async_set("sensor.test", "23") + + result = render( + hass, + ( + "{% for states in states | slice(1) -%}{% set state = states | first %}" + "{{ state.entity_id }}" + "{%- endfor %}" + ), + ) + assert result == "sensor.test" + + +async def test_state_attributes(hass: HomeAssistant) -> None: + """Test state attributes.""" + hass.states.async_set("sensor.test", "23") + + result = render(hass, "{{ states.sensor.test.last_changed }}") + assert result == str(hass.states.get("sensor.test").last_changed) + + result = render(hass, "{{ states.sensor.test.object_id }}") + assert result == hass.states.get("sensor.test").object_id + + result = render(hass, "{{ states.sensor.test.domain }}") + assert result == hass.states.get("sensor.test").domain + + result = render(hass, "{{ states.sensor.test.context.id }}") + assert result == hass.states.get("sensor.test").context.id + + result = render(hass, "{{ states.sensor.test.state_with_unit }}") + assert result == 23 + + result = render(hass, "{{ states.sensor.test.invalid_prop }}") + assert result == "" + + with pytest.raises(TemplateError): + render(hass, "{{ states.sensor.test.invalid_prop.xx }}") + + +async def test_unavailable_states(hass: HomeAssistant) -> None: + """Test watching unavailable states.""" + + for i in range(10): + hass.states.async_set(f"light.sensor{i}", "on") + + hass.states.async_set("light.unavailable", "unavailable") + hass.states.async_set("light.unknown", "unknown") + hass.states.async_set("light.none", "none") + + result = render( + hass, + ( + "{{ states | selectattr('state', 'in', ['unavailable','unknown','none']) " + "| sort(attribute='entity_id') | map(attribute='entity_id') | list | join(', ') }}" + ), + ) + assert result == "light.none, light.unavailable, light.unknown" + + result = render( + hass, + ( + "{{ states.light " + "| selectattr('state', 'in', ['unavailable','unknown','none']) " + "| sort(attribute='entity_id') | map(attribute='entity_id') | list " + "| join(', ') }}" + ), + ) + assert result == "light.none, light.unavailable, light.unknown"