From 284893d9423d0f85030c06c54b9465374430c70d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 14 Oct 2022 16:43:09 +0200 Subject: [PATCH] Fix reload of automation and scripts when blueprint changed (#80322) --- .../components/automation/__init__.py | 2 +- homeassistant/components/blueprint/models.py | 6 +- homeassistant/components/script/__init__.py | 2 +- tests/components/automation/test_init.py | 55 +++++++++++++++++++ tests/components/blueprint/test_models.py | 2 +- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 9b6f9de1945..9ad1f79354e 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -276,9 +276,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all automations and load new ones from config.""" + await async_get_blueprints(hass).async_reset_cache() if (conf := await component.async_prepare_reload(skip_reset=True)) is None: return - async_get_blueprints(hass).async_reset_cache() await _async_process_config(hass, conf, component) hass.bus.async_fire(EVENT_AUTOMATION_RELOADED, context=service_call.context) diff --git a/homeassistant/components/blueprint/models.py b/homeassistant/components/blueprint/models.py index f77a2bed9a4..bc0938b1097 100644 --- a/homeassistant/components/blueprint/models.py +++ b/homeassistant/components/blueprint/models.py @@ -202,10 +202,10 @@ class DomainBlueprints: """Return the blueprint folder.""" return pathlib.Path(self.hass.config.path(BLUEPRINT_FOLDER, self.domain)) - @callback - def async_reset_cache(self) -> None: + async def async_reset_cache(self) -> None: """Reset the blueprint cache.""" - self._blueprints = {} + async with self._load_lock: + self._blueprints = {} def _load_blueprint(self, blueprint_path) -> Blueprint: """Load a blueprint.""" diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 0effdc2754b..27bc2556dc5 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -176,9 +176,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service(service: ServiceCall) -> None: """Call a service to reload scripts.""" + await async_get_blueprints(hass).async_reset_cache() if (conf := await component.async_prepare_reload()) is None: return - async_get_blueprints(hass).async_reset_cache() await _async_process_config(hass, conf, component) async def turn_on_service(service: ServiceCall) -> None: diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index f129d829d4c..858f3de6549 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -46,6 +46,7 @@ from homeassistant.helpers.script import ( _async_stop_scripts_at_shutdown, ) from homeassistant.setup import async_setup_component +from homeassistant.util import yaml import homeassistant.util.dt as dt_util from tests.common import ( @@ -1002,6 +1003,60 @@ async def test_reload_unchanged_automation(hass, calls, automation_config): assert len(calls) == 2 +async def test_reload_automation_when_blueprint_changes(hass, calls): + """Test an automation is updated at reload if the blueprint has changed.""" + with patch( + "homeassistant.components.automation.AutomationEntity", wraps=AutomationEntity + ) as automation_entity_init: + config = { + automation.DOMAIN: [ + { + "use_blueprint": { + "path": "test_event_service.yaml", + "input": { + "trigger_event": "test_event", + "service_to_call": "test.automation", + "a_number": 5, + }, + } + } + ] + } + assert await async_setup_component(hass, automation.DOMAIN, config) + assert automation_entity_init.call_count == 1 + automation_entity_init.reset_mock() + + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 1 + + # Reload the automations without any change, but with updated blueprint + blueprint_path = automation.async_get_blueprints(hass).blueprint_folder + blueprint_config = yaml.load_yaml(blueprint_path / "test_event_service.yaml") + blueprint_config["action"] = [blueprint_config["action"]] + blueprint_config["action"].append(blueprint_config["action"][-1]) + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value=config, + ), patch( + "homeassistant.components.blueprint.models.yaml.load_yaml", + autospec=True, + return_value=blueprint_config, + ): + await hass.services.async_call( + automation.DOMAIN, SERVICE_RELOAD, blocking=True + ) + + assert automation_entity_init.call_count == 1 + automation_entity_init.reset_mock() + + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 3 + + async def test_automation_restore_state(hass): """Ensure states are restored on startup.""" time = dt_util.utcnow() diff --git a/tests/components/blueprint/test_models.py b/tests/components/blueprint/test_models.py index 02ed94709db..589025a08ba 100644 --- a/tests/components/blueprint/test_models.py +++ b/tests/components/blueprint/test_models.py @@ -224,7 +224,7 @@ async def test_domain_blueprints_caching(domain_bps): assert await domain_bps.async_get_blueprint("something") is obj obj_2 = object() - domain_bps.async_reset_cache() + await domain_bps.async_reset_cache() # Now we call this method again. with patch.object(domain_bps, "_load_blueprint", return_value=obj_2):