From 9e219d9b6ef1087f052ac40a476d75012841a87e Mon Sep 17 00:00:00 2001 From: "Richard T. Schaefer" Date: Wed, 28 Jul 2021 02:09:13 -0500 Subject: [PATCH] Add this variable for use by automation and script templates (#52774) --- .../components/automation/__init__.py | 21 ++++++--- homeassistant/components/script/__init__.py | 7 ++- tests/components/automation/test_init.py | 45 ++++++++++++++++++- tests/components/script/test_init.py | 38 ++++++++++++++++ 4 files changed, 104 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index b94029db4ee..a0ff4930b51 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -445,15 +445,19 @@ class AutomationEntity(ToggleEntity, RestoreEntity): trigger_context, self._trace_config, ) as automation_trace: + this = None + state = self.hass.states.get(self.entity_id) + if state: + this = state.as_dict() + variables = {"this": this, **(run_variables or {})} if self._variables: try: - variables = self._variables.async_render(self.hass, run_variables) + variables = self._variables.async_render(self.hass, variables) except template.TemplateError as err: self._logger.error("Error rendering variables: %s", err) automation_trace.set_error(err) return - else: - variables = run_variables + # Prepare tracing the automation automation_trace.set_trace(trace_get()) @@ -569,11 +573,18 @@ class AutomationEntity(ToggleEntity, RestoreEntity): def log_cb(level, msg, **kwargs): self._logger.log(level, "%s %s", msg, self.name, **kwargs) - variables = None + this = None + self.async_write_ha_state() + state = self.hass.states.get(self.entity_id) + if state: + this = state.as_dict() + variables = {"this": this} if self._trigger_variables: try: variables = self._trigger_variables.async_render( - self.hass, None, limited=True + self.hass, + variables, + limited=True, ) except template.TemplateError as err: self._logger.error("Error rendering trigger variables: %s", err) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 41d5e697cf1..483b4065be2 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -401,7 +401,12 @@ class ScriptEntity(ToggleEntity): # Prepare tracing the execution of the script's sequence script_trace.set_trace(trace_get()) with trace_path("sequence"): - return await self.script.async_run(variables, context) + this = None + state = self.hass.states.get(self.entity_id) + if state: + this = state.as_dict() + script_vars = {"this": this, **(variables or {})} + return await self.script.async_run(script_vars, context) async def async_turn_off(self, **kwargs): """Stop running the script. diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 80fe5c52abc..214b2ea20e8 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1184,6 +1184,7 @@ async def test_automation_variables(hass, caplog): "variables": { "test_var": "defined_in_config", "event_type": "{{ trigger.event.event_type }}", + "this_variables": "{{this.entity_id}}", }, "trigger": {"platform": "event", "event_type": "test_event"}, "action": { @@ -1191,6 +1192,8 @@ async def test_automation_variables(hass, caplog): "data": { "value": "{{ test_var }}", "event_type": "{{ event_type }}", + "this_template": "{{this.entity_id}}", + "this_variables": "{{this_variables}}", }, }, }, @@ -1224,6 +1227,11 @@ async def test_automation_variables(hass, caplog): assert len(calls) == 1 assert calls[0].data["value"] == "defined_in_config" assert calls[0].data["event_type"] == "test_event" + # Verify this available to all templates + assert calls[0].data.get("this_template") == "automation.automation_0" + # Verify this available during variables rendering + assert calls[0].data.get("this_variables") == "automation.automation_0" + assert "Error rendering variables" not in caplog.text hass.bus.async_fire("test_event_2") await hass.async_block_till_done() @@ -1276,6 +1284,7 @@ async def test_automation_trigger_variables(hass, caplog): }, "trigger_variables": { "test_var": "defined_in_config", + "this_trigger_variables": "{{this.entity_id}}", }, "trigger": {"platform": "event", "event_type": "test_event_2"}, "action": { @@ -1283,6 +1292,8 @@ async def test_automation_trigger_variables(hass, caplog): "data": { "value": "{{ test_var }}", "event_type": "{{ event_type }}", + "this_template": "{{this.entity_id}}", + "this_trigger_variables": "{{this_trigger_variables}}", }, }, }, @@ -1300,7 +1311,10 @@ async def test_automation_trigger_variables(hass, caplog): assert len(calls) == 2 assert calls[1].data["value"] == "overridden_in_config" assert calls[1].data["event_type"] == "test_event_2" - + # Verify this available to all templates + assert calls[1].data.get("this_template") == "automation.automation_1" + # Verify this available during trigger variables rendering + assert calls[1].data.get("this_trigger_variables") == "automation.automation_1" assert "Error rendering variables" not in caplog.text @@ -1332,6 +1346,35 @@ async def test_automation_bad_trigger_variables(hass, caplog): assert len(calls) == 0 +async def test_automation_this_var_always(hass, caplog): + """Test automation always has reference to this, even with no variable or trigger variables configured.""" + calls = async_mock_service(hass, "test", "automation") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": { + "service": "test.automation", + "data": { + "this_template": "{{this.entity_id}}", + }, + }, + }, + ] + }, + ) + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 1 + # Verify this available to all templates + assert calls[0].data.get("this_template") == "automation.automation_0" + assert "Error rendering variables" not in caplog.text + + async def test_blueprint_automation(hass, calls): """Test blueprint automation.""" assert await async_setup_component( diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 8967a20a01b..6070daeb8af 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -679,6 +679,7 @@ async def test_script_variables(hass, caplog): "script": { "script1": { "variables": { + "this_variable": "{{this.entity_id}}", "test_var": "from_config", "templated_config_var": "{{ var_from_service | default('config-default') }}", }, @@ -688,6 +689,8 @@ async def test_script_variables(hass, caplog): "data": { "value": "{{ test_var }}", "templated_config_var": "{{ templated_config_var }}", + "this_template": "{{this.entity_id}}", + "this_variable": "{{this_variable}}", }, }, ], @@ -731,6 +734,10 @@ async def test_script_variables(hass, caplog): assert len(mock_calls) == 1 assert mock_calls[0].data["value"] == "from_config" assert mock_calls[0].data["templated_config_var"] == "hello" + # Verify this available to all templates + assert mock_calls[0].data.get("this_template") == "script.script1" + # Verify this available during trigger variables rendering + assert mock_calls[0].data.get("this_variable") == "script.script1" await hass.services.async_call( "script", "script1", {"test_var": "from_service"}, blocking=True @@ -758,3 +765,34 @@ async def test_script_variables(hass, caplog): assert len(mock_calls) == 4 assert mock_calls[3].data["value"] == 1 + + +async def test_script_this_var_always(hass, caplog): + """Test script always has reference to this, even with no variabls are configured.""" + + assert await async_setup_component( + hass, + "script", + { + "script": { + "script1": { + "sequence": [ + { + "service": "test.script", + "data": { + "this_template": "{{this.entity_id}}", + }, + }, + ], + }, + }, + }, + ) + mock_calls = async_mock_service(hass, "test", "script") + + await hass.services.async_call("script", "script1", blocking=True) + + assert len(mock_calls) == 1 + # Verify this available to all templates + assert mock_calls[0].data.get("this_template") == "script.script1" + assert "Error rendering variables" not in caplog.text