Fix persistent setup error notification content (#29995)
* Fix persistent setup error notification content * Use documentation from manifest, enriched error messages * Fix issue caught by mypy
This commit is contained in:
parent
c16fae2c0b
commit
0439d6964c
3 changed files with 36 additions and 24 deletions
|
@ -60,7 +60,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
DATA_PERSISTENT_ERRORS = "bootstrap_persistent_errors"
|
DATA_PERSISTENT_ERRORS = "bootstrap_persistent_errors"
|
||||||
RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml")
|
RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml")
|
||||||
RE_ASCII = re.compile(r"\033\[[^m]*m")
|
RE_ASCII = re.compile(r"\033\[[^m]*m")
|
||||||
HA_COMPONENT_URL = "[{}](https://home-assistant.io/integrations/{}/)"
|
|
||||||
YAML_CONFIG_FILE = "configuration.yaml"
|
YAML_CONFIG_FILE = "configuration.yaml"
|
||||||
VERSION_FILE = ".HA_VERSION"
|
VERSION_FILE = ".HA_VERSION"
|
||||||
CONFIG_DIR_NAME = ".homeassistant"
|
CONFIG_DIR_NAME = ".homeassistant"
|
||||||
|
@ -412,19 +411,25 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None:
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_log_exception(
|
def async_log_exception(
|
||||||
ex: Exception, domain: str, config: Dict, hass: HomeAssistant
|
ex: Exception,
|
||||||
|
domain: str,
|
||||||
|
config: Dict,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
link: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Log an error for configuration validation.
|
"""Log an error for configuration validation.
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
"""
|
"""
|
||||||
if hass is not None:
|
if hass is not None:
|
||||||
async_notify_setup_error(hass, domain, True)
|
async_notify_setup_error(hass, domain, link)
|
||||||
_LOGGER.error(_format_config_error(ex, domain, config))
|
_LOGGER.error(_format_config_error(ex, domain, config, link))
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _format_config_error(ex: Exception, domain: str, config: Dict) -> str:
|
def _format_config_error(
|
||||||
|
ex: Exception, domain: str, config: Dict, link: Optional[str] = None
|
||||||
|
) -> str:
|
||||||
"""Generate log exception for configuration validation.
|
"""Generate log exception for configuration validation.
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
|
@ -455,12 +460,8 @@ def _format_config_error(ex: Exception, domain: str, config: Dict) -> str:
|
||||||
getattr(domain_config, "__line__", "?"),
|
getattr(domain_config, "__line__", "?"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if domain != CONF_CORE:
|
if domain != CONF_CORE and link:
|
||||||
integration = domain.split(".")[-1]
|
message += f"Please check the docs at {link}"
|
||||||
message += (
|
|
||||||
"Please check the docs at "
|
|
||||||
f"https://home-assistant.io/integrations/{integration}/"
|
|
||||||
)
|
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
@ -717,7 +718,7 @@ async def async_process_component_config(
|
||||||
hass, config
|
hass, config
|
||||||
)
|
)
|
||||||
except (vol.Invalid, HomeAssistantError) as ex:
|
except (vol.Invalid, HomeAssistantError) as ex:
|
||||||
async_log_exception(ex, domain, config, hass)
|
async_log_exception(ex, domain, config, hass, integration.documentation)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# No custom config validator, proceed with schema validation
|
# No custom config validator, proceed with schema validation
|
||||||
|
@ -725,7 +726,7 @@ async def async_process_component_config(
|
||||||
try:
|
try:
|
||||||
return component.CONFIG_SCHEMA(config) # type: ignore
|
return component.CONFIG_SCHEMA(config) # type: ignore
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
async_log_exception(ex, domain, config, hass)
|
async_log_exception(ex, domain, config, hass, integration.documentation)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
component_platform_schema = getattr(
|
component_platform_schema = getattr(
|
||||||
|
@ -741,7 +742,7 @@ async def async_process_component_config(
|
||||||
try:
|
try:
|
||||||
p_validated = component_platform_schema(p_config)
|
p_validated = component_platform_schema(p_config)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
async_log_exception(ex, domain, p_config, hass)
|
async_log_exception(ex, domain, p_config, hass, integration.documentation)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Not all platform components follow same pattern for platforms
|
# Not all platform components follow same pattern for platforms
|
||||||
|
@ -770,7 +771,13 @@ async def async_process_component_config(
|
||||||
p_config
|
p_config
|
||||||
)
|
)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
async_log_exception(ex, f"{domain}.{p_name}", p_config, hass)
|
async_log_exception(
|
||||||
|
ex,
|
||||||
|
f"{domain}.{p_name}",
|
||||||
|
p_config,
|
||||||
|
hass,
|
||||||
|
p_integration.documentation,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
platforms.append(p_validated)
|
platforms.append(p_validated)
|
||||||
|
@ -806,7 +813,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]:
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_notify_setup_error(
|
def async_notify_setup_error(
|
||||||
hass: HomeAssistant, component: str, display_link: bool = False
|
hass: HomeAssistant, component: str, display_link: Optional[str] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Print a persistent notification.
|
"""Print a persistent notification.
|
||||||
|
|
||||||
|
@ -821,11 +828,11 @@ def async_notify_setup_error(
|
||||||
|
|
||||||
errors[component] = errors.get(component) or display_link
|
errors[component] = errors.get(component) or display_link
|
||||||
|
|
||||||
message = "The following components and platforms could not be set up:\n\n"
|
message = "The following integrations and platforms could not be set up:\n\n"
|
||||||
|
|
||||||
for name, link in errors.items():
|
for name, link in errors.items():
|
||||||
if link:
|
if link:
|
||||||
part = HA_COMPONENT_URL.format(name.replace("_", "-"), name)
|
part = f"[{name}]({link})"
|
||||||
else:
|
else:
|
||||||
part = name
|
part = name
|
||||||
|
|
||||||
|
|
|
@ -234,6 +234,11 @@ class Integration:
|
||||||
"""Return config_flow."""
|
"""Return config_flow."""
|
||||||
return cast(bool, self.manifest.get("config_flow", False))
|
return cast(bool, self.manifest.get("config_flow", False))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def documentation(self) -> Optional[str]:
|
||||||
|
"""Return documentation."""
|
||||||
|
return cast(str, self.manifest.get("documentation"))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_built_in(self) -> bool:
|
def is_built_in(self) -> bool:
|
||||||
"""Test if package is a built-in integration."""
|
"""Test if package is a built-in integration."""
|
||||||
|
|
|
@ -92,7 +92,7 @@ async def _async_setup_component(
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def log_error(msg: str, link: bool = True) -> None:
|
def log_error(msg: str, link: Optional[str] = None) -> None:
|
||||||
"""Log helper."""
|
"""Log helper."""
|
||||||
_LOGGER.error("Setup failed for %s: %s", domain, msg)
|
_LOGGER.error("Setup failed for %s: %s", domain, msg)
|
||||||
async_notify_setup_error(hass, domain, link)
|
async_notify_setup_error(hass, domain, link)
|
||||||
|
@ -100,7 +100,7 @@ async def _async_setup_component(
|
||||||
try:
|
try:
|
||||||
integration = await loader.async_get_integration(hass, domain)
|
integration = await loader.async_get_integration(hass, domain)
|
||||||
except loader.IntegrationNotFound:
|
except loader.IntegrationNotFound:
|
||||||
log_error("Integration not found.", False)
|
log_error("Integration not found.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Validate all dependencies exist and there are no circular dependencies
|
# Validate all dependencies exist and there are no circular dependencies
|
||||||
|
@ -127,7 +127,7 @@ async def _async_setup_component(
|
||||||
try:
|
try:
|
||||||
await async_process_deps_reqs(hass, config, integration)
|
await async_process_deps_reqs(hass, config, integration)
|
||||||
except HomeAssistantError as err:
|
except HomeAssistantError as err:
|
||||||
log_error(str(err))
|
log_error(str(err), integration.documentation)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Some integrations fail on import because they call functions incorrectly.
|
# Some integrations fail on import because they call functions incorrectly.
|
||||||
|
@ -135,7 +135,7 @@ async def _async_setup_component(
|
||||||
try:
|
try:
|
||||||
component = integration.get_component()
|
component = integration.get_component()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
log_error("Unable to import component", False)
|
log_error("Unable to import component", integration.documentation)
|
||||||
return False
|
return False
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Setup failed for %s: unknown error", domain)
|
_LOGGER.exception("Setup failed for %s: unknown error", domain)
|
||||||
|
@ -146,7 +146,7 @@ async def _async_setup_component(
|
||||||
)
|
)
|
||||||
|
|
||||||
if processed_config is None:
|
if processed_config is None:
|
||||||
log_error("Invalid config.")
|
log_error("Invalid config.", integration.documentation)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
start = timer()
|
start = timer()
|
||||||
|
@ -178,7 +178,7 @@ async def _async_setup_component(
|
||||||
return False
|
return False
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Error during setup of component %s", domain)
|
_LOGGER.exception("Error during setup of component %s", domain)
|
||||||
async_notify_setup_error(hass, domain, True)
|
async_notify_setup_error(hass, domain, integration.documentation)
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
end = timer()
|
end = timer()
|
||||||
|
|
Loading…
Add table
Reference in a new issue