From 25bea91683f416c026f24deb48498890937e04de Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 5 Dec 2023 15:06:13 +0100 Subject: [PATCH] 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 --- homeassistant/config.py | 40 +++++++---- homeassistant/helpers/check_config.py | 8 ++- homeassistant/setup.py | 2 +- tests/components/rest/test_switch.py | 9 ++- tests/components/template/test_cover.py | 2 +- tests/components/trend/test_binary_sensor.py | 4 +- tests/helpers/test_check_config.py | 9 ++- tests/scripts/test_check_config.py | 5 +- tests/snapshots/test_config.ambr | 70 ++++++++++---------- 9 files changed, 89 insertions(+), 60 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 5d5d246884c..bbdd30c3683 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -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, ) diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 23707949dcd..59334c20b30 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -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 diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 53e88f2aaa5..7a7f4323be6 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -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.") diff --git a/tests/components/rest/test_switch.py b/tests/components/rest/test_switch.py index df90af44e73..7ded4fb0aed 100644 --- a/tests/components/rest/test_switch.py +++ b/tests/components/rest/test_switch.py @@ -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 diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index 35f03ee9508..88f0fc366a3 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -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)]) diff --git a/tests/components/trend/test_binary_sensor.py b/tests/components/trend/test_binary_sensor.py index ddd980ae970..1906c002101 100644 --- a/tests/components/trend/test_binary_sensor.py +++ b/tests/components/trend/test_binary_sensor.py @@ -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 ) diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index b65f09aeaf9..de57fa0a8f3 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -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, ) diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 06dff1e0869..425ad561f50 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -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." + ), ), ], ) diff --git a/tests/snapshots/test_config.ambr b/tests/snapshots/test_config.ambr index 7438bda5cde..26a44f60184 100644 --- a/tests/snapshots/test_config.ambr +++ b/tests/snapshots/test_config.ambr @@ -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",