diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index f2ef404ab34..4bf443bfeb9 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -333,8 +333,7 @@ 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 + conf = await component.async_prepare_reload(skip_reset=True) if automation_id := service_call.data.get(CONF_ID): await _async_process_single_config(hass, conf, component, automation_id) else: diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index f89bf67861d..e0713eca373 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -194,8 +194,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - Remove group.group entities not created by service calls and set them up again - Reload xxx.group platforms """ - if (conf := await component.async_prepare_reload(skip_reset=True)) is None: - return + conf = await component.async_prepare_reload(skip_reset=True) # Simplified + modified version of EntityPlatform.async_reset: # - group.group never retries setup diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index 57165c5508a..f39abe3f141 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -121,8 +121,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all input booleans and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - return await yaml_collection.async_load( [ {CONF_ID: id_, **(conf or {})} diff --git a/homeassistant/components/input_button/__init__.py b/homeassistant/components/input_button/__init__.py index 1488d80a1f5..c9c1e34a50a 100644 --- a/homeassistant/components/input_button/__init__.py +++ b/homeassistant/components/input_button/__init__.py @@ -106,8 +106,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all input buttons and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - return await yaml_collection.async_load( [ {CONF_ID: id_, **(conf or {})} diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 5d2c1e7ff8d..fe51d52e0db 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -159,8 +159,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - conf = {DOMAIN: {}} await yaml_collection.async_load( [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] ) diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index f55ceabc6f0..7de2e29654d 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -137,8 +137,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - conf = {DOMAIN: {}} await yaml_collection.async_load( [{CONF_ID: id_, **conf} for id_, conf in conf.get(DOMAIN, {}).items()] ) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 44d2df02a92..507f777adb9 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -167,8 +167,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - conf = {DOMAIN: {}} await yaml_collection.async_load( [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] ) diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 7d8f6663673..d3cd7ba6527 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -137,8 +137,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - conf = {DOMAIN: {}} await yaml_collection.async_load( [{CONF_ID: id_, **(cfg or {})} for id_, cfg in conf.get(DOMAIN, {}).items()] ) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index b793f4b33ae..fd295db65c1 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -403,8 +403,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_reload_yaml(call: ServiceCall) -> None: """Reload YAML.""" conf = await entity_component.async_prepare_reload(skip_reset=True) - if conf is None: - return await yaml_collection.async_load( await filter_yaml_data(hass, conf.get(DOMAIN, [])) ) diff --git a/homeassistant/components/schedule/__init__.py b/homeassistant/components/schedule/__init__.py index 08d0b083f7c..2325141f21d 100644 --- a/homeassistant/components/schedule/__init__.py +++ b/homeassistant/components/schedule/__init__.py @@ -187,8 +187,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - conf = {DOMAIN: {}} await yaml_collection.async_load( [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] ) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 84cb2ecbeae..1a80bd7ae52 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -229,8 +229,7 @@ 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(skip_reset=True)) is None: - return + conf = await component.async_prepare_reload(skip_reset=True) await _async_process_config(hass, conf, component) async def turn_on_service(service: ServiceCall) -> None: diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index 3f2b4bd7f43..8f0f2625a48 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -141,8 +141,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - conf = {DOMAIN: {}} await yaml_collection.async_load( [{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()] ) diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 1c43a79e10e..0863f1095ba 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -265,8 +265,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all zones and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) - if conf is None: - return await yaml_collection.async_load(conf[DOMAIN]) service.async_register_admin_service( diff --git a/homeassistant/config.py b/homeassistant/config.py index a61fcbdbb0c..5699a2ea2ab 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -16,7 +16,7 @@ from pathlib import Path import re import shutil from types import ModuleType -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal, overload from urllib.parse import urlparse from awesomeversion import AwesomeVersion @@ -1208,6 +1208,39 @@ def _get_log_message_and_stack_print_pref( return (log_message, show_stack_trace, placeholders) +# The complicated overloads are due to a limitation in mypy, details in +# https://github.com/python/mypy/issues/7333 +@overload +@callback +async def async_process_component_and_handle_errors( + hass: HomeAssistant, + config: ConfigType, + integration: Integration, +) -> ConfigType | None: ... + + +@overload +@callback +async def async_process_component_and_handle_errors( + hass: HomeAssistant, + config: ConfigType, + integration: Integration, + *, + raise_on_failure: Literal[True], +) -> ConfigType: ... + + +@overload +@callback +async def async_process_component_and_handle_errors( + hass: HomeAssistant, + config: ConfigType, + integration: Integration, + *, + raise_on_failure: bool, +) -> ConfigType | None: ... + + async def async_process_component_and_handle_errors( hass: HomeAssistant, config: ConfigType, @@ -1226,7 +1259,7 @@ async def async_process_component_and_handle_errors( hass, config, integration ) async_handle_component_errors( - hass, integration_config_info, integration, raise_on_failure + hass, integration_config_info, integration, raise_on_failure=raise_on_failure ) return async_drop_config_annotations(integration_config_info, integration) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 0034eb1c6fc..1a03fe88aaf 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -348,28 +348,19 @@ class EntityComponent(Generic[_EntityT]): if found: await found.async_remove_entity(entity_id) - async def async_prepare_reload( - self, *, skip_reset: bool = False - ) -> ConfigType | None: + async def async_prepare_reload(self, *, skip_reset: bool = False) -> ConfigType: """Prepare reloading this entity component. This method must be run in the event loop. """ - try: - conf = await conf_util.async_hass_config_yaml(self.hass) - except HomeAssistantError as err: - self.logger.error(err) - return None + conf = await conf_util.async_hass_config_yaml(self.hass) integration = await async_get_integration(self.hass, self.domain) processed_conf = await conf_util.async_process_component_and_handle_errors( - self.hass, conf, integration + self.hass, conf, integration, raise_on_failure=True ) - if processed_conf is None: - return None - if not skip_reset: await self._async_reset() diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index cdd53731d6e..0e33fedb28e 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -136,6 +136,8 @@ async def _async_reconfig_platform( await asyncio.gather(*tasks) +# The complicated overloads are due to a limitation in mypy, details in +# https://github.com/python/mypy/issues/7333 @overload async def async_integration_yaml_config( hass: HomeAssistant, integration_name: str diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 8bac0c15db9..3aee6ae1f32 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -704,9 +704,12 @@ async def test_reload_config_handles_load_fails( assert len(calls) == 1 assert calls[0].data.get("event") == "test_event" - with patch( - "homeassistant.config.load_yaml_config_file", - side_effect=HomeAssistantError("bla"), + with ( + patch( + "homeassistant.config.load_yaml_config_file", + side_effect=HomeAssistantError("bla"), + ), + pytest.raises(HomeAssistantError), ): await hass.services.async_call(automation.DOMAIN, SERVICE_RELOAD, blocking=True)