Report unmet dependencies for failing config flows (#65061)
* Report unmet dependencies for failing config flows * Apply suggestions from code review Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/setup.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Modify error message * Add test Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
40c727df66
commit
0eb2caabcf
4 changed files with 60 additions and 10 deletions
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from http import HTTPStatus
|
||||
|
||||
from aiohttp import web
|
||||
import aiohttp.web_exceptions
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -11,7 +12,7 @@ from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES, POLICY_EDIT
|
|||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import Unauthorized
|
||||
from homeassistant.exceptions import DependencyError, Unauthorized
|
||||
from homeassistant.helpers.data_entry_flow import (
|
||||
FlowManagerIndexView,
|
||||
FlowManagerResourceView,
|
||||
|
@ -127,7 +128,13 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
|
|||
raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
return await super().post(request)
|
||||
try:
|
||||
return await super().post(request)
|
||||
except DependencyError as exc:
|
||||
return web.Response(
|
||||
text=f"Failed dependencies {', '.join(exc.failed_dependencies)}",
|
||||
status=HTTPStatus.BAD_REQUEST,
|
||||
)
|
||||
|
||||
def _prepare_result_json(self, result):
|
||||
"""Convert result to JSON."""
|
||||
|
|
|
@ -199,3 +199,15 @@ class RequiredParameterMissing(HomeAssistantError):
|
|||
),
|
||||
)
|
||||
self.parameter_names = parameter_names
|
||||
|
||||
|
||||
class DependencyError(HomeAssistantError):
|
||||
"""Raised when dependencies can not be setup."""
|
||||
|
||||
def __init__(self, failed_dependencies: list[str]) -> None:
|
||||
"""Initialize error."""
|
||||
super().__init__(
|
||||
self,
|
||||
f"Could not setup dependencies: {', '.join(failed_dependencies)}",
|
||||
)
|
||||
self.failed_dependencies = failed_dependencies
|
||||
|
|
|
@ -18,7 +18,7 @@ from .const import (
|
|||
Platform,
|
||||
)
|
||||
from .core import CALLBACK_TYPE
|
||||
from .exceptions import HomeAssistantError
|
||||
from .exceptions import DependencyError, HomeAssistantError
|
||||
from .helpers.typing import ConfigType
|
||||
from .util import dt as dt_util, ensure_unique_string
|
||||
|
||||
|
@ -83,8 +83,11 @@ async def async_setup_component(
|
|||
|
||||
async def _async_process_dependencies(
|
||||
hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
|
||||
) -> bool:
|
||||
"""Ensure all dependencies are set up."""
|
||||
) -> list[str]:
|
||||
"""Ensure all dependencies are set up.
|
||||
|
||||
Returns a list of dependencies which failed to set up.
|
||||
"""
|
||||
dependencies_tasks = {
|
||||
dep: hass.loop.create_task(async_setup_component(hass, dep, config))
|
||||
for dep in integration.dependencies
|
||||
|
@ -104,7 +107,7 @@ async def _async_process_dependencies(
|
|||
)
|
||||
|
||||
if not dependencies_tasks and not after_dependencies_tasks:
|
||||
return True
|
||||
return []
|
||||
|
||||
if dependencies_tasks:
|
||||
_LOGGER.debug(
|
||||
|
@ -135,8 +138,7 @@ async def _async_process_dependencies(
|
|||
", ".join(failed),
|
||||
)
|
||||
|
||||
return False
|
||||
return True
|
||||
return failed
|
||||
|
||||
|
||||
async def _async_setup_component(
|
||||
|
@ -341,8 +343,8 @@ async def async_process_deps_reqs(
|
|||
elif integration.domain in processed:
|
||||
return
|
||||
|
||||
if not await _async_process_dependencies(hass, config, integration):
|
||||
raise HomeAssistantError("Could not set up all dependencies.")
|
||||
if failed_deps := await _async_process_dependencies(hass, config, integration):
|
||||
raise DependencyError(failed_deps)
|
||||
|
||||
if not hass.config.skip_pip and integration.requirements:
|
||||
async with hass.timeout.async_freeze(integration.domain):
|
||||
|
|
|
@ -252,6 +252,35 @@ async def test_initialize_flow(hass, client):
|
|||
}
|
||||
|
||||
|
||||
async def test_initialize_flow_unmet_dependency(hass, client):
|
||||
"""Test unmet dependencies are listed."""
|
||||
mock_entity_platform(hass, "config_flow.test", None)
|
||||
|
||||
config_schema = vol.Schema({"comp_conf": {"hello": str}}, required=True)
|
||||
mock_integration(
|
||||
hass, MockModule(domain="dependency_1", config_schema=config_schema)
|
||||
)
|
||||
# The test2 config flow should fail because dependency_1 can't be automatically setup
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(domain="test2", partial_manifest={"dependencies": ["dependency_1"]}),
|
||||
)
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
async def async_step_user(self, user_input=None):
|
||||
pass
|
||||
|
||||
with patch.dict(HANDLERS, {"test2": TestFlow}):
|
||||
resp = await client.post(
|
||||
"/api/config/config_entries/flow",
|
||||
json={"handler": "test2", "show_advanced_options": True},
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||
data = await resp.text()
|
||||
assert data == "Failed dependencies dependency_1"
|
||||
|
||||
|
||||
async def test_initialize_flow_unauth(hass, client, hass_admin_user):
|
||||
"""Test we can initialize a flow."""
|
||||
hass_admin_user.groups = []
|
||||
|
|
Loading…
Add table
Reference in a new issue