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:
data = await request.json()
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:
return self.json_message(
f"Error rendering template: {ex}", HTTP_BAD_REQUEST

View File

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

View File

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

View File

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

View File

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

View File

@ -120,7 +120,16 @@ class TemplateLock(TemplateEntity, LockEntity):
if isinstance(result, TemplateError):
self._state = None
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):
"""Register callbacks."""

View File

@ -136,7 +136,16 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
if isinstance(result, TemplateError):
self._state = None
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):
"""Register callbacks."""

View File

@ -32,6 +32,7 @@ from homeassistant.const import (
CONF_ID,
CONF_INTERNAL_URL,
CONF_LATITUDE,
CONF_LEGACY_TEMPLATES,
CONF_LONGITUDE,
CONF_MEDIA_DIRS,
CONF_NAME,
@ -224,6 +225,7 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend(
),
# pylint: disable=no-value-for-parameter
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_EXTERNAL_URL, "external_url"),
(CONF_MEDIA_DIRS, "media_dirs"),
(CONF_LEGACY_TEMPLATES, "legacy_templates"),
):
if key in config:
setattr(hac, attr, config[key])

View File

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

View File

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

View File

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

View File

@ -111,8 +111,8 @@ class TrackTemplateResult:
"""
template: Template
last_result: Union[str, None, TemplateError]
result: Union[str, TemplateError]
last_result: Any
result: Any
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."""
from ast import literal_eval
import asyncio
import base64
import collections.abc
@ -302,7 +303,7 @@ class Template:
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."""
if self.is_static:
return self.template.strip()
@ -315,7 +316,7 @@ class Template:
).result()
@callback
def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> str:
def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any:
"""Render given template.
This method must be run in the event loop.
@ -329,10 +330,27 @@ class Template:
kwargs.update(variables)
try:
return compiled.render(kwargs).strip()
render_result = compiled.render(kwargs)
except jinja2.TemplateError as 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(
self, timeout: float, variables: TemplateVarsType = None, **kwargs: Any
) -> bool:

View File

@ -104,7 +104,7 @@ class TestNotifyDemo(unittest.TestCase):
self.hass.block_till_done()
last_event = self.events[-1]
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):
"""Test that all data from the service gets forwarded to service."""

View File

@ -65,7 +65,7 @@ DEFAULT_CONFIG_CODE = {
"name": "test",
"state_topic": "alarm/state",
"command_topic": "alarm/command",
"code": "1234",
"code": "0123",
"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
"""
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"] = (
'{"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 common.async_alarm_disarm(hass, 1234)
await common.async_alarm_disarm(hass, "0123")
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,
)
mqtt_mock.async_publish.assert_called_once_with(
"set-position-topic", "38", 0, False
)
mqtt_mock.async_publish.assert_called_once_with("set-position-topic", 38, 0, False)
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 }}")
await hass.async_block_till_done()
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):

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(
[
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,
)

View File

@ -705,4 +705,4 @@ async def test_script_variables(hass, caplog):
await hass.services.async_call("script", "script3", {"break": 0}, blocking=True)
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 calls[0].domain == "light"
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"

View File

@ -662,7 +662,7 @@ def _verify(
"""Verify fan's state, speed and osc."""
state = hass.states.get(_TEST_FAN)
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_OSCILLATING) == expected_oscillating
assert attributes.get(ATTR_DIRECTION) == expected_direction

View File

@ -550,7 +550,7 @@ class TestTemplateLight:
)
self.hass.block_till_done()
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")
assert state is not None
@ -649,7 +649,7 @@ class TestTemplateLight:
common.turn_on(self.hass, "light.test_template_light", **{ATTR_BRIGHTNESS: 124})
self.hass.block_till_done()
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")
_LOGGER.info(str(state.attributes))
@ -802,7 +802,7 @@ class TestTemplateLight:
common.turn_on(self.hass, "light.test_template_light", **{ATTR_COLOR_TEMP: 345})
self.hass.block_till_done()
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")
_LOGGER.info(str(state.attributes))
@ -1008,18 +1008,18 @@ class TestTemplateLight:
)
self.hass.block_till_done()
assert len(self.calls) == 2
assert self.calls[0].data["h"] == "40"
assert self.calls[0].data["s"] == "50"
assert self.calls[1].data["h"] == "40"
assert self.calls[1].data["s"] == "50"
assert self.calls[0].data["h"] == 40
assert self.calls[0].data["s"] == 50
assert self.calls[1].data["h"] == 40
assert self.calls[1].data["s"] == 50
state = self.hass.states.get("light.test_template_light")
_LOGGER.info(str(state.attributes))
assert state is not None
assert self.calls[0].data["h"] == "40"
assert self.calls[0].data["s"] == "50"
assert self.calls[1].data["h"] == "40"
assert self.calls[1].data["s"] == "50"
assert self.calls[0].data["h"] == 40
assert self.calls[0].data["s"] == 50
assert self.calls[1].data["h"] == 40
assert self.calls[1].data["s"] == 50
@pytest.mark.parametrize(
"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")
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()
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.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):
@ -29,6 +29,7 @@ async def test_form(hass):
result["flow_id"],
{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["title"] == "testadmin.vilfo.com"
@ -142,7 +143,10 @@ async def test_form_unexpected_exception(hass):
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(
result["flow_id"],
{"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")
await hass.async_block_till_done()
assert len(specific_runs) == 1
assert specific_runs[0].strip() == "['light.one']"
assert specific_runs[0] == ["light.one"]
assert info.listeners == {
"all": False,
@ -969,7 +969,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "lock")
await hass.async_block_till_done()
assert len(specific_runs) == 2
assert specific_runs[1].strip() == "['lock.one']"
assert specific_runs[1] == ["lock.one"]
assert info.listeners == {
"all": False,
"domains": {"lock"},
@ -987,7 +987,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "light")
await hass.async_block_till_done()
assert len(specific_runs) == 4
assert specific_runs[3].strip() == "['light.one']"
assert specific_runs[3] == ["light.one"]
assert info.listeners == {
"all": False,
"domains": {"light"},
@ -1022,7 +1022,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "lock")
await hass.async_block_till_done()
assert len(specific_runs) == 7
assert specific_runs[6].strip() == "['lock.one']"
assert specific_runs[6] == ["lock.one"]
assert info.listeners == {
"all": False,
"domains": {"lock"},
@ -1032,7 +1032,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "single_binary_sensor")
await hass.async_block_till_done()
assert len(specific_runs) == 8
assert specific_runs[7].strip() == "unknown"
assert specific_runs[7] == "unknown"
assert info.listeners == {
"all": False,
"domains": set(),
@ -1042,7 +1042,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("binary_sensor.single", "binary_sensor_on")
await hass.async_block_till_done()
assert len(specific_runs) == 9
assert specific_runs[8].strip() == "binary_sensor_on"
assert specific_runs[8] == "binary_sensor_on"
assert info.listeners == {
"all": False,
"domains": set(),
@ -1052,7 +1052,7 @@ async def test_track_template_result_complex(hass):
hass.states.async_set("sensor.domain", "lock")
await hass.async_block_till_done()
assert len(specific_runs) == 10
assert specific_runs[9].strip() == "['lock.one']"
assert specific_runs[9] == ["lock.one"]
assert info.listeners == {
"all": False,
"domains": {"lock"},
@ -1144,13 +1144,13 @@ async def test_track_template_result_with_group(hass):
await hass.async_block_till_done()
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)
await hass.async_block_till_done()
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(
"homeassistant.config.load_yaml_config_file",
@ -1165,7 +1165,7 @@ async def test_track_template_result_with_group(hass):
info.async_refresh()
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):
@ -1421,38 +1421,38 @@ async def test_track_template_rate_limit(hass):
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs == ["0"]
assert refresh_runs == [0]
hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done()
assert refresh_runs == ["0"]
assert refresh_runs == [0]
info.async_refresh()
assert refresh_runs == ["0", "1"]
assert refresh_runs == [0, 1]
hass.states.async_set("sensor.two", "any")
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)
with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
):
async_fire_time_changed(hass, next_time)
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")
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")
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)
with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
):
async_fire_time_changed(hass, next_time)
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")
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):
@ -1474,18 +1474,18 @@ async def test_track_template_rate_limit_five(hass):
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs == ["0"]
assert refresh_runs == [0]
hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done()
assert refresh_runs == ["0"]
assert refresh_runs == [0]
info.async_refresh()
assert refresh_runs == ["0", "1"]
assert refresh_runs == [0, 1]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
assert refresh_runs == ["0", "1"]
assert refresh_runs == [0, 1]
hass.states.async_set("sensor.three", "any")
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):
@ -1508,18 +1508,18 @@ async def test_track_template_has_default_rate_limit(hass):
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs == ["1"]
assert refresh_runs == [1]
hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done()
assert refresh_runs == ["1"]
assert refresh_runs == [1]
info.async_refresh()
assert refresh_runs == ["1", "2"]
assert refresh_runs == [1, 2]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
assert refresh_runs == ["1", "2"]
assert refresh_runs == [1, 2]
hass.states.async_set("sensor.three", "any")
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):
@ -1545,21 +1545,21 @@ async def test_track_template_unavailable_sates_has_default_rate_limit(hass):
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs == ["1"]
assert refresh_runs == [1]
hass.states.async_set("sensor.one", "unknown")
await hass.async_block_till_done()
assert refresh_runs == ["1"]
assert refresh_runs == [1]
info.async_refresh()
assert refresh_runs == ["1", "2"]
assert refresh_runs == [1, 2]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
assert refresh_runs == ["1", "2"]
assert refresh_runs == [1, 2]
hass.states.async_set("sensor.three", "unknown")
await hass.async_block_till_done()
assert refresh_runs == ["1", "2"]
assert refresh_runs == [1, 2]
info.async_refresh()
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):
@ -1628,19 +1628,19 @@ async def test_track_two_templates_with_different_rate_limits(hass):
info.async_refresh()
await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0"]
assert refresh_runs[template_five] == ["0"]
assert refresh_runs[template_one] == [0]
assert refresh_runs[template_five] == [0]
hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0"]
assert refresh_runs[template_five] == ["0"]
assert refresh_runs[template_one] == [0]
assert refresh_runs[template_five] == [0]
info.async_refresh()
assert refresh_runs[template_one] == ["0", "1"]
assert refresh_runs[template_five] == ["0", "1"]
assert refresh_runs[template_one] == [0, 1]
assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1"]
assert refresh_runs[template_five] == ["0", "1"]
assert refresh_runs[template_one] == [0, 1]
assert refresh_runs[template_five] == [0, 1]
next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 1)
with patch(
"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)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"]
assert refresh_runs[template_five] == ["0", "1"]
assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"]
assert refresh_runs[template_five] == ["0", "1"]
assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.four", "any")
await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"]
assert refresh_runs[template_five] == ["0", "1"]
assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == [0, 1]
hass.states.async_set("sensor.five", "any")
await hass.async_block_till_done()
assert refresh_runs[template_one] == ["0", "1", "2"]
assert refresh_runs[template_five] == ["0", "1"]
assert refresh_runs[template_one] == [0, 1, 2]
assert refresh_runs[template_five] == [0, 1]
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")
await hass.async_block_till_done()
assert refresh_runs == ["False"]
assert refresh_runs == [False]
assert len(refresh_runs) == 1
@ -1770,9 +1770,9 @@ async def test_async_track_template_result_multiple_templates(hass):
assert refresh_runs == [
[
TrackTemplateResult(template_1, None, "True"),
TrackTemplateResult(template_2, None, "True"),
TrackTemplateResult(template_3, None, "False"),
TrackTemplateResult(template_1, None, True),
TrackTemplateResult(template_2, None, True),
TrackTemplateResult(template_3, None, False),
]
]
@ -1782,9 +1782,9 @@ async def test_async_track_template_result_multiple_templates(hass):
assert refresh_runs == [
[
TrackTemplateResult(template_1, "True", "False"),
TrackTemplateResult(template_2, "True", "False"),
TrackTemplateResult(template_3, "False", "True"),
TrackTemplateResult(template_1, True, False),
TrackTemplateResult(template_2, True, False),
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()
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 == [
[
TrackTemplateResult(template_1, None, "True"),
TrackTemplateResult(template_2, None, "True"),
TrackTemplateResult(template_3, None, "False"),
TrackTemplateResult(template_4, None, "['switch.test']"),
TrackTemplateResult(template_1, None, True),
TrackTemplateResult(template_2, None, True),
TrackTemplateResult(template_3, None, False),
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 == [
[
TrackTemplateResult(template_1, "True", "False"),
TrackTemplateResult(template_2, "True", "False"),
TrackTemplateResult(template_3, "False", "True"),
TrackTemplateResult(template_1, True, False),
TrackTemplateResult(template_2, True, False),
TrackTemplateResult(template_3, False, True),
]
]
@ -1859,7 +1859,7 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(hass
assert refresh_runs == [
[
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)
logger.debug("simulated service (%s:%s) started", fire, listen)
unsub = hass.bus.async_listen(listen, service_done_cb)
hass.bus.async_fire(fire)
unsub = hass.bus.async_listen(str(listen), service_done_cb)
hass.bus.async_fire(str(fire))
await service_done.wait()
unsub()
@ -834,14 +834,14 @@ async def test_wait_variables_out(hass, mode, action_type):
assert not script_obj.is_running
assert len(events) == 1
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":
assert "'to_state': <state switch.test=off" in events[0].data["trigger"]
else:
assert events[0].data["trigger"] == "None"
assert events[0].data["trigger"] is None
remaining = events[0].data["remaining"]
if mode == "no_timeout":
assert remaining == "None"
assert remaining is None
elif mode == "timeout_finish":
assert 0.0 < float(remaining) < 5
else:
@ -977,9 +977,9 @@ async def test_repeat_count(hass):
assert len(events) == count
for index, event in enumerate(events):
assert event.data.get("first") == str(index == 0)
assert event.data.get("index") == str(index + 1)
assert event.data.get("last") == str(index == count - 1)
assert event.data.get("first") == (index == 0)
assert event.data.get("index") == index + 1
assert event.data.get("last") == (index == count - 1)
@pytest.mark.parametrize("condition", ["while", "until"])
@ -1052,8 +1052,8 @@ async def test_repeat_conditional(hass, condition, direct_template):
assert len(events) == count
for index, event in enumerate(events):
assert event.data.get("first") == str(index == 0)
assert event.data.get("index") == str(index + 1)
assert event.data.get("first") == (index == 0)
assert event.data.get("index") == index + 1
@pytest.mark.parametrize("condition", ["while", "until"])
@ -1089,8 +1089,8 @@ async def test_repeat_var_in_condition(hass, condition):
@pytest.mark.parametrize(
"variables,first_last,inside_x",
[
(None, {"repeat": "None", "x": "None"}, "None"),
(MappingProxyType({"x": 1}), {"repeat": "None", "x": "1"}, "1"),
(None, {"repeat": None, "x": None}, None),
(MappingProxyType({"x": 1}), {"repeat": None, "x": 1}, 1),
],
)
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
for index, result in enumerate(
(
("True", "1", "False", inside_x),
("True", "1", "False", inside_x),
("False", "2", "True", inside_x),
("True", "1", "False", inside_x),
("False", "2", "True", 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),
(True, 1, False, inside_x),
(False, 2, True, inside_x),
(True, 1, False, inside_x),
(False, 2, True, inside_x),
(False, 2, True, inside_x),
),
1,
):
@ -1827,8 +1827,8 @@ async def test_set_redefines_variable(hass, caplog):
await script_obj.async_run(context=Context())
await hass.async_block_till_done()
assert mock_calls[0].data["value"] == "1"
assert mock_calls[1].data["value"] == "2"
assert mock_calls[0].data["value"] == 1
assert mock_calls[1].data["value"] == 2
async def test_validate_action_config(hass):

View File

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

View File

@ -7,6 +7,7 @@ import pytest
import pytz
from homeassistant.components import group
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT,
LENGTH_METERS,
@ -173,7 +174,7 @@ def test_iterating_all_states_unavailable(hass):
hass.states.async_set("sensor.temperature", 10)
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):
@ -205,14 +206,14 @@ def test_float(hass):
template.Template(
"{{ float(states.sensor.temperature.state) }}", hass
).async_render()
== "12.0"
== 12.0
)
assert (
template.Template(
"{{ float(states.sensor.temperature.state) > 11 }}", hass
).async_render()
== "True"
is True
)
assert (
@ -229,41 +230,41 @@ def test_rounding_value(hass):
template.Template(
"{{ states.sensor.temperature.state | round(1) }}", hass
).async_render()
== "12.8"
== 12.8
)
assert (
template.Template(
"{{ states.sensor.temperature.state | multiply(10) | round }}", hass
).async_render()
== "128"
== 128
)
assert (
template.Template(
'{{ states.sensor.temperature.state | round(1, "floor") }}', hass
).async_render()
== "12.7"
== 12.7
)
assert (
template.Template(
'{{ states.sensor.temperature.state | round(1, "ceil") }}', hass
).async_render()
== "12.8"
== 12.8
)
assert (
template.Template(
'{{ states.sensor.temperature.state | round(1, "half") }}', hass
).async_render()
== "13.0"
== 13.0
)
def test_rounding_value_get_original_value_on_error(hass):
"""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 (
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):
"""Test multiply."""
tests = {None: "None", 10: "100", '"abcd"': "abcd"}
tests = {None: None, 10: 100, '"abcd"': "abcd"}
for inp, out in tests.items():
assert (
@ -287,11 +288,11 @@ def test_multiply(hass):
def test_logarithm(hass):
"""Test logarithm."""
tests = [
(4, 2, "2.0"),
(1000, 10, "3.0"),
(math.e, "", "1.0"),
(4, 2, 2.0),
(1000, 10, 3.0),
(math.e, "", 1.0),
('"invalid"', "_", "invalid"),
(10, '"invalid"', "10.0"),
(10, '"invalid"', 10.0),
]
for value, base, expected in tests:
@ -313,11 +314,11 @@ def test_logarithm(hass):
def test_sine(hass):
"""Test sine."""
tests = [
(0, "0.0"),
(math.pi / 2, "1.0"),
(math.pi, "0.0"),
(math.pi * 1.5, "-1.0"),
(math.pi / 10, "0.309"),
(0, 0.0),
(math.pi / 2, 1.0),
(math.pi, 0.0),
(math.pi * 1.5, -1.0),
(math.pi / 10, 0.309),
('"duck"', "duck"),
]
@ -331,11 +332,11 @@ def test_sine(hass):
def test_cos(hass):
"""Test cosine."""
tests = [
(0, "1.0"),
(math.pi / 2, "0.0"),
(math.pi, "-1.0"),
(math.pi * 1.5, "-0.0"),
(math.pi / 10, "0.951"),
(0, 1.0),
(math.pi / 2, 0.0),
(math.pi, -1.0),
(math.pi * 1.5, -0.0),
(math.pi / 10, 0.951),
("'error'", "error"),
]
@ -349,11 +350,11 @@ def test_cos(hass):
def test_tan(hass):
"""Test tangent."""
tests = [
(0, "0.0"),
(math.pi, "-0.0"),
(math.pi / 180 * 45, "1.0"),
(math.pi / 180 * 90, "1.633123935319537e+16"),
(math.pi / 180 * 135, "-1.0"),
(0, 0.0),
(math.pi, -0.0),
(math.pi / 180 * 45, 1.0),
(math.pi / 180 * 90, 1.633123935319537e16),
(math.pi / 180 * 135, -1.0),
("'error'", "error"),
]
@ -367,11 +368,11 @@ def test_tan(hass):
def test_sqrt(hass):
"""Test square root."""
tests = [
(0, "0.0"),
(1, "1.0"),
(2, "1.414"),
(10, "3.162"),
(100, "10.0"),
(0, 0.0),
(1, 1.0),
(2, 1.414),
(10, 3.162),
(100, 10.0),
("'error'", "error"),
]
@ -385,13 +386,13 @@ def test_sqrt(hass):
def test_arc_sine(hass):
"""Test arcus sine."""
tests = [
(-2.0, "-2.0"), # value error
(-1.0, "-1.571"),
(-0.5, "-0.524"),
(0.0, "0.0"),
(0.5, "0.524"),
(1.0, "1.571"),
(2.0, "2.0"), # value error
(-2.0, -2.0), # value error
(-1.0, -1.571),
(-0.5, -0.524),
(0.0, 0.0),
(0.5, 0.524),
(1.0, 1.571),
(2.0, 2.0), # value error
('"error"', "error"),
]
@ -405,13 +406,13 @@ def test_arc_sine(hass):
def test_arc_cos(hass):
"""Test arcus cosine."""
tests = [
(-2.0, "-2.0"), # value error
(-1.0, "3.142"),
(-0.5, "2.094"),
(0.0, "1.571"),
(0.5, "1.047"),
(1.0, "0.0"),
(2.0, "2.0"), # value error
(-2.0, -2.0), # value error
(-1.0, 3.142),
(-0.5, 2.094),
(0.0, 1.571),
(0.5, 1.047),
(1.0, 0.0),
(2.0, 2.0), # value error
('"error"', "error"),
]
@ -425,15 +426,15 @@ def test_arc_cos(hass):
def test_arc_tan(hass):
"""Test arcus tangent."""
tests = [
(-10.0, "-1.471"),
(-2.0, "-1.107"),
(-1.0, "-0.785"),
(-0.5, "-0.464"),
(0.0, "0.0"),
(0.5, "0.464"),
(1.0, "0.785"),
(2.0, "1.107"),
(10.0, "1.471"),
(-10.0, -1.471),
(-2.0, -1.107),
(-1.0, -0.785),
(-0.5, -0.464),
(0.0, 0.0),
(0.5, 0.464),
(1.0, 0.785),
(2.0, 1.107),
(10.0, 1.471),
('"error"', "error"),
]
@ -447,19 +448,19 @@ def test_arc_tan(hass):
def test_arc_tan2(hass):
"""Test two parameter version of arcus tangent."""
tests = [
(-10.0, -10.0, "-2.356"),
(-10.0, 0.0, "-1.571"),
(-10.0, 10.0, "-0.785"),
(0.0, -10.0, "3.142"),
(0.0, 0.0, "0.0"),
(0.0, 10.0, "0.0"),
(10.0, -10.0, "2.356"),
(10.0, 0.0, "1.571"),
(10.0, 10.0, "0.785"),
(-4.0, 3.0, "-0.927"),
(-1.0, 2.0, "-0.464"),
(2.0, 1.0, "1.107"),
('"duck"', '"goose"', "('duck', 'goose')"),
(-10.0, -10.0, -2.356),
(-10.0, 0.0, -1.571),
(-10.0, 10.0, -0.785),
(0.0, -10.0, 3.142),
(0.0, 0.0, 0.0),
(0.0, 10.0, 0.0),
(10.0, -10.0, 2.356),
(10.0, 0.0, 1.571),
(10.0, 10.0, 0.785),
(-4.0, 3.0, -0.927),
(-1.0, 2.0, -0.464),
(2.0, 1.0, 1.107),
('"duck"', '"goose"', ("duck", "goose")),
]
for y, x, expected in tests:
@ -486,26 +487,26 @@ def test_strptime(hass):
("2016-10-19", "%Y-%m-%d", None),
("2016", "%Y", None),
("15:22:05", "%H:%M:%S", None),
("1469119144", "%Y", "1469119144"),
("1469119144", "%Y", 1469119144),
("invalid", "%Y", "invalid"),
]
for inp, fmt, expected in tests:
if expected is None:
expected = datetime.strptime(inp, fmt)
expected = str(datetime.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):
"""Test the timestamps to custom filter."""
now = dt_util.utcnow()
tests = [
(None, None, None, "None"),
(None, None, None, None),
(1469119144, None, True, "2016-07-21 16:39:04"),
(1469119144, "%Y", True, "2016"),
(1469119144, "%Y", True, 2016),
(1469119144, "invalid", True, "invalid"),
(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):
"""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():
assert (
@ -550,7 +551,7 @@ def test_to_json(hass):
# 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.
expected_result = '{"Foo": "Bar"}'
expected_result = {"Foo": "Bar"}
actual_result = template.Template(
"{{ {'Foo': 'Bar'} | to_json }}", hass
).async_render()
@ -571,17 +572,17 @@ def test_from_json(hass):
def test_min(hass):
"""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):
"""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):
"""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):
@ -626,7 +627,7 @@ def test_timestamp_utc(hass):
"""Test the timestamps to local filter."""
now = dt_util.utcnow()
tests = {
None: "None",
None: None,
1469119144: "2016-07-21 16:39:04",
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):
"""Test the as_timestamp function."""
assert (
template.Template('{{ as_timestamp("invalid") }}', hass).async_render()
== "None"
template.Template('{{ as_timestamp("invalid") }}', hass).async_render() is None
)
hass.mock = None
assert (
template.Template("{{ as_timestamp(states.mock) }}", hass).async_render()
== "None"
is None
)
tpl = (
'{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", '
'"%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")
@ -669,22 +669,19 @@ def test_random_every_time(test_choice, hass):
def test_passing_vars_as_keywords(hass):
"""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):
"""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):
"""Test passing variables as list."""
assert (
template.render_complex(
template.Template("{{ hello }}", hass), {"hello": ["foo", "bar"]}
)
== "['foo', 'bar']"
)
assert template.render_complex(
template.Template("{{ hello }}", hass), {"hello": ["foo", "bar"]}
) == ["foo", "bar"]
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):
"""Test passing variables as list."""
assert (
template.render_complex(
template.Template("{{ hello }}", hass), {"hello": {"foo": "bar"}}
)
== "{'foo': 'bar'}"
)
assert template.render_complex(
template.Template("{{ hello }}", hass), {"hello": {"foo": "bar"}}
) == {"foo": "bar"}
def test_render_with_possible_json_value_with_valid_json(hass):
@ -801,7 +795,7 @@ def test_is_state(hass):
""",
hass,
)
assert tpl.async_render() == "False"
assert tpl.async_render() is False
def test_is_state_attr(hass):
@ -821,7 +815,7 @@ def test_is_state_attr(hass):
""",
hass,
)
assert tpl.async_render() == "False"
assert tpl.async_render() is False
def test_state_attr(hass):
@ -841,7 +835,7 @@ def test_state_attr(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
def test_states_function(hass):
@ -994,7 +988,7 @@ def test_regex_match(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
tpl = template.Template(
"""
@ -1002,7 +996,7 @@ def test_regex_match(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
tpl = template.Template(
"""
@ -1010,7 +1004,7 @@ def test_regex_match(hass):
""",
hass,
)
assert tpl.async_render() == "False"
assert tpl.async_render() is False
tpl = template.Template(
"""
@ -1018,7 +1012,7 @@ def test_regex_match(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
def test_regex_search(hass):
@ -1029,7 +1023,7 @@ def test_regex_search(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
tpl = template.Template(
"""
@ -1037,7 +1031,7 @@ def test_regex_search(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
tpl = template.Template(
"""
@ -1045,7 +1039,7 @@ def test_regex_search(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
tpl = template.Template(
"""
@ -1053,7 +1047,7 @@ def test_regex_search(hass):
""",
hass,
)
assert tpl.async_render() == "True"
assert tpl.async_render() is True
def test_regex_replace(hass):
@ -1072,7 +1066,7 @@ def test_regex_replace(hass):
""",
hass,
)
assert tpl.async_render() == "['Home Assistant test']"
assert tpl.async_render() == ["Home Assistant test"]
def test_regex_findall_index(hass):
@ -1110,21 +1104,21 @@ def test_bitwise_and(hass):
""",
hass,
)
assert tpl.async_render() == str(8 & 8)
assert tpl.async_render() == 8 & 8
tpl = template.Template(
"""
{{ 10 | bitwise_and(2) }}
""",
hass,
)
assert tpl.async_render() == str(10 & 2)
assert tpl.async_render() == 10 & 2
tpl = template.Template(
"""
{{ 8 | bitwise_and(2) }}
""",
hass,
)
assert tpl.async_render() == str(8 & 2)
assert tpl.async_render() == 8 & 2
def test_bitwise_or(hass):
@ -1135,21 +1129,21 @@ def test_bitwise_or(hass):
""",
hass,
)
assert tpl.async_render() == str(8 | 8)
assert tpl.async_render() == 8 | 8
tpl = template.Template(
"""
{{ 10 | bitwise_or(2) }}
""",
hass,
)
assert tpl.async_render() == str(10 | 2)
assert tpl.async_render() == 10 | 2
tpl = template.Template(
"""
{{ 8 | bitwise_or(2) }}
""",
hass,
)
assert tpl.async_render() == str(8 | 2)
assert tpl.async_render() == 8 | 2
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}
)
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):
@ -1176,14 +1170,14 @@ def test_distance_function_with_2_states(hass):
tpl = template.Template(
"{{ 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):
"""Test distance function with 1 coord."""
_set_up_units(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):
@ -1195,7 +1189,7 @@ def test_distance_function_with_2_coords(hass):
% (hass.config.latitude, hass.config.longitude),
hass,
).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 }}",
hass,
)
assert tpl.async_render() == "187"
assert tpl.async_render() == 187
tpl2 = template.Template(
'{{ distance(states.test.object_2, "32.87336", "-117.22943") ' "| round }}",
hass,
)
assert tpl2.async_render() == "187"
assert tpl2.async_render() == 187
def test_distance_function_return_none_if_invalid_state(hass):
"""Test distance function return None if invalid state."""
hass.states.async_set("test.object_2", "happy", {"latitude": 10})
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):
"""Test distance function return None if invalid coord."""
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(
"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},
)
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):
@ -1258,7 +1252,7 @@ def test_distance_function_with_2_entity_ids(hass):
tpl = template.Template(
'{{ 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):
@ -1272,7 +1266,7 @@ def test_distance_function_with_1_entity_1_coord(hass):
tpl = template.Template(
'{{ 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):
@ -1400,11 +1394,11 @@ async def test_closest_function_home_vs_group_state(hass):
async def test_expand(hass):
"""Test expand function."""
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
info = render_to_info(hass, "{{ expand(56) }}")
assert_result_info(info, "[]")
assert_result_info(info, [])
assert info.rate_limit is None
hass.states.async_set("test.object", "happy")
@ -1476,7 +1470,7 @@ async def test_expand(hass):
)
assert_result_info(
info,
str(200.2 + 400.4),
200.2 + 400.4,
{"group.power_sensors", "sensor.power_1", "sensor.power_2", "sensor.power_3"},
)
assert info.rate_limit is None
@ -1593,7 +1587,7 @@ def test_async_render_to_info_with_complex_branching(hass):
{"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
@ -1820,7 +1814,7 @@ def test_closest_function_invalid_state(hass):
for state in ("states.zone.non_existing", '"zone.non_existing"'):
assert (
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(
"{{ closest(states.test_domain.closest_home, states) }}", hass
).async_render()
== "None"
is None
)
@ -1855,13 +1849,13 @@ def test_closest_function_invalid_coordinates(hass):
template.Template(
'{{ closest("invalid", "coord", states) }}', hass
).async_render()
== "None"
is None
)
assert (
template.Template(
'{{ states | closest("invalid", "coord") }}', hass
).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)
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.cow", "True")
@ -2020,7 +2014,7 @@ async def test_async_render_to_info_in_conditional(hass):
tmp = template.Template(template_str, hass)
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.pig", "oink")
@ -2327,17 +2321,17 @@ def test_length_of_states(hass):
hass.states.async_set("climate.test2", "cooling")
tpl = template.Template("{{ states | length }}", hass)
assert tpl.async_render() == "3"
assert tpl.async_render() == 3
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):
"""Test that we can render non-template fields."""
assert template.render_complex(
{True: 1, False: template.Template("{{ hello }}", hass)}, {"hello": 2}
) == {True: 1, False: "2"}
) == {True: 1, False: 2}
def test_urlencode(hass):
@ -2571,7 +2565,7 @@ async def test_state_attributes(hass):
"{{ states.sensor.test.state_with_unit }}",
hass,
)
assert tpl.async_render() == "23"
assert tpl.async_render() == 23
tpl = template.Template(
"{{ states.sensor.test.invalid_prop }}",
@ -2608,3 +2602,20 @@ async def test_unavailable_states(hass):
hass,
)
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",
"internal_url": "http://example.local",
"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 hass.config.media_dirs == {"mymedia": "/usr"}
assert hass.config.config_source == config_util.SOURCE_YAML
assert hass.config.legacy_templates is True
async def test_loading_configuration_temperature_unit(hass):