diff --git a/homeassistant/loader.py b/homeassistant/loader.py index da8159ca2cf..1a72c8eb351 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -976,6 +976,8 @@ class Integration: comp = await self.hass.async_add_import_executor_job( self._get_component, True ) + except ModuleNotFoundError: + raise except ImportError as ex: load_executor = False _LOGGER.debug( @@ -1115,6 +1117,8 @@ class Integration: self._load_platforms, platform_names ) ) + except ModuleNotFoundError: + raise except ImportError as ex: _LOGGER.debug( "Failed to import %s platforms %s in executor", diff --git a/tests/test_loader.py b/tests/test_loader.py index 41796f2f7d2..404858200bc 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1471,6 +1471,50 @@ async def test_async_get_component_deadlock_fallback( assert module is module_mock +async def test_async_get_component_deadlock_fallback_module_not_found( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Verify async_get_component fallback behavior. + + Ensure that fallback is not triggered on ModuleNotFoundError. + """ + executor_import_integration = _get_test_integration( + hass, "executor_import", True, import_executor=True + ) + assert executor_import_integration.import_executor is True + module_mock = MagicMock(__file__="__init__.py") + import_attempts = 0 + + def mock_import(module: str, *args: Any, **kwargs: Any) -> Any: + nonlocal import_attempts + if module == "homeassistant.components.executor_import": + import_attempts += 1 + + if import_attempts == 1: + raise ModuleNotFoundError( + "homeassistant.components.executor_import not found", + name="homeassistant.components.executor_import", + ) + + return module_mock + + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + with ( + patch("homeassistant.loader.importlib.import_module", mock_import), + pytest.raises( + ModuleNotFoundError, match="homeassistant.components.executor_import" + ), + ): + await executor_import_integration.async_get_component() + + # We should not have tried to fall back to the event loop import + assert "loaded_executor=False" not in caplog.text + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + assert import_attempts == 1 + + async def test_async_get_component_raises_after_import_failure( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: @@ -1551,6 +1595,52 @@ async def test_async_get_platform_deadlock_fallback( assert module is module_mock +async def test_async_get_platform_deadlock_fallback_module_not_found( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Verify async_get_platform fallback behavior. + + Ensure that fallback is not triggered on ModuleNotFoundError. + """ + executor_import_integration = _get_test_integration( + hass, "executor_import", True, import_executor=True + ) + assert executor_import_integration.import_executor is True + module_mock = MagicMock() + import_attempts = 0 + + def mock_import(module: str, *args: Any, **kwargs: Any) -> Any: + nonlocal import_attempts + if module == "homeassistant.components.executor_import.config_flow": + import_attempts += 1 + + if import_attempts == 1: + raise ModuleNotFoundError( + "Not found homeassistant.components.executor_import.config_flow", + name="homeassistant.components.executor_import.config_flow", + ) + + return module_mock + + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + with ( + patch("homeassistant.loader.importlib.import_module", mock_import), + pytest.raises( + ModuleNotFoundError, + match="homeassistant.components.executor_import.config_flow", + ), + ): + await executor_import_integration.async_get_platform("config_flow") + + # We should not have tried to fall back to the event loop import + assert "executor=['config_flow']" in caplog.text + assert "loop=['config_flow']" not in caplog.text + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + assert import_attempts == 1 + + async def test_async_get_platform_raises_after_import_failure( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: