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:
Franck Nijhof 2019-12-16 20:16:23 +01:00 committed by Paulus Schoutsen
parent c16fae2c0b
commit 0439d6964c
3 changed files with 36 additions and 24 deletions

View file

@ -60,7 +60,6 @@ _LOGGER = logging.getLogger(__name__)
DATA_PERSISTENT_ERRORS = "bootstrap_persistent_errors"
RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml")
RE_ASCII = re.compile(r"\033\[[^m]*m")
HA_COMPONENT_URL = "[{}](https://home-assistant.io/integrations/{}/)"
YAML_CONFIG_FILE = "configuration.yaml"
VERSION_FILE = ".HA_VERSION"
CONFIG_DIR_NAME = ".homeassistant"
@ -412,19 +411,25 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None:
@callback
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:
"""Log an error for configuration validation.
This method must be run in the event loop.
"""
if hass is not None:
async_notify_setup_error(hass, domain, True)
_LOGGER.error(_format_config_error(ex, domain, config))
async_notify_setup_error(hass, domain, link)
_LOGGER.error(_format_config_error(ex, domain, config, link))
@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.
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__", "?"),
)
if domain != CONF_CORE:
integration = domain.split(".")[-1]
message += (
"Please check the docs at "
f"https://home-assistant.io/integrations/{integration}/"
)
if domain != CONF_CORE and link:
message += f"Please check the docs at {link}"
return message
@ -717,7 +718,7 @@ async def async_process_component_config(
hass, config
)
except (vol.Invalid, HomeAssistantError) as ex:
async_log_exception(ex, domain, config, hass)
async_log_exception(ex, domain, config, hass, integration.documentation)
return None
# No custom config validator, proceed with schema validation
@ -725,7 +726,7 @@ async def async_process_component_config(
try:
return component.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
async_log_exception(ex, domain, config, hass, integration.documentation)
return None
component_platform_schema = getattr(
@ -741,7 +742,7 @@ async def async_process_component_config(
try:
p_validated = component_platform_schema(p_config)
except vol.Invalid as ex:
async_log_exception(ex, domain, p_config, hass)
async_log_exception(ex, domain, p_config, hass, integration.documentation)
continue
# Not all platform components follow same pattern for platforms
@ -770,7 +771,13 @@ async def async_process_component_config(
p_config
)
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
platforms.append(p_validated)
@ -806,7 +813,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]:
@callback
def async_notify_setup_error(
hass: HomeAssistant, component: str, display_link: bool = False
hass: HomeAssistant, component: str, display_link: Optional[str] = None
) -> None:
"""Print a persistent notification.
@ -821,11 +828,11 @@ def async_notify_setup_error(
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():
if link:
part = HA_COMPONENT_URL.format(name.replace("_", "-"), name)
part = f"[{name}]({link})"
else:
part = name

View file

@ -234,6 +234,11 @@ class Integration:
"""Return config_flow."""
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
def is_built_in(self) -> bool:
"""Test if package is a built-in integration."""

View file

@ -92,7 +92,7 @@ async def _async_setup_component(
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."""
_LOGGER.error("Setup failed for %s: %s", domain, msg)
async_notify_setup_error(hass, domain, link)
@ -100,7 +100,7 @@ async def _async_setup_component(
try:
integration = await loader.async_get_integration(hass, domain)
except loader.IntegrationNotFound:
log_error("Integration not found.", False)
log_error("Integration not found.")
return False
# Validate all dependencies exist and there are no circular dependencies
@ -127,7 +127,7 @@ async def _async_setup_component(
try:
await async_process_deps_reqs(hass, config, integration)
except HomeAssistantError as err:
log_error(str(err))
log_error(str(err), integration.documentation)
return False
# Some integrations fail on import because they call functions incorrectly.
@ -135,7 +135,7 @@ async def _async_setup_component(
try:
component = integration.get_component()
except ImportError:
log_error("Unable to import component", False)
log_error("Unable to import component", integration.documentation)
return False
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Setup failed for %s: unknown error", domain)
@ -146,7 +146,7 @@ async def _async_setup_component(
)
if processed_config is None:
log_error("Invalid config.")
log_error("Invalid config.", integration.documentation)
return False
start = timer()
@ -178,7 +178,7 @@ async def _async_setup_component(
return False
except Exception: # pylint: disable=broad-except
_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
finally:
end = timer()