Use modern platform path when reporting platform config errors (#104238)

* Use modern platform path when reporting platform config errors

* Update tests

* Address review comment

* Explicitly pass platform domain to log helpers

* Revert overly complicated changes

* Try a simpler solution
This commit is contained in:
Erik Montnemery 2023-12-05 15:06:13 +01:00 committed by GitHub
parent db9d6b401a
commit 25bea91683
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 60 deletions

View file

@ -141,7 +141,7 @@ class ConfigExceptionInfo:
exception: Exception
translation_key: ConfigErrorTranslationKey
platform_name: str
platform_path: str
config: ConfigType
integration_link: str | None
@ -659,7 +659,14 @@ def stringify_invalid(
- Give a more user friendly output for unknown options
- Give a more user friendly output for missing options
"""
message_prefix = f"Invalid config for '{domain}'"
if "." in domain:
integration_domain, _, platform_domain = domain.partition(".")
message_prefix = (
f"Invalid config for '{platform_domain}' from integration "
f"'{integration_domain}'"
)
else:
message_prefix = f"Invalid config for '{domain}'"
if domain != CONF_CORE and link:
message_suffix = f", please check the docs at {link}"
else:
@ -730,7 +737,14 @@ def format_homeassistant_error(
link: str | None = None,
) -> str:
"""Format HomeAssistantError thrown by a custom config validator."""
message_prefix = f"Invalid config for '{domain}'"
if "." in domain:
integration_domain, _, platform_domain = domain.partition(".")
message_prefix = (
f"Invalid config for '{platform_domain}' from integration "
f"'{integration_domain}'"
)
else:
message_prefix = f"Invalid config for '{domain}'"
# HomeAssistantError raised by custom config validator has no path to the
# offending configuration key, use the domain key as path instead.
if annotation := find_annotation(config, [domain]):
@ -1064,7 +1078,7 @@ def _get_log_message_and_stack_print_pref(
) -> tuple[str | None, bool, dict[str, str]]:
"""Get message to log and print stack trace preference."""
exception = platform_exception.exception
platform_name = platform_exception.platform_name
platform_path = platform_exception.platform_path
platform_config = platform_exception.config
link = platform_exception.integration_link
@ -1088,7 +1102,7 @@ def _get_log_message_and_stack_print_pref(
True,
),
ConfigErrorTranslationKey.PLATFORM_VALIDATOR_UNKNOWN_ERR: (
f"Unknown error validating {platform_name} platform config with {domain} "
f"Unknown error validating {platform_path} platform config with {domain} "
"component platform schema",
True,
),
@ -1101,7 +1115,7 @@ def _get_log_message_and_stack_print_pref(
True,
),
ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR: (
f"Unknown error validating config for {platform_name} platform "
f"Unknown error validating config for {platform_path} platform "
f"for {domain} component with PLATFORM_SCHEMA",
True,
),
@ -1115,7 +1129,7 @@ def _get_log_message_and_stack_print_pref(
show_stack_trace = False
if isinstance(exception, vol.Invalid):
log_message = format_schema_error(
hass, exception, platform_name, platform_config, link
hass, exception, platform_path, platform_config, link
)
if annotation := find_annotation(platform_config, exception.path):
placeholders["config_file"], line = annotation
@ -1124,9 +1138,9 @@ def _get_log_message_and_stack_print_pref(
if TYPE_CHECKING:
assert isinstance(exception, HomeAssistantError)
log_message = format_homeassistant_error(
hass, exception, platform_name, platform_config, link
hass, exception, platform_path, platform_config, link
)
if annotation := find_annotation(platform_config, [platform_name]):
if annotation := find_annotation(platform_config, [platform_path]):
placeholders["config_file"], line = annotation
placeholders["line"] = str(line)
show_stack_trace = True
@ -1363,7 +1377,7 @@ async def async_process_component_config( # noqa: C901
platforms: list[ConfigType] = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
platform_name = f"{domain}.{p_name}"
platform_path = f"{p_name}.{domain}"
try:
p_validated = component_platform_schema(p_config)
except vol.Invalid as exc:
@ -1400,7 +1414,7 @@ async def async_process_component_config( # noqa: C901
exc_info = ConfigExceptionInfo(
exc,
ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_ERR,
platform_name,
platform_path,
p_config,
integration_docs,
)
@ -1413,7 +1427,7 @@ async def async_process_component_config( # noqa: C901
exc_info = ConfigExceptionInfo(
exc,
ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_EXC,
platform_name,
platform_path,
p_config,
integration_docs,
)
@ -1428,7 +1442,7 @@ async def async_process_component_config( # noqa: C901
exc_info = ConfigExceptionInfo(
exc,
ConfigErrorTranslationKey.PLATFORM_CONFIG_VALIDATION_ERR,
platform_name,
platform_path,
p_config,
p_integration.documentation,
)

View file

@ -276,13 +276,17 @@ async def async_check_ha_config_file( # noqa: C901
# show errors for a missing integration in recovery mode or safe mode to
# not confuse the user.
if not hass.config.recovery_mode and not hass.config.safe_mode:
result.add_warning(f"Platform error {domain}.{p_name} - {ex}")
result.add_warning(
f"Platform error '{domain}' from integration '{p_name}' - {ex}"
)
continue
except (
RequirementsNotFound,
ImportError,
) as ex:
result.add_warning(f"Platform error {domain}.{p_name} - {ex}")
result.add_warning(
f"Platform error '{domain}' from integration '{p_name}' - {ex}"
)
continue
# Validate platform specific schema

View file

@ -263,7 +263,7 @@ async def _async_setup_component(
if platform_exception.translation_key not in NOTIFY_FOR_TRANSLATION_KEYS:
continue
async_notify_setup_error(
hass, platform_exception.platform_name, platform_exception.integration_link
hass, platform_exception.platform_path, platform_exception.integration_link
)
if processed_config is None:
log_error("Invalid config.")

View file

@ -62,8 +62,8 @@ async def test_setup_missing_config(
await hass.async_block_till_done()
assert_setup_component(0, SWITCH_DOMAIN)
assert (
"Invalid config for 'switch.rest': required key 'resource' not provided"
in caplog.text
"Invalid config for 'switch' from integration 'rest': required key 'resource' "
"not provided" in caplog.text
)
@ -75,7 +75,10 @@ async def test_setup_missing_schema(
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
await hass.async_block_till_done()
assert_setup_component(0, SWITCH_DOMAIN)
assert "Invalid config for 'switch.rest': invalid url" in caplog.text
assert (
"Invalid config for 'switch' from integration 'rest': invalid url"
in caplog.text
)
@respx.mock

View file

@ -424,7 +424,7 @@ async def test_template_open_or_position(
) -> None:
"""Test that at least one of open_cover or set_position is used."""
assert hass.states.async_all("cover") == []
assert "Invalid config for 'cover.template'" in caplog_setup_text
assert "Invalid config for 'cover' from integration 'template'" in caplog_setup_text
@pytest.mark.parametrize(("count", "domain"), [(1, DOMAIN)])

View file

@ -513,6 +513,6 @@ async def test_invalid_min_sample(
record = caplog.records[0]
assert record.levelname == "ERROR"
assert (
"Invalid config for 'binary_sensor.trend': min_samples must be smaller than or equal to max_samples"
in record.message
"Invalid config for 'binary_sensor' from integration 'trend': min_samples must "
"be smaller than or equal to max_samples" in record.message
)

View file

@ -227,7 +227,12 @@ async def test_platform_not_found(hass: HomeAssistant) -> None:
assert res["light"] == []
warning = CheckConfigError(
"Platform error light.beer - Integration 'beer' not found.", None, None
(
"Platform error 'light' from integration 'beer' - "
"Integration 'beer' not found."
),
None,
None,
)
_assert_warnings_errors(res, [warning], [])
@ -361,7 +366,7 @@ async def test_platform_import_error(hass: HomeAssistant) -> None:
assert res.keys() == {"homeassistant", "light"}
warning = CheckConfigError(
"Platform error light.demo - blablabla",
"Platform error 'light' from integration 'demo' - blablabla",
None,
None,
)

View file

@ -78,7 +78,10 @@ def test_config_platform_valid(
(
BASE_CONFIG + "light:\n platform: beer",
{"homeassistant", "light"},
"Platform error light.beer - Integration 'beer' not found.",
(
"Platform error 'light' from integration 'beer' - "
"Integration 'beer' not found."
),
),
],
)

View file

@ -7,18 +7,18 @@
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 9: expected str for dictionary value 'option1', got 123",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 9: expected str for dictionary value 'option1', got 123",
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 12: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 12: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option",
}),
dict({
'has_exc_info': False,
'message': '''
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 18: required key 'option1' not provided
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 19: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 20: expected str for dictionary value 'option2', got 123
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 18: required key 'option1' not provided
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 19: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 20: expected str for dictionary value 'option2', got 123
''',
}),
dict({
@ -63,18 +63,18 @@
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 8: expected str for dictionary value 'option1', got 123",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 8: expected str for dictionary value 'option1', got 123",
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 11: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 11: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option",
}),
dict({
'has_exc_info': False,
'message': '''
Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 17: required key 'option1' not provided
Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 18: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option
Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 19: expected str for dictionary value 'option2', got 123
Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 17: required key 'option1' not provided
Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 18: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option
Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 19: expected str for dictionary value 'option2', got 123
''',
}),
dict({
@ -119,18 +119,18 @@
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_3.yaml, line 3: expected str for dictionary value 'option1', got 123",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_3.yaml, line 3: expected str for dictionary value 'option1', got 123",
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_4.yaml, line 3: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_4.yaml, line 3: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option",
}),
dict({
'has_exc_info': False,
'message': '''
Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_5.yaml, line 5: required key 'option1' not provided
Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_5.yaml, line 6: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option
Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_5.yaml, line 7: expected str for dictionary value 'option2', got 123
Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_5.yaml, line 5: required key 'option1' not provided
Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_5.yaml, line 6: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option
Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_5.yaml, line 7: expected str for dictionary value 'option2', got 123
''',
}),
])
@ -143,18 +143,18 @@
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_2.yaml, line 3: expected str for dictionary value 'option1', got 123",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_2.yaml, line 3: expected str for dictionary value 'option1', got 123",
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_2.yaml, line 6: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_2.yaml, line 6: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option",
}),
dict({
'has_exc_info': False,
'message': '''
Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_2.yaml, line 12: required key 'option1' not provided
Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_2.yaml, line 13: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option
Invalid config for 'iot_domain.non_adr_0007' at iot_domain/iot_domain_2.yaml, line 14: expected str for dictionary value 'option2', got 123
Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_2.yaml, line 12: required key 'option1' not provided
Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_2.yaml, line 13: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option
Invalid config for 'iot_domain' from integration 'non_adr_0007' at iot_domain/iot_domain_2.yaml, line 14: expected str for dictionary value 'option2', got 123
''',
}),
])
@ -167,18 +167,18 @@
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 16: expected str for dictionary value 'option1', got 123",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 16: expected str for dictionary value 'option1', got 123",
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 21: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 21: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option",
}),
dict({
'has_exc_info': False,
'message': '''
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 29: required key 'option1' not provided
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 30: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 31: expected str for dictionary value 'option2', got 123
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 29: required key 'option1' not provided
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 30: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 31: expected str for dictionary value 'option2', got 123
''',
}),
dict({
@ -255,18 +255,18 @@
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 9: expected str for dictionary value 'option1', got 123",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 9: expected str for dictionary value 'option1', got 123",
}),
dict({
'has_exc_info': False,
'message': "Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 12: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option",
'message': "Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 12: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option",
}),
dict({
'has_exc_info': False,
'message': '''
Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 18: required key 'option1' not provided
Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 19: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option
Invalid config for 'iot_domain.non_adr_0007' at integrations/iot_domain.yaml, line 20: expected str for dictionary value 'option2', got 123
Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 18: required key 'option1' not provided
Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 19: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option
Invalid config for 'iot_domain' from integration 'non_adr_0007' at integrations/iot_domain.yaml, line 20: expected str for dictionary value 'option2', got 123
''',
}),
])
@ -274,12 +274,12 @@
# name: test_component_config_validation_error_with_docs[basic]
list([
"Invalid config for 'iot_domain' at configuration.yaml, line 6: required key 'platform' not provided, please check the docs at https://www.home-assistant.io/integrations/iot_domain",
"Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 9: expected str for dictionary value 'option1', got 123, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007",
"Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 12: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007",
"Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 9: expected str for dictionary value 'option1', got 123, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007",
"Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 12: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007",
'''
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 18: required key 'option1' not provided, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 19: 'no_such_option' is an invalid option for 'iot_domain.non_adr_0007', check: no_such_option, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007
Invalid config for 'iot_domain.non_adr_0007' at configuration.yaml, line 20: expected str for dictionary value 'option2', got 123, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 18: required key 'option1' not provided, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 19: 'no_such_option' is an invalid option for 'non_adr_0007.iot_domain', check: no_such_option, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007
Invalid config for 'iot_domain' from integration 'non_adr_0007' at configuration.yaml, line 20: expected str for dictionary value 'option2', got 123, please check the docs at https://www.home-assistant.io/integrations/non_adr_0007
''',
"Invalid config for 'adr_0007_2' at configuration.yaml, line 27: required key 'host' not provided, please check the docs at https://www.home-assistant.io/integrations/adr_0007_2",
"Invalid config for 'adr_0007_3' at configuration.yaml, line 32: expected int for dictionary value 'adr_0007_3->port', got 'foo', please check the docs at https://www.home-assistant.io/integrations/adr_0007_3",