From e0db9ab0410a28638de11f6da4c9865d4aa90d93 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 1 Jun 2023 09:51:26 +0200 Subject: [PATCH] Add CONFIG_SCHEMA to broadlink (#93854) * Add CONFIG_SCHEMA to broadlink * Simplify error message * Rename to no_yaml_config_schema * Add tests * Raise issue * Tweak repairs issue description and title * Update homeassistant/helpers/config_validation.py * Add link * Update homeassistant/components/homeassistant/strings.json Co-authored-by: Franck Nijhof * Fix logic, add test --------- Co-authored-by: Franck Nijhof --- .../components/broadlink/__init__.py | 3 + .../components/homeassistant/strings.json | 4 +- homeassistant/helpers/config_validation.py | 50 ++++++++++++++++ tests/helpers/test_config_validation.py | 58 ++++++++++++++++++- 4 files changed, 110 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py index 3b1312a64c5..f9c00b8d4d5 100644 --- a/homeassistant/components/broadlink/__init__.py +++ b/homeassistant/components/broadlink/__init__.py @@ -5,12 +5,15 @@ from dataclasses import dataclass, field from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType from .const import DOMAIN from .device import BroadlinkDevice from .heartbeat import BroadlinkHeartbeat +CONFIG_SCHEMA = cv.no_yaml_config_schema(DOMAIN) + @dataclass class BroadlinkData: diff --git a/homeassistant/components/homeassistant/strings.json b/homeassistant/components/homeassistant/strings.json index 55a40e7ba9d..6ea8a214dda 100644 --- a/homeassistant/components/homeassistant/strings.json +++ b/homeassistant/components/homeassistant/strings.json @@ -13,8 +13,8 @@ "description": "Support for running Home Assistant in the current used Python version {current_python_version} is deprecated and will be removed in Home Assistant {breaks_in_ha_version}. Please upgrade Python to {required_python_version} to prevent your Home Assistant instance from breaking." }, "integration_key_no_support": { - "title": "This integration does not support YAML configuration", - "description": "The {domain} integration does not support configuration via YAML file. You may not notice any obvious issues with the integration, but the configuration settings defined in YAML are not actually applied. \n\nTo resolve this: 1. Please remove this integration from your YAML configuration file.\n\n2. Restart Home Assistant." + "title": "The {domain} integration does not support YAML configuration", + "description": "The {domain} integration does not support configuration via YAML file. You may not notice any obvious issues with the integration, but any configuration settings defined in YAML are not actually applied.\n\nTo resolve this:\n\n1. If you've not already done so, [set up the integration]({add_integration}).\n\n2. Remove `{domain}:` from your YAML configuration file.\n\n3. Restart Home Assistant." } }, "system_health": { diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 6551b7e4709..da39913b491 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -86,6 +86,7 @@ from homeassistant.const import ( UnitOfTemperature, ) from homeassistant.core import ( + DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, async_get_hass, split_entity_id, @@ -1074,6 +1075,55 @@ def empty_config_schema(domain: str) -> Callable[[dict], dict]: return validator +def no_yaml_config_schema(domain: str) -> Callable[[dict], dict]: + """Return a config schema which logs if attempted to setup from YAML.""" + + module = inspect.getmodule(inspect.stack(context=0)[2].frame) + if module is not None: + module_name = module.__name__ + else: + # If Python is unable to access the sources files, the call stack frame + # will be missing information, so let's guard. + # https://github.com/home-assistant/core/issues/24982 + module_name = __name__ + logger_func = logging.getLogger(module_name).error + + def raise_issue() -> None: + # pylint: disable-next=import-outside-toplevel + from .issue_registry import IssueSeverity, async_create_issue + + add_integration = f"/_my_redirect/config_flow_start?domain={domain}" + with contextlib.suppress(LookupError): + hass = async_get_hass() + async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"integration_key_no_support_{domain}", + is_fixable=False, + issue_domain=domain, + severity=IssueSeverity.ERROR, + translation_key="integration_key_no_support", + translation_placeholders={ + "domain": domain, + "add_integration": add_integration, + }, + ) + + def validator(config: dict) -> dict: + if domain in config: + logger_func( + ( + "The %s integration does not support YAML setup, please remove it " + "from your configuration file" + ), + domain, + ) + raise_issue() + return config + + return validator + + PLATFORM_SCHEMA = vol.Schema( { vol.Required(CONF_PLATFORM): string, diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 58082838841..fb8cbc2f46b 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -11,8 +11,13 @@ import pytest import voluptuous as vol import homeassistant -from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv, selector, template +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +from homeassistant.helpers import ( + config_validation as cv, + issue_registry as ir, + selector, + template, +) def test_boolean() -> None: @@ -1475,7 +1480,7 @@ def test_positive_time_period_template() -> None: def test_empty_schema(caplog: pytest.LogCaptureFixture) -> None: - """Test if the current module cannot be inspected.""" + """Test empty_config_schema.""" expected_message = ( "The test_domain integration does not support any configuration parameters" ) @@ -1494,3 +1499,50 @@ def test_empty_schema_cant_find_module() -> None: """Test if the current module cannot be inspected.""" with patch("inspect.getmodule", return_value=None): cv.empty_config_schema("test_domain")({"test_domain": {"foo": "bar"}}) + + +def test_no_yaml_schema(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: + """Test no_yaml_config_schema.""" + expected_issue = "integration_key_no_support_test_domain" + expected_message = ( + "The test_domain integration does not support YAML setup, please remove " + "it from your configuration" + ) + issue_registry = ir.async_get(hass) + + cv.no_yaml_config_schema("test_domain")({}) + assert expected_message not in caplog.text + assert not issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, expected_issue) + + cv.no_yaml_config_schema("test_domain")({"test_domain": {}}) + assert expected_message in caplog.text + assert issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, expected_issue) + issue_registry.async_delete(HOMEASSISTANT_DOMAIN, expected_issue) + + cv.no_yaml_config_schema("test_domain")({"test_domain": {"foo": "bar"}}) + assert expected_message in caplog.text + assert issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, expected_issue) + + +def test_no_yaml_schema_cant_find_module() -> None: + """Test if the current module cannot be inspected.""" + with patch("inspect.getmodule", return_value=None): + cv.no_yaml_config_schema("test_domain")({"test_domain": {"foo": "bar"}}) + + +def test_no_yaml_schema_no_hass( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test if the the hass context var is not set in our context.""" + with patch( + "homeassistant.helpers.config_validation.async_get_hass", + side_effect=LookupError, + ): + cv.no_yaml_config_schema("test_domain")({"test_domain": {"foo": "bar"}}) + expected_message = ( + "The test_domain integration does not support YAML setup, please remove " + "it from your configuration" + ) + assert expected_message in caplog.text + issue_registry = ir.async_get(hass) + assert not issue_registry.issues