diff --git a/homeassistant/loader.py b/homeassistant/loader.py index e904fa4bdaf..2498cf39ffe 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1447,31 +1447,13 @@ async def resolve_integrations_dependencies( Detects circular dependencies and missing integrations. """ - resolved = _ResolveDependenciesCache() - - async def _resolve_deps_catch_exceptions(itg: Integration) -> set[str] | None: - try: - return await _do_resolve_dependencies(itg, cache=resolved) - except Exception as exc: # noqa: BLE001 - _LOGGER.error("Unable to resolve dependencies for %s: %s", itg.domain, exc) - return None - - resolve_dependencies_tasks = { - itg.domain: create_eager_task( - _resolve_deps_catch_exceptions(itg), - name=f"resolve dependencies {itg.domain}", - loop=hass.loop, - ) - for itg in integrations - } - - result = await asyncio.gather(*resolve_dependencies_tasks.values()) - - return { - domain: deps - for domain, deps in zip(resolve_dependencies_tasks, result, strict=True) - if deps is not None - } + return await _resolve_integrations_dependencies( + hass, + "resolve dependencies", + integrations, + cache=_ResolveDependenciesCache(), + ignore_exceptions=False, + ) async def resolve_integrations_after_dependencies( @@ -1485,26 +1467,46 @@ async def resolve_integrations_after_dependencies( Detects circular dependencies and missing integrations. """ - resolved: dict[Integration, set[str] | Exception] = {} + return await _resolve_integrations_dependencies( + hass, + "resolve (after) dependencies", + integrations, + cache={}, + possible_after_dependencies=possible_after_dependencies, + ignore_exceptions=ignore_exceptions, + ) + + +async def _resolve_integrations_dependencies( + hass: HomeAssistant, + name: str, + integrations: Iterable[Integration], + *, + cache: _ResolveDependenciesCacheProtocol, + possible_after_dependencies: set[str] | None | UndefinedType = UNDEFINED, + ignore_exceptions: bool, +) -> dict[str, set[str]]: + """Resolve all dependencies, possibly including after_dependencies, for integrations. + + Detects circular dependencies and missing integrations. + """ async def _resolve_deps_catch_exceptions(itg: Integration) -> set[str] | None: try: - return await _do_resolve_dependencies( + return await _resolve_integration_dependencies( itg, - cache=resolved, + cache=cache, possible_after_dependencies=possible_after_dependencies, ignore_exceptions=ignore_exceptions, ) except Exception as exc: # noqa: BLE001 - _LOGGER.error( - "Unable to resolve (after) dependencies for %s: %s", itg.domain, exc - ) + _LOGGER.error("Unable to %s for %s: %s", name, itg.domain, exc) return None resolve_dependencies_tasks = { itg.domain: create_eager_task( _resolve_deps_catch_exceptions(itg), - name=f"resolve after dependencies {itg.domain}", + name=f"{name} {itg.domain}", loop=hass.loop, ) for itg in integrations @@ -1519,7 +1521,7 @@ async def resolve_integrations_after_dependencies( } -async def _do_resolve_dependencies( +async def _resolve_integration_dependencies( itg: Integration, *, cache: _ResolveDependenciesCacheProtocol, @@ -1542,7 +1544,7 @@ async def _do_resolve_dependencies( resolved = cache resolving: set[str] = set() - async def do_resolve_dependencies_impl(itg: Integration) -> set[str]: + async def resolve_dependencies_impl(itg: Integration) -> set[str]: domain = itg.domain # If it's already resolved, no point doing it again. @@ -1584,7 +1586,7 @@ async def _do_resolve_dependencies( all_dependencies.add(dep_domain) try: - dep_dependencies = await do_resolve_dependencies_impl(dep_integration) + dep_dependencies = await resolve_dependencies_impl(dep_integration) except CircularDependency as exc: exc.extend_cycle(domain) resolved[itg] = exc @@ -1600,7 +1602,7 @@ async def _do_resolve_dependencies( resolved[itg] = all_dependencies return all_dependencies - return await do_resolve_dependencies_impl(itg) + return await resolve_dependencies_impl(itg) class LoaderError(Exception): diff --git a/tests/test_loader.py b/tests/test_loader.py index 0b83ddee3ea..793e0de6fef 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -29,25 +29,25 @@ async def test_circular_component_dependencies(hass: HomeAssistant) -> None: mod_4 = mock_integration(hass, MockModule("mod4", dependencies=["mod2", "mod3"])) all_domains = {"mod1", "mod2", "mod3", "mod4"} - deps = await loader._do_resolve_dependencies(mod_4, cache={}) + deps = await loader._resolve_integration_dependencies(mod_4, cache={}) assert deps == {"mod1", "mod2", "mod3"} # Create a circular dependency mock_integration(hass, MockModule("mod1", dependencies=["mod4"])) with pytest.raises(loader.CircularDependency): - await loader._do_resolve_dependencies(mod_4, cache={}) + await loader._resolve_integration_dependencies(mod_4, cache={}) # Create a different circular dependency mock_integration(hass, MockModule("mod1", dependencies=["mod3"])) with pytest.raises(loader.CircularDependency): - await loader._do_resolve_dependencies(mod_4, cache={}) + await loader._resolve_integration_dependencies(mod_4, cache={}) # Create a circular after_dependency mock_integration( hass, MockModule("mod1", partial_manifest={"after_dependencies": ["mod4"]}) ) with pytest.raises(loader.CircularDependency): - await loader._do_resolve_dependencies( + await loader._resolve_integration_dependencies( mod_4, cache={}, possible_after_dependencies=all_domains, @@ -58,7 +58,7 @@ async def test_circular_component_dependencies(hass: HomeAssistant) -> None: hass, MockModule("mod1", partial_manifest={"after_dependencies": ["mod3"]}) ) with pytest.raises(loader.CircularDependency): - await loader._do_resolve_dependencies( + await loader._resolve_integration_dependencies( mod_4, cache={}, possible_after_dependencies=all_domains, @@ -72,7 +72,7 @@ async def test_circular_component_dependencies(hass: HomeAssistant) -> None: hass, MockModule("mod4", partial_manifest={"after_dependencies": ["mod2"]}) ) with pytest.raises(loader.CircularDependency): - await loader._do_resolve_dependencies( + await loader._resolve_integration_dependencies( mod_4, cache={}, possible_after_dependencies=all_domains,