diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 140153c33c2..0d03c4f81eb 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -847,8 +847,9 @@ class Integration: domain = self.domain # Some integrations fail on import because they call functions incorrectly. # So we do it before validating config to catch these errors. - load_executor = ( - self.import_executor and f"{self.pkg_path}.{domain}" not in sys.modules + load_executor = self.import_executor and ( + self.pkg_path not in sys.modules + or (self.config_flow and f"{self.pkg_path}.config_flow" not in sys.modules) ) if load_executor: try: diff --git a/tests/test_loader.py b/tests/test_loader.py index d627d950f30..a70bf7d4e3f 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1073,6 +1073,66 @@ async def test_async_get_component_preloads_config_and_config_flow( ) +async def test_async_get_component_loads_loop_if_already_in_sys_modules( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Verify async_get_component does not create an executor job if the module is already in sys.modules.""" + integration = await loader.async_get_integration( + hass, "test_package_loaded_executor" + ) + assert integration.pkg_path == "custom_components.test_package_loaded_executor" + assert integration.import_executor is True + assert integration.config_flow is True + + assert "executor_import" not in hass.config.components + assert "executor_import.config_flow" not in hass.config.components + + config_flow_module_name = f"{integration.pkg_path}.config_flow" + module_mock = MagicMock() + config_flow_module_mock = MagicMock() + + def import_module(name: str) -> Any: + if name == integration.pkg_path: + return module_mock + if name == config_flow_module_name: + return config_flow_module_mock + raise ImportError + + modules_without_config_flow = { + k: v for k, v in sys.modules.items() if k != config_flow_module_name + } + with patch.dict( + "sys.modules", + {**modules_without_config_flow, integration.pkg_path: module_mock}, + clear=True, + ), patch("homeassistant.loader.importlib.import_module", import_module): + module = await integration.async_get_component() + + # The config flow is missing so we should load + # in the executor + assert "loaded_executor=True" in caplog.text + assert "loaded_executor=False" not in caplog.text + assert module is module_mock + caplog.clear() + + with patch.dict( + "sys.modules", + { + integration.pkg_path: module_mock, + config_flow_module_name: config_flow_module_mock, + }, + ), patch("homeassistant.loader.importlib.import_module", import_module): + module = await integration.async_get_component() + + # Everything is there so we should load in the event loop + # since it will all be cached + assert "loaded_executor=False" in caplog.text + assert "loaded_executor=True" not in caplog.text + assert module is module_mock + + async def test_async_get_component_deadlock_fallback( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: