diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 34d899e996c..779df785164 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -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 diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 40c0a76bfba..68d1f84a215 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -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( diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index fe8b6568c1e..7c35663bb5f 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -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 diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 4f75faa36ff..f0b22fb46b3 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -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]: diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 2b79846986c..5d6c1befd4a 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -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 diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index b917430a6ff..fa28436180e 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -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.""" diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index fc7b2408f21..d255d5b610c 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -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.""" diff --git a/homeassistant/config.py b/homeassistant/config.py index 3e9dd27458d..d0df72a810a 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -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]) diff --git a/homeassistant/const.py b/homeassistant/const.py index d0f17b5de3d..4f46dfe586a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -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" diff --git a/homeassistant/core.py b/homeassistant/core.py index 9f598d46410..50dae06acf1 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -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. diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index c982b58d8d9..57271ccaf81 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -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( diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index b6d59bb500c..c73196db604 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -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: diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 9c849bee22e..0f3d605523c 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -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: diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index 07b6d968c84..490b0e756a8 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -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.""" diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 7eb890903fd..b6c162eb87e 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -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 ) diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index d1529e63fc7..dd879338a41 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -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): diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index a4d1261daf8..b94dd123d8d 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -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): diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 5481b8b2565..23c9a15a7dc 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -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, ) diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 152c74d8fe9..068b0b91a2c 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -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 diff --git a/tests/components/snips/test_init.py b/tests/components/snips/test_init.py index dc69c10a7fb..82811b61925 100644 --- a/tests/components/snips/test_init.py +++ b/tests/components/snips/test_init.py @@ -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" diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 10af5fd0b74..20ab78f958d 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -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 diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 5353aaa7d17..1b99e1cec0a 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -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", diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 26cb35a0388..681794e910f 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -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 diff --git a/tests/components/vilfo/test_config_flow.py b/tests/components/vilfo/test_config_flow.py index 167fc71a78a..f14ec123ba5 100644 --- a/tests/components/vilfo/test_config_flow.py +++ b/tests/components/vilfo/test_config_flow.py @@ -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"}, diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index bb0d17d7b0e..5c3a6c1a71d 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -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"] ) ] ] diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 93bb249c485..a7cf6b17e7a 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -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': 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" + ) diff --git a/tests/test_config.py b/tests/test_config.py index 181e80da30c..368b11c1503 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -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):