diff --git a/CODEOWNERS b/CODEOWNERS index 69ed17cbdf26..82c8f2d709c6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -329,7 +329,7 @@ homeassistant/components/tado/* @michaelarnauts homeassistant/components/tahoma/* @philklei homeassistant/components/tautulli/* @ludeeus homeassistant/components/tellduslive/* @fredrike -homeassistant/components/template/* @PhracturedBlue +homeassistant/components/template/* @PhracturedBlue @tetienne homeassistant/components/tesla/* @zabuldon @alandtse homeassistant/components/tfiac/* @fredrike @mellado homeassistant/components/thethingsnetwork/* @fabaff diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index f4682fa903d8..70c097d0b2b4 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -267,38 +267,10 @@ class LightTemplate(Light): self.async_schedule_update_ha_state() async def async_update(self): - """Update the state from the template.""" - if self._template is not None: - try: - state = self._template.async_render().lower() - except TemplateError as ex: - _LOGGER.error(ex) - self._state = None + """Update from templates.""" + self.update_state() - if state in _VALID_STATES: - self._state = state in ("true", STATE_ON) - else: - _LOGGER.error( - "Received invalid light is_on state: %s. Expected: %s", - state, - ", ".join(_VALID_STATES), - ) - self._state = None - - if self._level_template is not None: - try: - brightness = self._level_template.async_render() - except TemplateError as ex: - _LOGGER.error(ex) - self._state = None - - if 0 <= int(brightness) <= 255: - self._brightness = int(brightness) - else: - _LOGGER.error( - "Received invalid brightness : %s. Expected: 0-255", brightness - ) - self._brightness = None + self.update_brightness() for property_name, template in ( ("_icon", self._icon_template), @@ -335,3 +307,39 @@ class LightTemplate(Light): self._name, ex, ) + + @callback + def update_brightness(self): + """Update the brightness from the template.""" + if self._level_template is not None: + try: + brightness = self._level_template.async_render() + if 0 <= int(brightness) <= 255: + self._brightness = int(brightness) + else: + _LOGGER.error( + "Received invalid brightness : %s. Expected: 0-255", brightness + ) + self._brightness = None + except TemplateError as ex: + _LOGGER.error(ex) + self._state = None + + @callback + def update_state(self): + """Update the state from the template.""" + if self._template is not None: + try: + state = self._template.async_render().lower() + if state in _VALID_STATES: + self._state = state in ("true", STATE_ON) + else: + _LOGGER.error( + "Received invalid light is_on state: %s. Expected: %s", + state, + ", ".join(_VALID_STATES), + ) + self._state = None + except TemplateError as ex: + _LOGGER.error(ex) + self._state = None diff --git a/homeassistant/components/template/manifest.json b/homeassistant/components/template/manifest.json index 833f0071ed82..5f9bbaf4d02f 100644 --- a/homeassistant/components/template/manifest.json +++ b/homeassistant/components/template/manifest.json @@ -4,5 +4,5 @@ "documentation": "https://www.home-assistant.io/integrations/template", "requirements": [], "dependencies": [], - "codeowners": ["@PhracturedBlue"] + "codeowners": ["@PhracturedBlue", "@tetienne"] } diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 62d377a93370..8da61ff3890a 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -1,6 +1,8 @@ """The tests for the Template light platform.""" import logging +import pytest + from homeassistant import setup from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE @@ -38,6 +40,45 @@ class TestTemplateLight: """Stop everything that was started.""" self.hass.stop() + def test_template_state_invalid(self): + """Test template state with render error.""" + with assert_setup_component(1, "light"): + assert setup.setup_component( + self.hass, + "light", + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{states.test['big.fat...']}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + } + }, + } + }, + ) + + self.hass.start() + self.hass.block_till_done() + + state = self.hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + def test_template_state_text(self): """Test the state text of a template.""" with assert_setup_component(1, "light"): @@ -86,7 +127,11 @@ class TestTemplateLight: state = self.hass.states.get("light.test_template_light") assert state.state == STATE_OFF - def test_template_state_boolean_on(self): + @pytest.mark.parametrize( + "expected_state,template", + [(STATE_ON, "{{ 1 == 1 }}"), (STATE_OFF, "{{ 1 == 2 }}")], + ) + def test_template_state_boolean(self, expected_state, template): """Test the setting of the state with boolean on.""" with assert_setup_component(1, "light"): assert setup.setup_component( @@ -97,7 +142,7 @@ class TestTemplateLight: "platform": "template", "lights": { "test_template_light": { - "value_template": "{{ 1 == 1 }}", + "value_template": template, "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -123,46 +168,7 @@ class TestTemplateLight: self.hass.block_till_done() state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_ON - - def test_template_state_boolean_off(self): - """Test the setting of the state with off.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 2 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF + assert state.state == expected_state def test_template_syntax_error(self): """Test templating syntax error.""" @@ -271,110 +277,47 @@ class TestTemplateLight: assert self.hass.states.all() == [] - def test_missing_template_does_create(self): + @pytest.mark.parametrize( + "missing_key, count", [("value_template", 1), ("turn_on", 0), ("turn_off", 0)] + ) + def test_missing_key(self, missing_key, count): """Test missing template.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "light_one": { - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } + light = { + "light": { + "platform": "template", + "lights": { + "light_one": { + "value_template": "{{ 1== 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, }, } }, - ) + } + } + del light["light"]["lights"]["light_one"][missing_key] + with assert_setup_component(count, "light"): + assert setup.setup_component(self.hass, "light", light) self.hass.start() self.hass.block_till_done() - assert self.hass.states.all() != [] - - def test_missing_on_does_not_create(self): - """Test missing on.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "bad name here": { - "value_template": "{{ 1== 1}}", - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - def test_missing_off_does_not_create(self): - """Test missing off.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "bad name here": { - "value_template": "{{ 1== 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] + if count: + assert self.hass.states.all() != [] + else: + assert self.hass.states.all() == [] def test_on_action(self): """Test on action.""" @@ -594,7 +537,11 @@ class TestTemplateLight: assert state is not None assert state.attributes.get("brightness") == 124 - def test_level_template(self): + @pytest.mark.parametrize( + "expected_level,template", + [(255, "{{255}}"), (None, "{{256}}"), (None, "{{x - 12}}")], + ) + def test_level_template(self, expected_level, template): """Test the template for the level.""" with assert_setup_component(1, "light"): assert setup.setup_component( @@ -621,7 +568,7 @@ class TestTemplateLight: "brightness": "{{brightness}}", }, }, - "level_template": "{{42}}", + "level_template": template, } }, } @@ -633,8 +580,7 @@ class TestTemplateLight: state = self.hass.states.get("light.test_template_light") assert state is not None - - assert state.attributes.get("brightness") == 42 + assert state.attributes.get("brightness") == expected_level def test_friendly_name(self): """Test the accessibility of the friendly_name attribute."""