Add native Python types support to templates (#41227)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Franck Nijhof 2020-10-07 00:05:52 +02:00 committed by GitHub
parent cbb4324c84
commit ee914366a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 349 additions and 282 deletions

View file

@ -410,7 +410,7 @@ class APITemplateView(HomeAssistantView):
try: try:
data = await request.json() data = await request.json()
tpl = template.Template(data["template"], request.app["hass"]) tpl = template.Template(data["template"], request.app["hass"])
return tpl.async_render(data.get("variables")) return str(tpl.async_render(data.get("variables")))
except (ValueError, TemplateError) as ex: except (ValueError, TemplateError) as ex:
return self.json_message( return self.json_message(
f"Error rendering template: {ex}", HTTP_BAD_REQUEST f"Error rendering template: {ex}", HTTP_BAD_REQUEST

View file

@ -271,7 +271,7 @@ class HistoryStatsSensor(Entity):
except (TemplateError, TypeError) as ex: except (TemplateError, TypeError) as ex:
HistoryStatsHelper.handle_template_exception(ex, "start") HistoryStatsHelper.handle_template_exception(ex, "start")
return return
start = dt_util.parse_datetime(start_rendered) start = dt_util.parse_datetime(str(start_rendered))
if start is None: if start is None:
try: try:
start = dt_util.as_local( start = dt_util.as_local(

View file

@ -258,7 +258,7 @@ class CoverTemplate(TemplateEntity, CoverEntity):
self._position = None self._position = None
return return
state = result.lower() state = str(result).lower()
if state in _VALID_STATES: if state in _VALID_STATES:
if state in ("true", STATE_OPEN): if state in ("true", STATE_OPEN):
self._position = 100 self._position = 100

View file

@ -367,6 +367,7 @@ class TemplateFan(TemplateEntity, FanEntity):
@callback @callback
def _update_speed(self, speed): def _update_speed(self, speed):
# Validate speed # Validate speed
speed = str(speed)
if speed in self._speed_list: if speed in self._speed_list:
self._speed = speed self._speed = speed
elif speed in [STATE_UNAVAILABLE, STATE_UNKNOWN]: elif speed in [STATE_UNAVAILABLE, STATE_UNKNOWN]:

View file

@ -412,7 +412,7 @@ class LightTemplate(TemplateEntity, LightEntity):
self._available = True self._available = True
return return
state = result.lower() state = str(result).lower()
if state in _VALID_STATES: if state in _VALID_STATES:
self._state = state in ("true", STATE_ON) self._state = state in ("true", STATE_ON)
else: else:
@ -451,12 +451,17 @@ class LightTemplate(TemplateEntity, LightEntity):
@callback @callback
def _update_color(self, render): def _update_color(self, render):
"""Update the hs_color from the template.""" """Update the hs_color from the template."""
if render in ("None", ""): h_str = s_str = None
self._color = None if isinstance(render, str):
return if render in ("None", ""):
h_str, s_str = map( self._color = None
float, render.replace("(", "").replace(")", "").split(",", 1) return
) h_str, s_str = map(
float, render.replace("(", "").replace(")", "").split(",", 1)
)
elif isinstance(render, (list, tuple)) and len(render) == 2:
h_str, s_str = render
if ( if (
h_str is not None h_str is not None
and s_str is not None and s_str is not None

View file

@ -120,7 +120,16 @@ class TemplateLock(TemplateEntity, LockEntity):
if isinstance(result, TemplateError): if isinstance(result, TemplateError):
self._state = None self._state = None
return return
self._state = result.lower() in ("true", STATE_ON, STATE_LOCKED)
if isinstance(result, bool):
self._state = result
return
if isinstance(result, str):
self._state = result.lower() in ("true", STATE_ON, STATE_LOCKED)
return
self._state = False
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""

View file

@ -136,7 +136,16 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
if isinstance(result, TemplateError): if isinstance(result, TemplateError):
self._state = None self._state = None
return return
self._state = result.lower() in ("true", STATE_ON)
if isinstance(result, bool):
self._state = result
return
if isinstance(result, str):
self._state = result.lower() in ("true", STATE_ON)
return
self._state = False
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""

View file

@ -32,6 +32,7 @@ from homeassistant.const import (
CONF_ID, CONF_ID,
CONF_INTERNAL_URL, CONF_INTERNAL_URL,
CONF_LATITUDE, CONF_LATITUDE,
CONF_LEGACY_TEMPLATES,
CONF_LONGITUDE, CONF_LONGITUDE,
CONF_MEDIA_DIRS, CONF_MEDIA_DIRS,
CONF_NAME, CONF_NAME,
@ -224,6 +225,7 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend(
), ),
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
vol.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys(vol.IsDir()), vol.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys(vol.IsDir()),
vol.Optional(CONF_LEGACY_TEMPLATES): cv.boolean,
} }
) )
@ -500,6 +502,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non
(CONF_INTERNAL_URL, "internal_url"), (CONF_INTERNAL_URL, "internal_url"),
(CONF_EXTERNAL_URL, "external_url"), (CONF_EXTERNAL_URL, "external_url"),
(CONF_MEDIA_DIRS, "media_dirs"), (CONF_MEDIA_DIRS, "media_dirs"),
(CONF_LEGACY_TEMPLATES, "legacy_templates"),
): ):
if key in config: if key in config:
setattr(hac, attr, config[key]) setattr(hac, attr, config[key])

View file

@ -112,6 +112,7 @@ CONF_INCLUDE = "include"
CONF_INTERNAL_URL = "internal_url" CONF_INTERNAL_URL = "internal_url"
CONF_IP_ADDRESS = "ip_address" CONF_IP_ADDRESS = "ip_address"
CONF_LATITUDE = "latitude" CONF_LATITUDE = "latitude"
CONF_LEGACY_TEMPLATES = "legacy_templates"
CONF_LIGHTS = "lights" CONF_LIGHTS = "lights"
CONF_LONGITUDE = "longitude" CONF_LONGITUDE = "longitude"
CONF_MAC = "mac" CONF_MAC = "mac"

View file

@ -1427,6 +1427,9 @@ class Config:
# If Home Assistant is running in safe mode # If Home Assistant is running in safe mode
self.safe_mode: bool = False self.safe_mode: bool = False
# Use legacy template behavior
self.legacy_templates: bool = False
def distance(self, lat: float, lon: float) -> Optional[float]: def distance(self, lat: float, lon: float) -> Optional[float]:
"""Calculate distance from Home Assistant. """Calculate distance from Home Assistant.

View file

@ -458,7 +458,10 @@ def async_template(
_LOGGER.error("Error during template condition: %s", ex) _LOGGER.error("Error during template condition: %s", ex)
return False return False
return value.lower() == "true" if isinstance(value, bool):
return value
return str(value).lower() == "true"
def async_template_from_config( def async_template_from_config(

View file

@ -111,8 +111,8 @@ class TrackTemplateResult:
""" """
template: Template template: Template
last_result: Union[str, None, TemplateError] last_result: Any
result: Union[str, TemplateError] result: Any
def threaded_listener_factory(async_factory: Callable[..., Any]) -> CALLBACK_TYPE: def threaded_listener_factory(async_factory: Callable[..., Any]) -> CALLBACK_TYPE:

View file

@ -1,4 +1,5 @@
"""Template helper methods for rendering strings with Home Assistant data.""" """Template helper methods for rendering strings with Home Assistant data."""
from ast import literal_eval
import asyncio import asyncio
import base64 import base64
import collections.abc import collections.abc
@ -302,7 +303,7 @@ class Template:
return extract_entities(self.hass, self.template, variables) return extract_entities(self.hass, self.template, variables)
def render(self, variables: TemplateVarsType = None, **kwargs: Any) -> str: def render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any:
"""Render given template.""" """Render given template."""
if self.is_static: if self.is_static:
return self.template.strip() return self.template.strip()
@ -315,7 +316,7 @@ class Template:
).result() ).result()
@callback @callback
def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> str: def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any:
"""Render given template. """Render given template.
This method must be run in the event loop. This method must be run in the event loop.
@ -329,10 +330,27 @@ class Template:
kwargs.update(variables) kwargs.update(variables)
try: try:
return compiled.render(kwargs).strip() render_result = compiled.render(kwargs)
except jinja2.TemplateError as err: except jinja2.TemplateError as err:
raise TemplateError(err) from err raise TemplateError(err) from err
render_result = render_result.strip()
if not self.hass.config.legacy_templates:
try:
result = literal_eval(render_result)
# If the literal_eval result is a string, use the original
# render, by not returning right here. The evaluation of strings
# resulting in strings impacts quotes, to avoid unexpected
# output; use the original render instead of the evaluated one.
if not isinstance(result, str):
return result
except (ValueError, SyntaxError, MemoryError):
pass
return render_result
async def async_render_will_timeout( async def async_render_will_timeout(
self, timeout: float, variables: TemplateVarsType = None, **kwargs: Any self, timeout: float, variables: TemplateVarsType = None, **kwargs: Any
) -> bool: ) -> bool:

View file

@ -104,7 +104,7 @@ class TestNotifyDemo(unittest.TestCase):
self.hass.block_till_done() self.hass.block_till_done()
last_event = self.events[-1] last_event = self.events[-1]
assert last_event.data[notify.ATTR_TITLE] == "temperature" assert last_event.data[notify.ATTR_TITLE] == "temperature"
assert last_event.data[notify.ATTR_MESSAGE] == "10" assert last_event.data[notify.ATTR_MESSAGE] == 10
def test_method_forwards_correct_data(self): def test_method_forwards_correct_data(self):
"""Test that all data from the service gets forwarded to service.""" """Test that all data from the service gets forwarded to service."""

View file

@ -65,7 +65,7 @@ DEFAULT_CONFIG_CODE = {
"name": "test", "name": "test",
"state_topic": "alarm/state", "state_topic": "alarm/state",
"command_topic": "alarm/command", "command_topic": "alarm/command",
"code": "1234", "code": "0123",
"code_arm_required": True, "code_arm_required": True,
} }
} }
@ -396,7 +396,7 @@ async def test_disarm_publishes_mqtt_with_template(hass, mqtt_mock):
When command_template set to output json When command_template set to output json
""" """
config = copy.deepcopy(DEFAULT_CONFIG_CODE) config = copy.deepcopy(DEFAULT_CONFIG_CODE)
config[alarm_control_panel.DOMAIN]["code"] = "1234" config[alarm_control_panel.DOMAIN]["code"] = "0123"
config[alarm_control_panel.DOMAIN]["command_template"] = ( config[alarm_control_panel.DOMAIN]["command_template"] = (
'{"action":"{{ action }}",' '"code":"{{ code }}"}' '{"action":"{{ action }}",' '"code":"{{ code }}"}'
) )
@ -407,9 +407,9 @@ async def test_disarm_publishes_mqtt_with_template(hass, mqtt_mock):
) )
await hass.async_block_till_done() await hass.async_block_till_done()
await common.async_alarm_disarm(hass, 1234) await common.async_alarm_disarm(hass, "0123")
mqtt_mock.async_publish.assert_called_once_with( mqtt_mock.async_publish.assert_called_once_with(
"alarm/command", '{"action":"DISARM","code":"1234"}', 0, False "alarm/command", {"action": "DISARM", "code": "0123"}, 0, False
) )

View file

@ -702,9 +702,7 @@ async def test_set_position_templated(hass, mqtt_mock):
blocking=True, blocking=True,
) )
mqtt_mock.async_publish.assert_called_once_with( mqtt_mock.async_publish.assert_called_once_with("set-position-topic", 38, 0, False)
"set-position-topic", "38", 0, False
)
async def test_set_position_untemplated(hass, mqtt_mock): async def test_set_position_untemplated(hass, mqtt_mock):

View file

@ -123,7 +123,7 @@ async def test_service_call_with_template_payload_renders_template(hass, mqtt_mo
mqtt.async_publish_template(hass, "test/topic", "{{ 1+1 }}") mqtt.async_publish_template(hass, "test/topic", "{{ 1+1 }}")
await hass.async_block_till_done() await hass.async_block_till_done()
assert mqtt_mock.async_publish.called assert mqtt_mock.async_publish.called
assert mqtt_mock.async_publish.call_args[0][1] == "2" assert mqtt_mock.async_publish.call_args[0][1] == 2
async def test_service_call_with_payload_doesnt_render_template(hass, mqtt_mock): async def test_service_call_with_payload_doesnt_render_template(hass, mqtt_mock):

View file

@ -837,7 +837,7 @@ async def test_sending_mqtt_color_temp_command_with_template(hass, mqtt_mock):
mqtt_mock.async_publish.assert_has_calls( mqtt_mock.async_publish.assert_has_calls(
[ [
call("test_light_color_temp/set", "on", 0, False), call("test_light_color_temp/set", "on", 0, False),
call("test_light_color_temp/color_temp/set", "10", 0, False), call("test_light_color_temp/color_temp/set", 10, 0, False),
], ],
any_order=True, any_order=True,
) )

View file

@ -705,4 +705,4 @@ async def test_script_variables(hass, caplog):
await hass.services.async_call("script", "script3", {"break": 0}, blocking=True) await hass.services.async_call("script", "script3", {"break": 0}, blocking=True)
assert len(mock_calls) == 4 assert len(mock_calls) == 4
assert mock_calls[3].data["value"] == "1" assert mock_calls[3].data["value"] == 1

View file

@ -402,7 +402,7 @@ async def test_intent_special_slots(hass, mqtt_mock):
assert len(calls) == 1 assert len(calls) == 1
assert calls[0].domain == "light" assert calls[0].domain == "light"
assert calls[0].service == "turn_on" assert calls[0].service == "turn_on"
assert calls[0].data["confidenceScore"] == "0.85" assert calls[0].data["confidenceScore"] == 0.85
assert calls[0].data["site_id"] == "default" assert calls[0].data["site_id"] == "default"

View file

@ -662,7 +662,7 @@ def _verify(
"""Verify fan's state, speed and osc.""" """Verify fan's state, speed and osc."""
state = hass.states.get(_TEST_FAN) state = hass.states.get(_TEST_FAN)
attributes = state.attributes attributes = state.attributes
assert state.state == expected_state assert state.state == str(expected_state)
assert attributes.get(ATTR_SPEED) == expected_speed assert attributes.get(ATTR_SPEED) == expected_speed
assert attributes.get(ATTR_OSCILLATING) == expected_oscillating assert attributes.get(ATTR_OSCILLATING) == expected_oscillating
assert attributes.get(ATTR_DIRECTION) == expected_direction assert attributes.get(ATTR_DIRECTION) == expected_direction

View file

@ -550,7 +550,7 @@ class TestTemplateLight:
) )
self.hass.block_till_done() self.hass.block_till_done()
assert len(self.calls) == 1 assert len(self.calls) == 1
assert self.calls[0].data["white_value"] == "124" assert self.calls[0].data["white_value"] == 124
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
@ -649,7 +649,7 @@ class TestTemplateLight:
common.turn_on(self.hass, "light.test_template_light", **{ATTR_BRIGHTNESS: 124}) common.turn_on(self.hass, "light.test_template_light", **{ATTR_BRIGHTNESS: 124})
self.hass.block_till_done() self.hass.block_till_done()
assert len(self.calls) == 1 assert len(self.calls) == 1
assert self.calls[0].data["brightness"] == "124" assert self.calls[0].data["brightness"] == 124
state = self.hass.states.get("light.test_template_light") state = self.hass.states.get("light.test_template_light")
_LOGGER.info(str(state.attributes)) _LOGGER.info(str(state.attributes))
@ -802,7 +802,7 @@ class TestTemplateLight:
common.turn_on(self.hass, "light.test_template_light", **{ATTR_COLOR_TEMP: 345}) common.turn_on(self.hass, "light.test_template_light", **{ATTR_COLOR_TEMP: 345})
self.hass.block_till_done() self.hass.block_till_done()
assert len(self.calls) == 1 assert len(self.calls) == 1
assert self.calls[0].data["color_temp"] == "345" assert self.calls[0].data["color_temp"] == 345
state = self.hass.states.get("light.test_template_light") state = self.hass.states.get("light.test_template_light")
_LOGGER.info(str(state.attributes)) _LOGGER.info(str(state.attributes))
@ -1008,18 +1008,18 @@ class TestTemplateLight:
) )
self.hass.block_till_done() self.hass.block_till_done()
assert len(self.calls) == 2 assert len(self.calls) == 2
assert self.calls[0].data["h"] == "40" assert self.calls[0].data["h"] == 40
assert self.calls[0].data["s"] == "50" assert self.calls[0].data["s"] == 50
assert self.calls[1].data["h"] == "40" assert self.calls[1].data["h"] == 40
assert self.calls[1].data["s"] == "50" assert self.calls[1].data["s"] == 50
state = self.hass.states.get("light.test_template_light") state = self.hass.states.get("light.test_template_light")
_LOGGER.info(str(state.attributes)) _LOGGER.info(str(state.attributes))
assert state is not None assert state is not None
assert self.calls[0].data["h"] == "40" assert self.calls[0].data["h"] == 40
assert self.calls[0].data["s"] == "50" assert self.calls[0].data["s"] == 50
assert self.calls[1].data["h"] == "40" assert self.calls[1].data["h"] == 40
assert self.calls[1].data["s"] == "50" assert self.calls[1].data["s"] == 50
@pytest.mark.parametrize( @pytest.mark.parametrize(
"expected_hs,template", "expected_hs,template",

View file

@ -867,7 +867,7 @@ async def test_self_referencing_entity_picture_loop(hass, caplog):
state = hass.states.get("sensor.test") state = hass.states.get("sensor.test")
assert int(state.state) == 1 assert int(state.state) == 1
assert state.attributes[ATTR_ENTITY_PICTURE] == "2" assert state.attributes[ATTR_ENTITY_PICTURE] == 2
await hass.async_block_till_done() await hass.async_block_till_done()
assert int(state.state) == 1 assert int(state.state) == 1

View file

@ -5,7 +5,7 @@ from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.components.vilfo.const import DOMAIN from homeassistant.components.vilfo.const import DOMAIN
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_ID, CONF_MAC from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_ID, CONF_MAC
from tests.async_mock import patch from tests.async_mock import Mock, patch
async def test_form(hass): async def test_form(hass):
@ -29,6 +29,7 @@ async def test_form(hass):
result["flow_id"], result["flow_id"],
{CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"},
) )
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "testadmin.vilfo.com" assert result2["title"] == "testadmin.vilfo.com"
@ -142,7 +143,10 @@ async def test_form_unexpected_exception(hass):
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with patch("vilfo.Client.ping", side_effect=Exception): with patch(
"homeassistant.components.vilfo.config_flow.VilfoClient",
) as mock_client:
mock_client.return_value.ping = Mock(side_effect=Exception)
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{"host": "testadmin.vilfo.com", "access_token": "test-token"}, {"host": "testadmin.vilfo.com", "access_token": "test-token"},

View file

@ -958,7 +958,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "light") hass.states.async_set("sensor.domain", "light")
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 1 assert len(specific_runs) == 1
assert specific_runs[0].strip() == "['light.one']" assert specific_runs[0] == ["light.one"]
assert info.listeners == { assert info.listeners == {
"all": False, "all": False,
@ -969,7 +969,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "lock") hass.states.async_set("sensor.domain", "lock")
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 2 assert len(specific_runs) == 2
assert specific_runs[1].strip() == "['lock.one']" assert specific_runs[1] == ["lock.one"]
assert info.listeners == { assert info.listeners == {
"all": False, "all": False,
"domains": {"lock"}, "domains": {"lock"},
@ -987,7 +987,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "light") hass.states.async_set("sensor.domain", "light")
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 4 assert len(specific_runs) == 4
assert specific_runs[3].strip() == "['light.one']" assert specific_runs[3] == ["light.one"]
assert info.listeners == { assert info.listeners == {
"all": False, "all": False,
"domains": {"light"}, "domains": {"light"},
@ -1022,7 +1022,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "lock") hass.states.async_set("sensor.domain", "lock")
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 7 assert len(specific_runs) == 7
assert specific_runs[6].strip() == "['lock.one']" assert specific_runs[6] == ["lock.one"]
assert info.listeners == { assert info.listeners == {
"all": False, "all": False,
"domains": {"lock"}, "domains": {"lock"},
@ -1032,7 +1032,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "single_binary_sensor") hass.states.async_set("sensor.domain", "single_binary_sensor")
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 8 assert len(specific_runs) == 8
assert specific_runs[7].strip() == "unknown" assert specific_runs[7] == "unknown"
assert info.listeners == { assert info.listeners == {
"all": False, "all": False,
"domains": set(), "domains": set(),
@ -1042,7 +1042,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("binary_sensor.single", "binary_sensor_on") hass.states.async_set("binary_sensor.single", "binary_sensor_on")
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 9 assert len(specific_runs) == 9
assert specific_runs[8].strip() == "binary_sensor_on" assert specific_runs[8] == "binary_sensor_on"
assert info.listeners == { assert info.listeners == {
"all": False, "all": False,
"domains": set(), "domains": set(),
@ -1052,7 +1052,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "lock") hass.states.async_set("sensor.domain", "lock")
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 10 assert len(specific_runs) == 10
assert specific_runs[9].strip() == "['lock.one']" assert specific_runs[9] == ["lock.one"]
assert info.listeners == { assert info.listeners == {
"all": False, "all": False,
"domains": {"lock"}, "domains": {"lock"},
@ -1144,13 +1144,13 @@ async def test_track_template_result_with_group(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 1 assert len(specific_runs) == 1
assert specific_runs[0] == str(100.1 + 200.2 + 400.4) assert specific_runs[0] == 100.1 + 200.2 + 400.4
hass.states.async_set("sensor.power_3", 0) hass.states.async_set("sensor.power_3", 0)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(specific_runs) == 2 assert len(specific_runs) == 2
assert specific_runs[1] == str(100.1 + 200.2 + 0) assert specific_runs[1] == 100.1 + 200.2 + 0
with patch( with patch(
"homeassistant.config.load_yaml_config_file", "homeassistant.config.load_yaml_config_file",
@ -1165,7 +1165,7 @@ async def test_track_template_result_with_group(hass):
info.async_refresh() info.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert specific_runs[-1] == str(100.1 + 200.2 + 0 + 800.8) assert specific_runs[-1] == 100.1 + 200.2 + 0 + 800.8
async def test_track_template_result_and_conditional(hass): async def test_track_template_result_and_conditional(hass):
@ -1421,38 +1421,38 @@ async def test_track_template_rate_limit(hass):
info.async_refresh() info.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0"] assert refresh_runs == [0]
hass.states.async_set("sensor.one", "any") hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0"] assert refresh_runs == [0]
info.async_refresh() info.async_refresh()
assert refresh_runs == ["0", "1"] assert refresh_runs == [0, 1]
hass.states.async_set("sensor.two", "any") hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1"] assert refresh_runs == [0, 1]
next_time = dt_util.utcnow() + timedelta(seconds=0.125) next_time = dt_util.utcnow() + timedelta(seconds=0.125)
with patch( with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
): ):
async_fire_time_changed(hass, next_time) async_fire_time_changed(hass, next_time)
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1", "2"] assert refresh_runs == [0, 1, 2]
hass.states.async_set("sensor.three", "any") hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1", "2"] assert refresh_runs == [0, 1, 2]
hass.states.async_set("sensor.four", "any") hass.states.async_set("sensor.four", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1", "2"] assert refresh_runs == [0, 1, 2]
next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2)
with patch( with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
): ):
async_fire_time_changed(hass, next_time) async_fire_time_changed(hass, next_time)
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1", "2", "4"] assert refresh_runs == [0, 1, 2, 4]
hass.states.async_set("sensor.five", "any") hass.states.async_set("sensor.five", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1", "2", "4"] assert refresh_runs == [0, 1, 2, 4]
async def test_track_template_rate_limit_five(hass): async def test_track_template_rate_limit_five(hass):
@ -1474,18 +1474,18 @@ async def test_track_template_rate_limit_five(hass):
info.async_refresh() info.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0"] assert refresh_runs == [0]
hass.states.async_set("sensor.one", "any") hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0"] assert refresh_runs == [0]
info.async_refresh() info.async_refresh()
assert refresh_runs == ["0", "1"] assert refresh_runs == [0, 1]
hass.states.async_set("sensor.two", "any") hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1"] assert refresh_runs == [0, 1]
hass.states.async_set("sensor.three", "any") hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["0", "1"] assert refresh_runs == [0, 1]
async def test_track_template_has_default_rate_limit(hass): async def test_track_template_has_default_rate_limit(hass):
@ -1508,18 +1508,18 @@ async def test_track_template_has_default_rate_limit(hass):
info.async_refresh() info.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1"] assert refresh_runs == [1]
hass.states.async_set("sensor.one", "any") hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1"] assert refresh_runs == [1]
info.async_refresh() info.async_refresh()
assert refresh_runs == ["1", "2"] assert refresh_runs == [1, 2]
hass.states.async_set("sensor.two", "any") hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1", "2"] assert refresh_runs == [1, 2]
hass.states.async_set("sensor.three", "any") hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1", "2"] assert refresh_runs == [1, 2]
async def test_track_template_unavailable_sates_has_default_rate_limit(hass): async def test_track_template_unavailable_sates_has_default_rate_limit(hass):
@ -1545,21 +1545,21 @@ async def test_track_template_unavailable_sates_has_default_rate_limit(hass):
info.async_refresh() info.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1"] assert refresh_runs == [1]
hass.states.async_set("sensor.one", "unknown") hass.states.async_set("sensor.one", "unknown")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1"] assert refresh_runs == [1]
info.async_refresh() info.async_refresh()
assert refresh_runs == ["1", "2"] assert refresh_runs == [1, 2]
hass.states.async_set("sensor.two", "any") hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1", "2"] assert refresh_runs == [1, 2]
hass.states.async_set("sensor.three", "unknown") hass.states.async_set("sensor.three", "unknown")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1", "2"] assert refresh_runs == [1, 2]
info.async_refresh() info.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["1", "2", "3"] assert refresh_runs == [1, 2, 3]
async def test_specifically_referenced_entity_is_not_rate_limited(hass): async def test_specifically_referenced_entity_is_not_rate_limited(hass):
@ -1628,19 +1628,19 @@ async def test_track_two_templates_with_different_rate_limits(hass):
info.async_refresh() info.async_refresh()
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0"] assert refresh_runs[template_one] == [0]
assert refresh_runs[template_five] == ["0"] assert refresh_runs[template_five] == [0]
hass.states.async_set("sensor.one", "any") hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0"] assert refresh_runs[template_one] == [0]
assert refresh_runs[template_five] == ["0"] assert refresh_runs[template_five] == [0]
info.async_refresh() info.async_refresh()
assert refresh_runs[template_one] == ["0", "1"] assert refresh_runs[template_one] == [0, 1]
assert refresh_runs[template_five] == ["0", "1"] assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.two", "any") hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1"] assert refresh_runs[template_one] == [0, 1]
assert refresh_runs[template_five] == ["0", "1"] assert refresh_runs[template_five] == [0, 1]
next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 1) next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 1)
with patch( with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
@ -1648,20 +1648,20 @@ async def test_track_two_templates_with_different_rate_limits(hass):
async_fire_time_changed(hass, next_time) async_fire_time_changed(hass, next_time)
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"] assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == ["0", "1"] assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.three", "any") hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"] assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == ["0", "1"] assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.four", "any") hass.states.async_set("sensor.four", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"] assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == ["0", "1"] assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.five", "any") hass.states.async_set("sensor.five", "any")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"] assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == ["0", "1"] assert refresh_runs[template_five] == [0, 1]
async def test_string(hass): async def test_string(hass):
@ -1702,7 +1702,7 @@ async def test_track_template_result_refresh_cancel(hass):
hass.states.async_set("switch.test", "off") hass.states.async_set("switch.test", "off")
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == ["False"] assert refresh_runs == [False]
assert len(refresh_runs) == 1 assert len(refresh_runs) == 1
@ -1770,9 +1770,9 @@ async def test_async_track_template_result_multiple_templates(hass):
assert refresh_runs == [ assert refresh_runs == [
[ [
TrackTemplateResult(template_1, None, "True"), TrackTemplateResult(template_1, None, True),
TrackTemplateResult(template_2, None, "True"), TrackTemplateResult(template_2, None, True),
TrackTemplateResult(template_3, None, "False"), TrackTemplateResult(template_3, None, False),
] ]
] ]
@ -1782,9 +1782,9 @@ async def test_async_track_template_result_multiple_templates(hass):
assert refresh_runs == [ assert refresh_runs == [
[ [
TrackTemplateResult(template_1, "True", "False"), TrackTemplateResult(template_1, True, False),
TrackTemplateResult(template_2, "True", "False"), TrackTemplateResult(template_2, True, False),
TrackTemplateResult(template_3, "False", "True"), TrackTemplateResult(template_3, False, True),
] ]
] ]
@ -1793,7 +1793,7 @@ async def test_async_track_template_result_multiple_templates(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
assert refresh_runs == [ assert refresh_runs == [
[TrackTemplateResult(template_4, None, "['binary_sensor.test']")] [TrackTemplateResult(template_4, None, ["binary_sensor.test"])]
] ]
@ -1827,10 +1827,10 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(hass
assert refresh_runs == [ assert refresh_runs == [
[ [
TrackTemplateResult(template_1, None, "True"), TrackTemplateResult(template_1, None, True),
TrackTemplateResult(template_2, None, "True"), TrackTemplateResult(template_2, None, True),
TrackTemplateResult(template_3, None, "False"), TrackTemplateResult(template_3, None, False),
TrackTemplateResult(template_4, None, "['switch.test']"), TrackTemplateResult(template_4, None, ["switch.test"]),
] ]
] ]
@ -1840,9 +1840,9 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(hass
assert refresh_runs == [ assert refresh_runs == [
[ [
TrackTemplateResult(template_1, "True", "False"), TrackTemplateResult(template_1, True, False),
TrackTemplateResult(template_2, "True", "False"), TrackTemplateResult(template_2, True, False),
TrackTemplateResult(template_3, "False", "True"), TrackTemplateResult(template_3, False, True),
] ]
] ]
@ -1859,7 +1859,7 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(hass
assert refresh_runs == [ assert refresh_runs == [
[ [
TrackTemplateResult( TrackTemplateResult(
template_4, "['switch.test']", "['switch.new', 'switch.test']" template_4, ["switch.test"], ["switch.new", "switch.test"]
) )
] ]
] ]

View file

@ -194,8 +194,8 @@ async def test_multiple_runs_no_wait(hass):
calls.append(service) calls.append(service)
logger.debug("simulated service (%s:%s) started", fire, listen) logger.debug("simulated service (%s:%s) started", fire, listen)
unsub = hass.bus.async_listen(listen, service_done_cb) unsub = hass.bus.async_listen(str(listen), service_done_cb)
hass.bus.async_fire(fire) hass.bus.async_fire(str(fire))
await service_done.wait() await service_done.wait()
unsub() unsub()
@ -834,14 +834,14 @@ async def test_wait_variables_out(hass, mode, action_type):
assert not script_obj.is_running assert not script_obj.is_running
assert len(events) == 1 assert len(events) == 1
if action_type == "template": if action_type == "template":
assert events[0].data["completed"] == str(mode != "timeout_not_finish") assert events[0].data["completed"] == (mode != "timeout_not_finish")
elif mode != "timeout_not_finish": elif mode != "timeout_not_finish":
assert "'to_state': <state switch.test=off" in events[0].data["trigger"] assert "'to_state': <state switch.test=off" in events[0].data["trigger"]
else: else:
assert events[0].data["trigger"] == "None" assert events[0].data["trigger"] is None
remaining = events[0].data["remaining"] remaining = events[0].data["remaining"]
if mode == "no_timeout": if mode == "no_timeout":
assert remaining == "None" assert remaining is None
elif mode == "timeout_finish": elif mode == "timeout_finish":
assert 0.0 < float(remaining) < 5 assert 0.0 < float(remaining) < 5
else: else:
@ -977,9 +977,9 @@ async def test_repeat_count(hass):
assert len(events) == count assert len(events) == count
for index, event in enumerate(events): for index, event in enumerate(events):
assert event.data.get("first") == str(index == 0) assert event.data.get("first") == (index == 0)
assert event.data.get("index") == str(index + 1) assert event.data.get("index") == index + 1
assert event.data.get("last") == str(index == count - 1) assert event.data.get("last") == (index == count - 1)
@pytest.mark.parametrize("condition", ["while", "until"]) @pytest.mark.parametrize("condition", ["while", "until"])
@ -1052,8 +1052,8 @@ async def test_repeat_conditional(hass, condition, direct_template):
assert len(events) == count assert len(events) == count
for index, event in enumerate(events): for index, event in enumerate(events):
assert event.data.get("first") == str(index == 0) assert event.data.get("first") == (index == 0)
assert event.data.get("index") == str(index + 1) assert event.data.get("index") == index + 1
@pytest.mark.parametrize("condition", ["while", "until"]) @pytest.mark.parametrize("condition", ["while", "until"])
@ -1089,8 +1089,8 @@ async def test_repeat_var_in_condition(hass, condition):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"variables,first_last,inside_x", "variables,first_last,inside_x",
[ [
(None, {"repeat": "None", "x": "None"}, "None"), (None, {"repeat": None, "x": None}, None),
(MappingProxyType({"x": 1}), {"repeat": "None", "x": "1"}, "1"), (MappingProxyType({"x": 1}), {"repeat": None, "x": 1}, 1),
], ],
) )
async def test_repeat_nested(hass, variables, first_last, inside_x): async def test_repeat_nested(hass, variables, first_last, inside_x):
@ -1168,14 +1168,14 @@ async def test_repeat_nested(hass, variables, first_last, inside_x):
assert events[-1].data == first_last assert events[-1].data == first_last
for index, result in enumerate( for index, result in enumerate(
( (
("True", "1", "False", inside_x), (True, 1, False, inside_x),
("True", "1", "False", inside_x), (True, 1, False, inside_x),
("False", "2", "True", inside_x), (False, 2, True, inside_x),
("True", "1", "False", inside_x), (True, 1, False, inside_x),
("False", "2", "True", inside_x), (False, 2, True, inside_x),
("True", "1", "False", inside_x), (True, 1, False, inside_x),
("False", "2", "True", inside_x), (False, 2, True, inside_x),
("False", "2", "True", inside_x), (False, 2, True, inside_x),
), ),
1, 1,
): ):
@ -1827,8 +1827,8 @@ async def test_set_redefines_variable(hass, caplog):
await script_obj.async_run(context=Context()) await script_obj.async_run(context=Context())
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_calls[0].data["value"] == "1" assert mock_calls[0].data["value"] == 1
assert mock_calls[1].data["value"] == "2" assert mock_calls[1].data["value"] == 2
async def test_validate_action_config(hass): async def test_validate_action_config(hass):

View file

@ -50,7 +50,7 @@ async def test_template_vars(hass):
"""Test template vars.""" """Test template vars."""
var = cv.SCRIPT_VARIABLES_SCHEMA({"hello": "{{ 1 + 1 }}"}) var = cv.SCRIPT_VARIABLES_SCHEMA({"hello": "{{ 1 + 1 }}"})
rendered = var.async_render(hass, None) rendered = var.async_render(hass, None)
assert rendered == {"hello": "2"} assert rendered == {"hello": 2}
async def test_template_vars_run_args(hass): async def test_template_vars_run_args(hass):
@ -70,7 +70,7 @@ async def test_template_vars_run_args(hass):
) )
assert rendered == { assert rendered == {
"run_var_ex": 5, "run_var_ex": 5,
"something": "6", "something": 6,
"something_2": 1, "something_2": 1,
} }
@ -79,7 +79,7 @@ async def test_template_vars_no_default(hass):
"""Test template vars.""" """Test template vars."""
var = cv.SCRIPT_VARIABLES_SCHEMA({"hello": "{{ 1 + 1 }}"}) var = cv.SCRIPT_VARIABLES_SCHEMA({"hello": "{{ 1 + 1 }}"})
rendered = var.async_render(hass, None, render_as_defaults=False) rendered = var.async_render(hass, None, render_as_defaults=False)
assert rendered == {"hello": "2"} assert rendered == {"hello": 2}
async def test_template_vars_run_args_no_default(hass): async def test_template_vars_run_args_no_default(hass):
@ -100,8 +100,8 @@ async def test_template_vars_run_args_no_default(hass):
) )
assert rendered == { assert rendered == {
"run_var_ex": 5, "run_var_ex": 5,
"something": "6", "something": 6,
"something_2": "6", "something_2": 6,
} }

View file

@ -7,6 +7,7 @@ import pytest
import pytz import pytz
from homeassistant.components import group from homeassistant.components import group
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
LENGTH_METERS, LENGTH_METERS,
@ -173,7 +174,7 @@ def test_iterating_all_states_unavailable(hass):
hass.states.async_set("sensor.temperature", 10) hass.states.async_set("sensor.temperature", 10)
info = render_to_info(hass, tmpl_str) info = render_to_info(hass, tmpl_str)
assert_result_info(info, "1", entities=[], all_states=True) assert_result_info(info, 1, entities=[], all_states=True)
def test_iterating_domain_states(hass): def test_iterating_domain_states(hass):
@ -205,14 +206,14 @@ def test_float(hass):
template.Template( template.Template(
"{{ float(states.sensor.temperature.state) }}", hass "{{ float(states.sensor.temperature.state) }}", hass
).async_render() ).async_render()
== "12.0" == 12.0
) )
assert ( assert (
template.Template( template.Template(
"{{ float(states.sensor.temperature.state) > 11 }}", hass "{{ float(states.sensor.temperature.state) > 11 }}", hass
).async_render() ).async_render()
== "True" is True
) )
assert ( assert (
@ -229,41 +230,41 @@ def test_rounding_value(hass):
template.Template( template.Template(
"{{ states.sensor.temperature.state | round(1) }}", hass "{{ states.sensor.temperature.state | round(1) }}", hass
).async_render() ).async_render()
== "12.8" == 12.8
) )
assert ( assert (
template.Template( template.Template(
"{{ states.sensor.temperature.state | multiply(10) | round }}", hass "{{ states.sensor.temperature.state | multiply(10) | round }}", hass
).async_render() ).async_render()
== "128" == 128
) )
assert ( assert (
template.Template( template.Template(
'{{ states.sensor.temperature.state | round(1, "floor") }}', hass '{{ states.sensor.temperature.state | round(1, "floor") }}', hass
).async_render() ).async_render()
== "12.7" == 12.7
) )
assert ( assert (
template.Template( template.Template(
'{{ states.sensor.temperature.state | round(1, "ceil") }}', hass '{{ states.sensor.temperature.state | round(1, "ceil") }}', hass
).async_render() ).async_render()
== "12.8" == 12.8
) )
assert ( assert (
template.Template( template.Template(
'{{ states.sensor.temperature.state | round(1, "half") }}', hass '{{ states.sensor.temperature.state | round(1, "half") }}', hass
).async_render() ).async_render()
== "13.0" == 13.0
) )
def test_rounding_value_get_original_value_on_error(hass): def test_rounding_value_get_original_value_on_error(hass):
"""Test rounding value get original value on error.""" """Test rounding value get original value on error."""
assert template.Template("{{ None | round }}", hass).async_render() == "None" assert template.Template("{{ None | round }}", hass).async_render() is None
assert ( assert (
template.Template('{{ "no_number" | round }}', hass).async_render() template.Template('{{ "no_number" | round }}', hass).async_render()
@ -273,7 +274,7 @@ def test_rounding_value_get_original_value_on_error(hass):
def test_multiply(hass): def test_multiply(hass):
"""Test multiply.""" """Test multiply."""
tests = {None: "None", 10: "100", '"abcd"': "abcd"} tests = {None: None, 10: 100, '"abcd"': "abcd"}
for inp, out in tests.items(): for inp, out in tests.items():
assert ( assert (
@ -287,11 +288,11 @@ def test_multiply(hass):
def test_logarithm(hass): def test_logarithm(hass):
"""Test logarithm.""" """Test logarithm."""
tests = [ tests = [
(4, 2, "2.0"), (4, 2, 2.0),
(1000, 10, "3.0"), (1000, 10, 3.0),
(math.e, "", "1.0"), (math.e, "", 1.0),
('"invalid"', "_", "invalid"), ('"invalid"', "_", "invalid"),
(10, '"invalid"', "10.0"), (10, '"invalid"', 10.0),
] ]
for value, base, expected in tests: for value, base, expected in tests:
@ -313,11 +314,11 @@ def test_logarithm(hass):
def test_sine(hass): def test_sine(hass):
"""Test sine.""" """Test sine."""
tests = [ tests = [
(0, "0.0"), (0, 0.0),
(math.pi / 2, "1.0"), (math.pi / 2, 1.0),
(math.pi, "0.0"), (math.pi, 0.0),
(math.pi * 1.5, "-1.0"), (math.pi * 1.5, -1.0),
(math.pi / 10, "0.309"), (math.pi / 10, 0.309),
('"duck"', "duck"), ('"duck"', "duck"),
] ]
@ -331,11 +332,11 @@ def test_sine(hass):
def test_cos(hass): def test_cos(hass):
"""Test cosine.""" """Test cosine."""
tests = [ tests = [
(0, "1.0"), (0, 1.0),
(math.pi / 2, "0.0"), (math.pi / 2, 0.0),
(math.pi, "-1.0"), (math.pi, -1.0),
(math.pi * 1.5, "-0.0"), (math.pi * 1.5, -0.0),
(math.pi / 10, "0.951"), (math.pi / 10, 0.951),
("'error'", "error"), ("'error'", "error"),
] ]
@ -349,11 +350,11 @@ def test_cos(hass):
def test_tan(hass): def test_tan(hass):
"""Test tangent.""" """Test tangent."""
tests = [ tests = [
(0, "0.0"), (0, 0.0),
(math.pi, "-0.0"), (math.pi, -0.0),
(math.pi / 180 * 45, "1.0"), (math.pi / 180 * 45, 1.0),
(math.pi / 180 * 90, "1.633123935319537e+16"), (math.pi / 180 * 90, 1.633123935319537e16),
(math.pi / 180 * 135, "-1.0"), (math.pi / 180 * 135, -1.0),
("'error'", "error"), ("'error'", "error"),
] ]
@ -367,11 +368,11 @@ def test_tan(hass):
def test_sqrt(hass): def test_sqrt(hass):
"""Test square root.""" """Test square root."""
tests = [ tests = [
(0, "0.0"), (0, 0.0),
(1, "1.0"), (1, 1.0),
(2, "1.414"), (2, 1.414),
(10, "3.162"), (10, 3.162),
(100, "10.0"), (100, 10.0),
("'error'", "error"), ("'error'", "error"),
] ]
@ -385,13 +386,13 @@ def test_sqrt(hass):
def test_arc_sine(hass): def test_arc_sine(hass):
"""Test arcus sine.""" """Test arcus sine."""
tests = [ tests = [
(-2.0, "-2.0"), # value error (-2.0, -2.0), # value error
(-1.0, "-1.571"), (-1.0, -1.571),
(-0.5, "-0.524"), (-0.5, -0.524),
(0.0, "0.0"), (0.0, 0.0),
(0.5, "0.524"), (0.5, 0.524),
(1.0, "1.571"), (1.0, 1.571),
(2.0, "2.0"), # value error (2.0, 2.0), # value error
('"error"', "error"), ('"error"', "error"),
] ]
@ -405,13 +406,13 @@ def test_arc_sine(hass):
def test_arc_cos(hass): def test_arc_cos(hass):
"""Test arcus cosine.""" """Test arcus cosine."""
tests = [ tests = [
(-2.0, "-2.0"), # value error (-2.0, -2.0), # value error
(-1.0, "3.142"), (-1.0, 3.142),
(-0.5, "2.094"), (-0.5, 2.094),
(0.0, "1.571"), (0.0, 1.571),
(0.5, "1.047"), (0.5, 1.047),
(1.0, "0.0"), (1.0, 0.0),
(2.0, "2.0"), # value error (2.0, 2.0), # value error
('"error"', "error"), ('"error"', "error"),
] ]
@ -425,15 +426,15 @@ def test_arc_cos(hass):
def test_arc_tan(hass): def test_arc_tan(hass):
"""Test arcus tangent.""" """Test arcus tangent."""
tests = [ tests = [
(-10.0, "-1.471"), (-10.0, -1.471),
(-2.0, "-1.107"), (-2.0, -1.107),
(-1.0, "-0.785"), (-1.0, -0.785),
(-0.5, "-0.464"), (-0.5, -0.464),
(0.0, "0.0"), (0.0, 0.0),
(0.5, "0.464"), (0.5, 0.464),
(1.0, "0.785"), (1.0, 0.785),
(2.0, "1.107"), (2.0, 1.107),
(10.0, "1.471"), (10.0, 1.471),
('"error"', "error"), ('"error"', "error"),
] ]
@ -447,19 +448,19 @@ def test_arc_tan(hass):
def test_arc_tan2(hass): def test_arc_tan2(hass):
"""Test two parameter version of arcus tangent.""" """Test two parameter version of arcus tangent."""
tests = [ tests = [
(-10.0, -10.0, "-2.356"), (-10.0, -10.0, -2.356),
(-10.0, 0.0, "-1.571"), (-10.0, 0.0, -1.571),
(-10.0, 10.0, "-0.785"), (-10.0, 10.0, -0.785),
(0.0, -10.0, "3.142"), (0.0, -10.0, 3.142),
(0.0, 0.0, "0.0"), (0.0, 0.0, 0.0),
(0.0, 10.0, "0.0"), (0.0, 10.0, 0.0),
(10.0, -10.0, "2.356"), (10.0, -10.0, 2.356),
(10.0, 0.0, "1.571"), (10.0, 0.0, 1.571),
(10.0, 10.0, "0.785"), (10.0, 10.0, 0.785),
(-4.0, 3.0, "-0.927"), (-4.0, 3.0, -0.927),
(-1.0, 2.0, "-0.464"), (-1.0, 2.0, -0.464),
(2.0, 1.0, "1.107"), (2.0, 1.0, 1.107),
('"duck"', '"goose"', "('duck', 'goose')"), ('"duck"', '"goose"', ("duck", "goose")),
] ]
for y, x, expected in tests: for y, x, expected in tests:
@ -486,26 +487,26 @@ def test_strptime(hass):
("2016-10-19", "%Y-%m-%d", None), ("2016-10-19", "%Y-%m-%d", None),
("2016", "%Y", None), ("2016", "%Y", None),
("15:22:05", "%H:%M:%S", None), ("15:22:05", "%H:%M:%S", None),
("1469119144", "%Y", "1469119144"), ("1469119144", "%Y", 1469119144),
("invalid", "%Y", "invalid"), ("invalid", "%Y", "invalid"),
] ]
for inp, fmt, expected in tests: for inp, fmt, expected in tests:
if expected is None: if expected is None:
expected = datetime.strptime(inp, fmt) expected = str(datetime.strptime(inp, fmt))
temp = f"{{{{ strptime('{inp}', '{fmt}') }}}}" temp = f"{{{{ strptime('{inp}', '{fmt}') }}}}"
assert template.Template(temp, hass).async_render() == str(expected) assert template.Template(temp, hass).async_render() == expected
def test_timestamp_custom(hass): def test_timestamp_custom(hass):
"""Test the timestamps to custom filter.""" """Test the timestamps to custom filter."""
now = dt_util.utcnow() now = dt_util.utcnow()
tests = [ tests = [
(None, None, None, "None"), (None, None, None, None),
(1469119144, None, True, "2016-07-21 16:39:04"), (1469119144, None, True, "2016-07-21 16:39:04"),
(1469119144, "%Y", True, "2016"), (1469119144, "%Y", True, 2016),
(1469119144, "invalid", True, "invalid"), (1469119144, "invalid", True, "invalid"),
(dt_util.as_timestamp(now), None, False, now.strftime("%Y-%m-%d %H:%M:%S")), (dt_util.as_timestamp(now), None, False, now.strftime("%Y-%m-%d %H:%M:%S")),
] ]
@ -523,7 +524,7 @@ def test_timestamp_custom(hass):
def test_timestamp_local(hass): def test_timestamp_local(hass):
"""Test the timestamps to local filter.""" """Test the timestamps to local filter."""
tests = {None: "None", 1469119144: "2016-07-21 16:39:04"} tests = {None: None, 1469119144: "2016-07-21 16:39:04"}
for inp, out in tests.items(): for inp, out in tests.items():
assert ( assert (
@ -550,7 +551,7 @@ def test_to_json(hass):
# Note that we're not testing the actual json.loads and json.dumps methods, # Note that we're not testing the actual json.loads and json.dumps methods,
# only the filters, so we don't need to be exhaustive with our sample JSON. # only the filters, so we don't need to be exhaustive with our sample JSON.
expected_result = '{"Foo": "Bar"}' expected_result = {"Foo": "Bar"}
actual_result = template.Template( actual_result = template.Template(
"{{ {'Foo': 'Bar'} | to_json }}", hass "{{ {'Foo': 'Bar'} | to_json }}", hass
).async_render() ).async_render()
@ -571,17 +572,17 @@ def test_from_json(hass):
def test_min(hass): def test_min(hass):
"""Test the min filter.""" """Test the min filter."""
assert template.Template("{{ [1, 2, 3] | min }}", hass).async_render() == "1" assert template.Template("{{ [1, 2, 3] | min }}", hass).async_render() == 1
def test_max(hass): def test_max(hass):
"""Test the max filter.""" """Test the max filter."""
assert template.Template("{{ [1, 2, 3] | max }}", hass).async_render() == "3" assert template.Template("{{ [1, 2, 3] | max }}", hass).async_render() == 3
def test_ord(hass): def test_ord(hass):
"""Test the ord filter.""" """Test the ord filter."""
assert template.Template('{{ "d" | ord }}', hass).async_render() == "100" assert template.Template('{{ "d" | ord }}', hass).async_render() == 100
def test_base64_encode(hass): def test_base64_encode(hass):
@ -626,7 +627,7 @@ def test_timestamp_utc(hass):
"""Test the timestamps to local filter.""" """Test the timestamps to local filter."""
now = dt_util.utcnow() now = dt_util.utcnow()
tests = { tests = {
None: "None", None: None,
1469119144: "2016-07-21 16:39:04", 1469119144: "2016-07-21 16:39:04",
dt_util.as_timestamp(now): now.strftime("%Y-%m-%d %H:%M:%S"), dt_util.as_timestamp(now): now.strftime("%Y-%m-%d %H:%M:%S"),
} }
@ -641,20 +642,19 @@ def test_timestamp_utc(hass):
def test_as_timestamp(hass): def test_as_timestamp(hass):
"""Test the as_timestamp function.""" """Test the as_timestamp function."""
assert ( assert (
template.Template('{{ as_timestamp("invalid") }}', hass).async_render() template.Template('{{ as_timestamp("invalid") }}', hass).async_render() is None
== "None"
) )
hass.mock = None hass.mock = None
assert ( assert (
template.Template("{{ as_timestamp(states.mock) }}", hass).async_render() template.Template("{{ as_timestamp(states.mock) }}", hass).async_render()
== "None" is None
) )
tpl = ( tpl = (
'{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", ' '{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", '
'"%Y-%m-%dT%H:%M:%S%z")) }}' '"%Y-%m-%dT%H:%M:%S%z")) }}'
) )
assert template.Template(tpl, hass).async_render() == "1706951424.0" assert template.Template(tpl, hass).async_render() == 1706951424.0
@patch.object(random, "choice") @patch.object(random, "choice")
@ -669,22 +669,19 @@ def test_random_every_time(test_choice, hass):
def test_passing_vars_as_keywords(hass): def test_passing_vars_as_keywords(hass):
"""Test passing variables as keywords.""" """Test passing variables as keywords."""
assert template.Template("{{ hello }}", hass).async_render(hello=127) == "127" assert template.Template("{{ hello }}", hass).async_render(hello=127) == 127
def test_passing_vars_as_vars(hass): def test_passing_vars_as_vars(hass):
"""Test passing variables as variables.""" """Test passing variables as variables."""
assert template.Template("{{ hello }}", hass).async_render({"hello": 127}) == "127" assert template.Template("{{ hello }}", hass).async_render({"hello": 127}) == 127
def test_passing_vars_as_list(hass): def test_passing_vars_as_list(hass):
"""Test passing variables as list.""" """Test passing variables as list."""
assert ( assert template.render_complex(
template.render_complex( template.Template("{{ hello }}", hass), {"hello": ["foo", "bar"]}
template.Template("{{ hello }}", hass), {"hello": ["foo", "bar"]} ) == ["foo", "bar"]
)
== "['foo', 'bar']"
)
def test_passing_vars_as_list_element(hass): def test_passing_vars_as_list_element(hass):
@ -709,12 +706,9 @@ def test_passing_vars_as_dict_element(hass):
def test_passing_vars_as_dict(hass): def test_passing_vars_as_dict(hass):
"""Test passing variables as list.""" """Test passing variables as list."""
assert ( assert template.render_complex(
template.render_complex( template.Template("{{ hello }}", hass), {"hello": {"foo": "bar"}}
template.Template("{{ hello }}", hass), {"hello": {"foo": "bar"}} ) == {"foo": "bar"}
)
== "{'foo': 'bar'}"
)
def test_render_with_possible_json_value_with_valid_json(hass): def test_render_with_possible_json_value_with_valid_json(hass):
@ -801,7 +795,7 @@ def test_is_state(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "False" assert tpl.async_render() is False
def test_is_state_attr(hass): def test_is_state_attr(hass):
@ -821,7 +815,7 @@ def test_is_state_attr(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "False" assert tpl.async_render() is False
def test_state_attr(hass): def test_state_attr(hass):
@ -841,7 +835,7 @@ def test_state_attr(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
def test_states_function(hass): def test_states_function(hass):
@ -994,7 +988,7 @@ def test_regex_match(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
tpl = template.Template( tpl = template.Template(
""" """
@ -1002,7 +996,7 @@ def test_regex_match(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
tpl = template.Template( tpl = template.Template(
""" """
@ -1010,7 +1004,7 @@ def test_regex_match(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "False" assert tpl.async_render() is False
tpl = template.Template( tpl = template.Template(
""" """
@ -1018,7 +1012,7 @@ def test_regex_match(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
def test_regex_search(hass): def test_regex_search(hass):
@ -1029,7 +1023,7 @@ def test_regex_search(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
tpl = template.Template( tpl = template.Template(
""" """
@ -1037,7 +1031,7 @@ def test_regex_search(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
tpl = template.Template( tpl = template.Template(
""" """
@ -1045,7 +1039,7 @@ def test_regex_search(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
tpl = template.Template( tpl = template.Template(
""" """
@ -1053,7 +1047,7 @@ def test_regex_search(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "True" assert tpl.async_render() is True
def test_regex_replace(hass): def test_regex_replace(hass):
@ -1072,7 +1066,7 @@ def test_regex_replace(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == "['Home Assistant test']" assert tpl.async_render() == ["Home Assistant test"]
def test_regex_findall_index(hass): def test_regex_findall_index(hass):
@ -1110,21 +1104,21 @@ def test_bitwise_and(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == str(8 & 8) assert tpl.async_render() == 8 & 8
tpl = template.Template( tpl = template.Template(
""" """
{{ 10 | bitwise_and(2) }} {{ 10 | bitwise_and(2) }}
""", """,
hass, hass,
) )
assert tpl.async_render() == str(10 & 2) assert tpl.async_render() == 10 & 2
tpl = template.Template( tpl = template.Template(
""" """
{{ 8 | bitwise_and(2) }} {{ 8 | bitwise_and(2) }}
""", """,
hass, hass,
) )
assert tpl.async_render() == str(8 & 2) assert tpl.async_render() == 8 & 2
def test_bitwise_or(hass): def test_bitwise_or(hass):
@ -1135,21 +1129,21 @@ def test_bitwise_or(hass):
""", """,
hass, hass,
) )
assert tpl.async_render() == str(8 | 8) assert tpl.async_render() == 8 | 8
tpl = template.Template( tpl = template.Template(
""" """
{{ 10 | bitwise_or(2) }} {{ 10 | bitwise_or(2) }}
""", """,
hass, hass,
) )
assert tpl.async_render() == str(10 | 2) assert tpl.async_render() == 10 | 2
tpl = template.Template( tpl = template.Template(
""" """
{{ 8 | bitwise_or(2) }} {{ 8 | bitwise_or(2) }}
""", """,
hass, hass,
) )
assert tpl.async_render() == str(8 | 2) assert tpl.async_render() == 8 | 2
def test_distance_function_with_1_state(hass): def test_distance_function_with_1_state(hass):
@ -1159,7 +1153,7 @@ def test_distance_function_with_1_state(hass):
"test.object", "happy", {"latitude": 32.87336, "longitude": -117.22943} "test.object", "happy", {"latitude": 32.87336, "longitude": -117.22943}
) )
tpl = template.Template("{{ distance(states.test.object) | round }}", hass) tpl = template.Template("{{ distance(states.test.object) | round }}", hass)
assert tpl.async_render() == "187" assert tpl.async_render() == 187
def test_distance_function_with_2_states(hass): def test_distance_function_with_2_states(hass):
@ -1176,14 +1170,14 @@ def test_distance_function_with_2_states(hass):
tpl = template.Template( tpl = template.Template(
"{{ distance(states.test.object, states.test.object_2) | round }}", hass "{{ distance(states.test.object, states.test.object_2) | round }}", hass
) )
assert tpl.async_render() == "187" assert tpl.async_render() == 187
def test_distance_function_with_1_coord(hass): def test_distance_function_with_1_coord(hass):
"""Test distance function with 1 coord.""" """Test distance function with 1 coord."""
_set_up_units(hass) _set_up_units(hass)
tpl = template.Template('{{ distance("32.87336", "-117.22943") | round }}', hass) tpl = template.Template('{{ distance("32.87336", "-117.22943") | round }}', hass)
assert tpl.async_render() == "187" assert tpl.async_render() == 187
def test_distance_function_with_2_coords(hass): def test_distance_function_with_2_coords(hass):
@ -1195,7 +1189,7 @@ def test_distance_function_with_2_coords(hass):
% (hass.config.latitude, hass.config.longitude), % (hass.config.latitude, hass.config.longitude),
hass, hass,
).async_render() ).async_render()
== "187" == 187
) )
@ -1211,29 +1205,29 @@ def test_distance_function_with_1_state_1_coord(hass):
'{{ distance("32.87336", "-117.22943", states.test.object_2) ' "| round }}", '{{ distance("32.87336", "-117.22943", states.test.object_2) ' "| round }}",
hass, hass,
) )
assert tpl.async_render() == "187" assert tpl.async_render() == 187
tpl2 = template.Template( tpl2 = template.Template(
'{{ distance(states.test.object_2, "32.87336", "-117.22943") ' "| round }}", '{{ distance(states.test.object_2, "32.87336", "-117.22943") ' "| round }}",
hass, hass,
) )
assert tpl2.async_render() == "187" assert tpl2.async_render() == 187
def test_distance_function_return_none_if_invalid_state(hass): def test_distance_function_return_none_if_invalid_state(hass):
"""Test distance function return None if invalid state.""" """Test distance function return None if invalid state."""
hass.states.async_set("test.object_2", "happy", {"latitude": 10}) hass.states.async_set("test.object_2", "happy", {"latitude": 10})
tpl = template.Template("{{ distance(states.test.object_2) | round }}", hass) tpl = template.Template("{{ distance(states.test.object_2) | round }}", hass)
assert tpl.async_render() == "None" assert tpl.async_render() is None
def test_distance_function_return_none_if_invalid_coord(hass): def test_distance_function_return_none_if_invalid_coord(hass):
"""Test distance function return None if invalid coord.""" """Test distance function return None if invalid coord."""
assert ( assert (
template.Template('{{ distance("123", "abc") }}', hass).async_render() == "None" template.Template('{{ distance("123", "abc") }}', hass).async_render() is None
) )
assert template.Template('{{ distance("123") }}', hass).async_render() == "None" assert template.Template('{{ distance("123") }}', hass).async_render() is None
hass.states.async_set( hass.states.async_set(
"test.object_2", "test.object_2",
@ -1241,7 +1235,7 @@ def test_distance_function_return_none_if_invalid_coord(hass):
{"latitude": hass.config.latitude, "longitude": hass.config.longitude}, {"latitude": hass.config.latitude, "longitude": hass.config.longitude},
) )
tpl = template.Template('{{ distance("123", states.test_object_2) }}', hass) tpl = template.Template('{{ distance("123", states.test_object_2) }}', hass)
assert tpl.async_render() == "None" assert tpl.async_render() is None
def test_distance_function_with_2_entity_ids(hass): def test_distance_function_with_2_entity_ids(hass):
@ -1258,7 +1252,7 @@ def test_distance_function_with_2_entity_ids(hass):
tpl = template.Template( tpl = template.Template(
'{{ distance("test.object", "test.object_2") | round }}', hass '{{ distance("test.object", "test.object_2") | round }}', hass
) )
assert tpl.async_render() == "187" assert tpl.async_render() == 187
def test_distance_function_with_1_entity_1_coord(hass): def test_distance_function_with_1_entity_1_coord(hass):
@ -1272,7 +1266,7 @@ def test_distance_function_with_1_entity_1_coord(hass):
tpl = template.Template( tpl = template.Template(
'{{ distance("test.object", "32.87336", "-117.22943") | round }}', hass '{{ distance("test.object", "32.87336", "-117.22943") | round }}', hass
) )
assert tpl.async_render() == "187" assert tpl.async_render() == 187
def test_closest_function_home_vs_domain(hass): def test_closest_function_home_vs_domain(hass):
@ -1400,11 +1394,11 @@ async def test_closest_function_home_vs_group_state(hass):
async def test_expand(hass): async def test_expand(hass):
"""Test expand function.""" """Test expand function."""
info = render_to_info(hass, "{{ expand('test.object') }}") info = render_to_info(hass, "{{ expand('test.object') }}")
assert_result_info(info, "[]", ["test.object"]) assert_result_info(info, [], ["test.object"])
assert info.rate_limit is None assert info.rate_limit is None
info = render_to_info(hass, "{{ expand(56) }}") info = render_to_info(hass, "{{ expand(56) }}")
assert_result_info(info, "[]") assert_result_info(info, [])
assert info.rate_limit is None assert info.rate_limit is None
hass.states.async_set("test.object", "happy") hass.states.async_set("test.object", "happy")
@ -1476,7 +1470,7 @@ async def test_expand(hass):
) )
assert_result_info( assert_result_info(
info, info,
str(200.2 + 400.4), 200.2 + 400.4,
{"group.power_sensors", "sensor.power_1", "sensor.power_2", "sensor.power_3"}, {"group.power_sensors", "sensor.power_1", "sensor.power_2", "sensor.power_3"},
) )
assert info.rate_limit is None assert info.rate_limit is None
@ -1593,7 +1587,7 @@ def test_async_render_to_info_with_complex_branching(hass):
{"otherdomain": "sensor"}, {"otherdomain": "sensor"},
) )
assert_result_info(info, "['sensor.a']", {"light.a", "light.b"}, {"sensor"}) assert_result_info(info, ["sensor.a"], {"light.a", "light.b"}, {"sensor"})
assert info.rate_limit == template.DEFAULT_RATE_LIMIT assert info.rate_limit == template.DEFAULT_RATE_LIMIT
@ -1820,7 +1814,7 @@ def test_closest_function_invalid_state(hass):
for state in ("states.zone.non_existing", '"zone.non_existing"'): for state in ("states.zone.non_existing", '"zone.non_existing"'):
assert ( assert (
template.Template("{{ closest(%s, states) }}" % state, hass).async_render() template.Template("{{ closest(%s, states) }}" % state, hass).async_render()
== "None" is None
) )
@ -1836,7 +1830,7 @@ def test_closest_function_state_with_invalid_location(hass):
template.Template( template.Template(
"{{ closest(states.test_domain.closest_home, states) }}", hass "{{ closest(states.test_domain.closest_home, states) }}", hass
).async_render() ).async_render()
== "None" is None
) )
@ -1855,13 +1849,13 @@ def test_closest_function_invalid_coordinates(hass):
template.Template( template.Template(
'{{ closest("invalid", "coord", states) }}', hass '{{ closest("invalid", "coord", states) }}', hass
).async_render() ).async_render()
== "None" is None
) )
assert ( assert (
template.Template( template.Template(
'{{ states | closest("invalid", "coord") }}', hass '{{ states | closest("invalid", "coord") }}', hass
).async_render() ).async_render()
== "None" is None
) )
@ -2004,7 +1998,7 @@ async def test_async_render_to_info_in_conditional(hass):
tmp = template.Template(template_str, hass) tmp = template.Template(template_str, hass)
info = tmp.async_render_to_info() info = tmp.async_render_to_info()
assert_result_info(info, "False", ["sensor.xyz"], []) assert_result_info(info, False, ["sensor.xyz"], [])
hass.states.async_set("sensor.xyz", "dog") hass.states.async_set("sensor.xyz", "dog")
hass.states.async_set("sensor.cow", "True") hass.states.async_set("sensor.cow", "True")
@ -2020,7 +2014,7 @@ async def test_async_render_to_info_in_conditional(hass):
tmp = template.Template(template_str, hass) tmp = template.Template(template_str, hass)
info = tmp.async_render_to_info() info = tmp.async_render_to_info()
assert_result_info(info, "True", ["sensor.xyz", "sensor.cow"], []) assert_result_info(info, True, ["sensor.xyz", "sensor.cow"], [])
hass.states.async_set("sensor.xyz", "sheep") hass.states.async_set("sensor.xyz", "sheep")
hass.states.async_set("sensor.pig", "oink") hass.states.async_set("sensor.pig", "oink")
@ -2327,17 +2321,17 @@ def test_length_of_states(hass):
hass.states.async_set("climate.test2", "cooling") hass.states.async_set("climate.test2", "cooling")
tpl = template.Template("{{ states | length }}", hass) tpl = template.Template("{{ states | length }}", hass)
assert tpl.async_render() == "3" assert tpl.async_render() == 3
tpl = template.Template("{{ states.sensor | length }}", hass) tpl = template.Template("{{ states.sensor | length }}", hass)
assert tpl.async_render() == "2" assert tpl.async_render() == 2
def test_render_complex_handling_non_template_values(hass): def test_render_complex_handling_non_template_values(hass):
"""Test that we can render non-template fields.""" """Test that we can render non-template fields."""
assert template.render_complex( assert template.render_complex(
{True: 1, False: template.Template("{{ hello }}", hass)}, {"hello": 2} {True: 1, False: template.Template("{{ hello }}", hass)}, {"hello": 2}
) == {True: 1, False: "2"} ) == {True: 1, False: 2}
def test_urlencode(hass): def test_urlencode(hass):
@ -2571,7 +2565,7 @@ async def test_state_attributes(hass):
"{{ states.sensor.test.state_with_unit }}", "{{ states.sensor.test.state_with_unit }}",
hass, hass,
) )
assert tpl.async_render() == "23" assert tpl.async_render() == 23
tpl = template.Template( tpl = template.Template(
"{{ states.sensor.test.invalid_prop }}", "{{ states.sensor.test.invalid_prop }}",
@ -2608,3 +2602,20 @@ async def test_unavailable_states(hass):
hass, hass,
) )
assert tpl.async_render() == "light.none, light.unavailable, light.unknown" assert tpl.async_render() == "light.none, light.unavailable, light.unknown"
async def test_legacy_templates(hass):
"""Test if old template behavior works when legacy templates are enabled."""
hass.states.async_set("sensor.temperature", "12")
assert (
template.Template("{{ states.sensor.temperature.state }}", hass).async_render()
== 12
)
await async_process_ha_core_config(hass, {"legacy_templates": True})
assert (
template.Template("{{ states.sensor.temperature.state }}", hass).async_render()
== "12"
)

View file

@ -471,6 +471,7 @@ async def test_loading_configuration(hass):
"external_url": "https://www.example.com", "external_url": "https://www.example.com",
"internal_url": "http://example.local", "internal_url": "http://example.local",
"media_dirs": {"mymedia": "/usr"}, "media_dirs": {"mymedia": "/usr"},
"legacy_templates": True,
}, },
) )
@ -487,6 +488,7 @@ async def test_loading_configuration(hass):
assert "/usr" in hass.config.allowlist_external_dirs assert "/usr" in hass.config.allowlist_external_dirs
assert hass.config.media_dirs == {"mymedia": "/usr"} assert hass.config.media_dirs == {"mymedia": "/usr"}
assert hass.config.config_source == config_util.SOURCE_YAML assert hass.config.config_source == config_util.SOURCE_YAML
assert hass.config.legacy_templates is True
async def test_loading_configuration_temperature_unit(hass): async def test_loading_configuration_temperature_unit(hass):