From 24546dfdf9bba33b42584e97e91bb78fa01c94a1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 25 Jan 2022 20:39:32 -0800 Subject: [PATCH] Catch all exceptions on import component/platform (#64930) --- homeassistant/loader.py | 38 ++++++++++++++++++++++++++++++++------ homeassistant/setup.py | 3 --- tests/test_loader.py | 15 +++++++++++++++ tests/test_setup.py | 2 +- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index da23d1141bd..04ddd8df571 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -539,18 +539,44 @@ class Integration: def get_component(self) -> ModuleType: """Return the component.""" - cache = self.hass.data.setdefault(DATA_COMPONENTS, {}) - if self.domain not in cache: + cache: dict[str, ModuleType] = self.hass.data.setdefault(DATA_COMPONENTS, {}) + if self.domain in cache: + return cache[self.domain] + + try: cache[self.domain] = importlib.import_module(self.pkg_path) - return cache[self.domain] # type: ignore + except ImportError: + raise + except Exception as err: + _LOGGER.exception( + "Unexpected exception importing component %s", self.pkg_path + ) + raise ImportError(f"Exception importing {self.pkg_path}") from err + + return cache[self.domain] def get_platform(self, platform_name: str) -> ModuleType: """Return a platform for an integration.""" - cache = self.hass.data.setdefault(DATA_COMPONENTS, {}) + cache: dict[str, ModuleType] = self.hass.data.setdefault(DATA_COMPONENTS, {}) full_name = f"{self.domain}.{platform_name}" - if full_name not in cache: + if full_name in cache: + return cache[full_name] + + try: cache[full_name] = self._import_platform(platform_name) - return cache[full_name] # type: ignore + except ImportError: + raise + except Exception as err: + _LOGGER.exception( + "Unexpected exception importing platform %s.%s", + self.pkg_path, + platform_name, + ) + raise ImportError( + f"Exception importing {self.pkg_path}.{platform_name}" + ) from err + + return cache[full_name] def _import_platform(self, platform_name: str) -> ModuleType: """Import the platform.""" diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 1c3702a4725..5ff6519f6ec 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -181,9 +181,6 @@ async def _async_setup_component( except ImportError as err: log_error(f"Unable to import component: {err}", integration.documentation) return False - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Setup failed for %s: unknown error", domain) - return False processed_config = await conf_util.async_process_component_config( hass, config, integration diff --git a/tests/test_loader.py b/tests/test_loader.py index 9c6dcba770a..68946a9de01 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -157,6 +157,21 @@ async def test_get_integration(hass): assert hue_light == integration.get_platform("light") +async def test_get_integration_exceptions(hass): + """Test resolving integration.""" + integration = await loader.async_get_integration(hass, "hue") + + with pytest.raises(ImportError), patch( + "homeassistant.loader.importlib.import_module", side_effect=ValueError("Boom") + ): + assert hue == integration.get_component() + + with pytest.raises(ImportError), patch( + "homeassistant.loader.importlib.import_module", side_effect=ValueError("Boom") + ): + assert hue_light == integration.get_platform("light") + + async def test_get_integration_legacy(hass, enable_custom_integrations): """Test resolving integration.""" integration = await loader.async_get_integration(hass, "test_embedded") diff --git a/tests/test_setup.py b/tests/test_setup.py index e5b58057601..f71ba01410b 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -577,7 +577,7 @@ async def test_async_when_setup_or_start_already_loaded(hass): async def test_setup_import_blows_up(hass): """Test that we handle it correctly when importing integration blows up.""" with patch( - "homeassistant.loader.Integration.get_component", side_effect=ValueError + "homeassistant.loader.Integration.get_component", side_effect=ImportError ): assert not await setup.async_setup_component(hass, "sun", {})