Ease code before adding color and temperature to light template (#30455)

* Split async_update

* Use pytest parameters to avoid duplicate code

* Fix UnboundLocalError

* Test error rendering for template state

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
tetienne 2020-01-07 17:22:42 +01:00 committed by Paulus Schoutsen
parent e18426051b
commit 13e6479b6e
4 changed files with 129 additions and 175 deletions

View File

@ -329,7 +329,7 @@ homeassistant/components/tado/* @michaelarnauts
homeassistant/components/tahoma/* @philklei homeassistant/components/tahoma/* @philklei
homeassistant/components/tautulli/* @ludeeus homeassistant/components/tautulli/* @ludeeus
homeassistant/components/tellduslive/* @fredrike homeassistant/components/tellduslive/* @fredrike
homeassistant/components/template/* @PhracturedBlue homeassistant/components/template/* @PhracturedBlue @tetienne
homeassistant/components/tesla/* @zabuldon @alandtse homeassistant/components/tesla/* @zabuldon @alandtse
homeassistant/components/tfiac/* @fredrike @mellado homeassistant/components/tfiac/* @fredrike @mellado
homeassistant/components/thethingsnetwork/* @fabaff homeassistant/components/thethingsnetwork/* @fabaff

View File

@ -267,38 +267,10 @@ class LightTemplate(Light):
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
async def async_update(self): async def async_update(self):
"""Update the state from the template.""" """Update from templates."""
if self._template is not None: self.update_state()
try:
state = self._template.async_render().lower()
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None
if state in _VALID_STATES: self.update_brightness()
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
for property_name, template in ( for property_name, template in (
("_icon", self._icon_template), ("_icon", self._icon_template),
@ -335,3 +307,39 @@ class LightTemplate(Light):
self._name, self._name,
ex, 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

View File

@ -4,5 +4,5 @@
"documentation": "https://www.home-assistant.io/integrations/template", "documentation": "https://www.home-assistant.io/integrations/template",
"requirements": [], "requirements": [],
"dependencies": [], "dependencies": [],
"codeowners": ["@PhracturedBlue"] "codeowners": ["@PhracturedBlue", "@tetienne"]
} }

View File

@ -1,6 +1,8 @@
"""The tests for the Template light platform.""" """The tests for the Template light platform."""
import logging import logging
import pytest
from homeassistant import setup from homeassistant import setup
from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
@ -38,6 +40,45 @@ class TestTemplateLight:
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() 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): def test_template_state_text(self):
"""Test the state text of a template.""" """Test the state text of a template."""
with assert_setup_component(1, "light"): with assert_setup_component(1, "light"):
@ -86,7 +127,11 @@ class TestTemplateLight:
state = self.hass.states.get("light.test_template_light") state = self.hass.states.get("light.test_template_light")
assert state.state == STATE_OFF 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.""" """Test the setting of the state with boolean on."""
with assert_setup_component(1, "light"): with assert_setup_component(1, "light"):
assert setup.setup_component( assert setup.setup_component(
@ -97,7 +142,7 @@ class TestTemplateLight:
"platform": "template", "platform": "template",
"lights": { "lights": {
"test_template_light": { "test_template_light": {
"value_template": "{{ 1 == 1 }}", "value_template": template,
"turn_on": { "turn_on": {
"service": "light.turn_on", "service": "light.turn_on",
"entity_id": "light.test_state", "entity_id": "light.test_state",
@ -123,46 +168,7 @@ class TestTemplateLight:
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get("light.test_template_light") state = self.hass.states.get("light.test_template_light")
assert state.state == STATE_ON assert state.state == expected_state
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
def test_template_syntax_error(self): def test_template_syntax_error(self):
"""Test templating syntax error.""" """Test templating syntax error."""
@ -271,110 +277,47 @@ class TestTemplateLight:
assert self.hass.states.all() == [] 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.""" """Test missing template."""
with assert_setup_component(1, "light"): light = {
assert setup.setup_component( "light": {
self.hass, "platform": "template",
"light", "lights": {
{ "light_one": {
"light": { "value_template": "{{ 1== 1}}",
"platform": "template", "turn_on": {
"lights": { "service": "light.turn_on",
"light_one": { "entity_id": "light.test_state",
"turn_on": { },
"service": "light.turn_on", "turn_off": {
"entity_id": "light.test_state", "service": "light.turn_off",
}, "entity_id": "light.test_state",
"turn_off": { },
"service": "light.turn_off", "set_level": {
"entity_id": "light.test_state", "service": "light.turn_on",
}, "data_template": {
"set_level": { "entity_id": "light.test_state",
"service": "light.turn_on", "brightness": "{{brightness}}",
"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.start()
self.hass.block_till_done() self.hass.block_till_done()
assert self.hass.states.all() != [] if count:
assert self.hass.states.all() != []
def test_missing_on_does_not_create(self): else:
"""Test missing on.""" assert self.hass.states.all() == []
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() == []
def test_on_action(self): def test_on_action(self):
"""Test on action.""" """Test on action."""
@ -594,7 +537,11 @@ class TestTemplateLight:
assert state is not None assert state is not None
assert state.attributes.get("brightness") == 124 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.""" """Test the template for the level."""
with assert_setup_component(1, "light"): with assert_setup_component(1, "light"):
assert setup.setup_component( assert setup.setup_component(
@ -621,7 +568,7 @@ class TestTemplateLight:
"brightness": "{{brightness}}", "brightness": "{{brightness}}",
}, },
}, },
"level_template": "{{42}}", "level_template": template,
} }
}, },
} }
@ -633,8 +580,7 @@ class TestTemplateLight:
state = self.hass.states.get("light.test_template_light") state = self.hass.states.get("light.test_template_light")
assert state is not None assert state is not None
assert state.attributes.get("brightness") == expected_level
assert state.attributes.get("brightness") == 42
def test_friendly_name(self): def test_friendly_name(self):
"""Test the accessibility of the friendly_name attribute.""" """Test the accessibility of the friendly_name attribute."""