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" 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

View file

@ -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."""

View file

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