Use common helper function in resolve integration dependencies (#140989)

Extract to helper function in resolve integration dependencies
This commit is contained in:
Artur Pragacz 2025-04-10 17:41:06 +02:00 committed by GitHub
parent eee6e8a2c3
commit efbb94a1b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 42 deletions

View File

@ -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):

View File

@ -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,