diff --git a/homeassistant/config.py b/homeassistant/config.py index 353a717778b..c3a97a1184c 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -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 diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 9e8ea9fc7ca..de05c944aaf 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -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.""" diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 01b13629e7b..2424f5fc465 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -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()