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
ba237fd383
commit
b902c59504
4 changed files with 60 additions and 10 deletions
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
import aiohttp.web_exceptions
|
import aiohttp.web_exceptions
|
||||||
import voluptuous as vol
|
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 import websocket_api
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import Unauthorized
|
from homeassistant.exceptions import DependencyError, Unauthorized
|
||||||
from homeassistant.helpers.data_entry_flow import (
|
from homeassistant.helpers.data_entry_flow import (
|
||||||
FlowManagerIndexView,
|
FlowManagerIndexView,
|
||||||
FlowManagerResourceView,
|
FlowManagerResourceView,
|
||||||
|
@ -127,7 +128,13 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
|
||||||
raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
|
raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# 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):
|
def _prepare_result_json(self, result):
|
||||||
"""Convert result to JSON."""
|
"""Convert result to JSON."""
|
||||||
|
|
|
@ -199,3 +199,15 @@ class RequiredParameterMissing(HomeAssistantError):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.parameter_names = parameter_names
|
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,
|
Platform,
|
||||||
)
|
)
|
||||||
from .core import CALLBACK_TYPE
|
from .core import CALLBACK_TYPE
|
||||||
from .exceptions import HomeAssistantError
|
from .exceptions import DependencyError, HomeAssistantError
|
||||||
from .helpers.typing import ConfigType
|
from .helpers.typing import ConfigType
|
||||||
from .util import dt as dt_util, ensure_unique_string
|
from .util import dt as dt_util, ensure_unique_string
|
||||||
|
|
||||||
|
@ -83,8 +83,11 @@ async def async_setup_component(
|
||||||
|
|
||||||
async def _async_process_dependencies(
|
async def _async_process_dependencies(
|
||||||
hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
|
hass: core.HomeAssistant, config: ConfigType, integration: loader.Integration
|
||||||
) -> bool:
|
) -> list[str]:
|
||||||
"""Ensure all dependencies are set up."""
|
"""Ensure all dependencies are set up.
|
||||||
|
|
||||||
|
Returns a list of dependencies which failed to set up.
|
||||||
|
"""
|
||||||
dependencies_tasks = {
|
dependencies_tasks = {
|
||||||
dep: hass.loop.create_task(async_setup_component(hass, dep, config))
|
dep: hass.loop.create_task(async_setup_component(hass, dep, config))
|
||||||
for dep in integration.dependencies
|
for dep in integration.dependencies
|
||||||
|
@ -104,7 +107,7 @@ async def _async_process_dependencies(
|
||||||
)
|
)
|
||||||
|
|
||||||
if not dependencies_tasks and not after_dependencies_tasks:
|
if not dependencies_tasks and not after_dependencies_tasks:
|
||||||
return True
|
return []
|
||||||
|
|
||||||
if dependencies_tasks:
|
if dependencies_tasks:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
@ -135,8 +138,7 @@ async def _async_process_dependencies(
|
||||||
", ".join(failed),
|
", ".join(failed),
|
||||||
)
|
)
|
||||||
|
|
||||||
return False
|
return failed
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
async def _async_setup_component(
|
async def _async_setup_component(
|
||||||
|
@ -341,8 +343,8 @@ async def async_process_deps_reqs(
|
||||||
elif integration.domain in processed:
|
elif integration.domain in processed:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not await _async_process_dependencies(hass, config, integration):
|
if failed_deps := await _async_process_dependencies(hass, config, integration):
|
||||||
raise HomeAssistantError("Could not set up all dependencies.")
|
raise DependencyError(failed_deps)
|
||||||
|
|
||||||
if not hass.config.skip_pip and integration.requirements:
|
if not hass.config.skip_pip and integration.requirements:
|
||||||
async with hass.timeout.async_freeze(integration.domain):
|
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):
|
async def test_initialize_flow_unauth(hass, client, hass_admin_user):
|
||||||
"""Test we can initialize a flow."""
|
"""Test we can initialize a flow."""
|
||||||
hass_admin_user.groups = []
|
hass_admin_user.groups = []
|
||||||
|
|
Loading…
Add table
Reference in a new issue