Improve error message when people have not moved config flow title yet (#34321)

This commit is contained in:
Paulus Schoutsen 2020-04-16 18:00:30 -07:00 committed by GitHub
parent b9e882fd5e
commit 2326a2941e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 43 deletions

View File

@ -125,18 +125,22 @@ def main():
general_errors = config.errors
invalid_itg = [itg for itg in integrations.values() if itg.errors]
warnings_itg = [itg for itg in integrations.values() if itg.warnings]
print()
print("Integrations:", len(integrations))
print("Invalid integrations:", len(invalid_itg))
print()
if not invalid_itg and not general_errors:
print_integrations_status(config, warnings_itg, show_fixable_errors=False)
if config.action == "generate":
for plugin in plugins:
if hasattr(plugin, "generate"):
plugin.generate(integrations, config)
return 0
print()
if config.action == "generate":
print("Found errors. Generating files canceled.")
print()
@ -147,15 +151,25 @@ def main():
print("*", error)
print()
for integration in sorted(invalid_itg, key=lambda itg: itg.domain):
extra = f" - {integration.path}" if config.specific_integrations else ""
print(f"Integration {integration.domain}{extra}:")
for error in integration.errors:
print("*", error)
print()
invalid_itg.extend(itg for itg in warnings_itg if itg not in invalid_itg)
print_integrations_status(config, invalid_itg, show_fixable_errors=False)
return 1
def print_integrations_status(config, integrations, *, show_fixable_errors=True):
"""Print integration status."""
for integration in sorted(integrations, key=lambda itg: itg.domain):
extra = f" - {integration.path}" if config.specific_integrations else ""
print(f"Integration {integration.domain}{extra}:")
for error in integration.errors:
if show_fixable_errors or not error.fixable:
print("*", error)
for warning in integration.warnings:
print("*", "[WARNING]", warning)
print()
if __name__ == "__main__":
sys.exit(main())

View File

@ -66,6 +66,7 @@ class Integration:
path = attr.ib(type=pathlib.Path)
manifest = attr.ib(type=dict, default=None)
errors = attr.ib(type=List[Error], factory=list)
warnings = attr.ib(type=List[Error], factory=list)
@property
def domain(self) -> str:
@ -86,6 +87,10 @@ class Integration:
"""Add an error."""
self.errors.append(Error(*args, **kwargs))
def add_warning(self, *args, **kwargs):
"""Add an warning."""
self.warnings.append(Error(*args, **kwargs))
def load_manifest(self) -> None:
"""Load manifest."""
manifest_path = self.path / "manifest.json"

View File

@ -1,17 +1,48 @@
"""Validate integration translation files."""
from functools import partial
import json
import logging
from typing import Dict
import voluptuous as vol
from voluptuous.humanize import humanize_error
from .model import Integration
from .model import Config, Integration
_LOGGER = logging.getLogger(__name__)
UNDEFINED = 0
REQUIRED = 1
REMOVED = 2
REMOVED_TITLE_MSG = (
"config.title key has been moved out of config and into the root of strings.json. "
"Starting Home Assistant 0.109 you only need to define this key in the root "
"if the title needs to be different than the name of your integration in the "
"manifest."
)
def data_entry_schema(*, require_title: bool, require_step_title: bool):
def removed_title_validator(config, integration, value):
"""Mark removed title."""
if not config.specific_integrations:
raise vol.Invalid(REMOVED_TITLE_MSG)
# Don't mark it as an error yet for custom components to allow backwards compat.
integration.add_warning("translations", REMOVED_TITLE_MSG)
return value
def gen_data_entry_schema(
*,
config: Config,
integration: Integration,
flow_title: int,
require_step_title: bool,
):
"""Generate a data entry schema."""
step_title_class = vol.Required if require_step_title else vol.Optional
data_entry_schema = {
schema = {
vol.Optional("flow_title"): str,
vol.Required("step"): {
str: {
@ -24,43 +55,64 @@ def data_entry_schema(*, require_title: bool, require_step_title: bool):
vol.Optional("abort"): {str: str},
vol.Optional("create_entry"): {str: str},
}
if require_title:
data_entry_schema[vol.Required("title")] = str
if flow_title == REQUIRED:
schema[vol.Required("title")] = str
elif flow_title == REMOVED:
schema[vol.Optional("title", msg=REMOVED_TITLE_MSG)] = partial(
removed_title_validator, config, integration
)
return data_entry_schema
return schema
STRINGS_SCHEMA = vol.Schema(
{
vol.Optional("title"): str,
vol.Optional("config"): data_entry_schema(
require_title=False, require_step_title=True
),
vol.Optional("options"): data_entry_schema(
require_title=False, require_step_title=False
),
vol.Optional("device_automation"): {
vol.Optional("action_type"): {str: str},
vol.Optional("condition_type"): {str: str},
vol.Optional("trigger_type"): {str: str},
vol.Optional("trigger_subtype"): {str: str},
},
vol.Optional("state"): {str: str},
}
)
AUTH_SCHEMA = vol.Schema(
{
vol.Optional("mfa_setup"): {
str: data_entry_schema(require_title=True, require_step_title=True)
def gen_strings_schema(config: Config, integration: Integration):
"""Generate a strings schema."""
return vol.Schema(
{
vol.Optional("title"): str,
vol.Optional("config"): gen_data_entry_schema(
config=config,
integration=integration,
flow_title=REMOVED,
require_step_title=True,
),
vol.Optional("options"): gen_data_entry_schema(
config=config,
integration=integration,
flow_title=UNDEFINED,
require_step_title=False,
),
vol.Optional("device_automation"): {
vol.Optional("action_type"): {str: str},
vol.Optional("condition_type"): {str: str},
vol.Optional("trigger_type"): {str: str},
vol.Optional("trigger_subtype"): {str: str},
},
vol.Optional("state"): {str: str},
}
}
)
)
def gen_auth_schema(config: Config, integration: Integration):
"""Generate auth schema."""
return vol.Schema(
{
vol.Optional("mfa_setup"): {
str: gen_data_entry_schema(
config=config,
integration=integration,
flow_title=REQUIRED,
require_step_title=True,
)
}
}
)
ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: str}})
def validate_translation_file(integration: Integration):
def validate_translation_file(config: Config, integration: Integration):
"""Validate translation files for integration."""
strings_file = integration.path / "strings.json"
@ -70,11 +122,11 @@ def validate_translation_file(integration: Integration):
strings = json.loads(strings_file.read_text())
if integration.domain == "auth":
schema = AUTH_SCHEMA
schema = gen_auth_schema(config, integration)
elif integration.domain == "onboarding":
schema = ONBOARDING_SCHEMA
else:
schema = STRINGS_SCHEMA
schema = gen_strings_schema(config, integration)
try:
schema(strings)
@ -84,7 +136,7 @@ def validate_translation_file(integration: Integration):
)
def validate(integrations: Dict[str, Integration], config):
def validate(integrations: Dict[str, Integration], config: Config):
"""Handle JSON files inside integrations."""
for integration in integrations.values():
validate_translation_file(integration)
validate_translation_file(config, integration)