From 95f7db197039efa257b811efea2ea27a277477fe Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 4 Dec 2023 12:48:49 +0100 Subject: [PATCH] Move config_per_platform and extract_domain_configs to config.py (#104989) --- homeassistant/components/automation/config.py | 4 +- .../components/device_tracker/legacy.py | 7 ++- .../components/homeassistant/scene.py | 8 +-- homeassistant/components/mailbox/__init__.py | 7 +-- homeassistant/components/notify/legacy.py | 3 +- homeassistant/components/script/config.py | 4 +- homeassistant/components/stt/legacy.py | 3 +- homeassistant/components/tts/legacy.py | 3 +- homeassistant/config.py | 45 ++++++++++++++--- homeassistant/helpers/__init__.py | 49 ++++++++++++------- homeassistant/helpers/entity_component.py | 4 +- homeassistant/helpers/reload.py | 3 +- tests/helpers/test_init.py | 16 +++++- tests/test_config.py | 33 +++++++++++++ 14 files changed, 138 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index ed801772e6d..ff0fe43ea26 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -11,7 +11,7 @@ from voluptuous.humanize import humanize_error from homeassistant.components import blueprint from homeassistant.components.trace import TRACE_CONFIG_SCHEMA -from homeassistant.config import config_without_domain +from homeassistant.config import config_per_platform, config_without_domain from homeassistant.const import ( CONF_ALIAS, CONF_CONDITION, @@ -21,7 +21,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_per_platform, config_validation as cv, script +from homeassistant.helpers import config_validation as cv, script from homeassistant.helpers.condition import async_validate_conditions_config from homeassistant.helpers.trigger import async_validate_trigger_config from homeassistant.helpers.typing import ConfigType diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index f18f7984e1e..5f2a3c3ba52 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -14,7 +14,11 @@ import voluptuous as vol from homeassistant import util from homeassistant.backports.functools import cached_property from homeassistant.components import zone -from homeassistant.config import async_log_schema_error, load_yaml_config_file +from homeassistant.config import ( + async_log_schema_error, + config_per_platform, + load_yaml_config_file, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_GPS_ACCURACY, @@ -33,7 +37,6 @@ from homeassistant.const import ( from homeassistant.core import Event, HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import ( - config_per_platform, config_validation as cv, discovery, entity_registry as er, diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 3308083f22f..9abfefc996f 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -30,11 +30,7 @@ from homeassistant.core import ( callback, ) from homeassistant.exceptions import HomeAssistantError, ServiceValidationError -from homeassistant.helpers import ( - config_per_platform, - config_validation as cv, - entity_platform, -) +from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback, EntityPlatform from homeassistant.helpers.service import ( async_extract_entity_ids, @@ -208,7 +204,7 @@ async def async_setup_platform( await platform.async_reset() # Extract only the config for the Home Assistant platform, ignore the rest. - for p_type, p_config in config_per_platform(conf, SCENE_DOMAIN): + for p_type, p_config in conf_util.config_per_platform(conf, SCENE_DOMAIN): if p_type != HA_DOMAIN: continue diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index 679abfd3164..623d0f06295 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -13,13 +13,10 @@ from aiohttp.web_exceptions import HTTPNotFound from homeassistant.components import frontend from homeassistant.components.http import HomeAssistantView +from homeassistant.config import config_per_platform from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import ( - config_per_platform, - config_validation as cv, - discovery, -) +from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType diff --git a/homeassistant/components/notify/legacy.py b/homeassistant/components/notify/legacy.py index 110671864e3..30981cd3658 100644 --- a/homeassistant/components/notify/legacy.py +++ b/homeassistant/components/notify/legacy.py @@ -6,10 +6,11 @@ from collections.abc import Callable, Coroutine, Mapping from functools import partial from typing import Any, Protocol, cast +from homeassistant.config import config_per_platform from homeassistant.const import CONF_DESCRIPTION, CONF_NAME from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_per_platform, discovery +from homeassistant.helpers import discovery from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType diff --git a/homeassistant/components/script/config.py b/homeassistant/components/script/config.py index c11bb37294f..1cbab23d843 100644 --- a/homeassistant/components/script/config.py +++ b/homeassistant/components/script/config.py @@ -13,7 +13,7 @@ from homeassistant.components.blueprint import ( is_blueprint_instance_config, ) from homeassistant.components.trace import TRACE_CONFIG_SCHEMA -from homeassistant.config import config_without_domain +from homeassistant.config import config_per_platform, config_without_domain from homeassistant.const import ( CONF_ALIAS, CONF_DEFAULT, @@ -30,7 +30,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_per_platform, config_validation as cv +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.script import ( SCRIPT_MODE_SINGLE, async_validate_actions_config, diff --git a/homeassistant/components/stt/legacy.py b/homeassistant/components/stt/legacy.py index 862f59d5f6d..cd5aef312ce 100644 --- a/homeassistant/components/stt/legacy.py +++ b/homeassistant/components/stt/legacy.py @@ -6,8 +6,9 @@ from collections.abc import AsyncIterable, Coroutine import logging from typing import Any +from homeassistant.config import config_per_platform from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import config_per_platform, discovery +from homeassistant.helpers import discovery from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_prepare_setup_platform diff --git a/homeassistant/components/tts/legacy.py b/homeassistant/components/tts/legacy.py index 4734c3f22d1..a52bcb802ab 100644 --- a/homeassistant/components/tts/legacy.py +++ b/homeassistant/components/tts/legacy.py @@ -18,6 +18,7 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA, MediaType, ) +from homeassistant.config import config_per_platform from homeassistant.const import ( ATTR_ENTITY_ID, CONF_DESCRIPTION, @@ -25,7 +26,7 @@ from homeassistant.const import ( CONF_PLATFORM, ) from homeassistant.core import HomeAssistant, ServiceCall, callback -from homeassistant.helpers import config_per_platform, discovery +from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType diff --git a/homeassistant/config.py b/homeassistant/config.py index b4850e372fd..5d5d246884c 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections import OrderedDict -from collections.abc import Callable, Sequence +from collections.abc import Callable, Iterable, Sequence from contextlib import suppress from dataclasses import dataclass from enum import StrEnum @@ -48,6 +48,7 @@ from .const import ( CONF_MEDIA_DIRS, CONF_NAME, CONF_PACKAGES, + CONF_PLATFORM, CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, CONF_TYPE, @@ -58,12 +59,7 @@ from .const import ( from .core import DOMAIN as CONF_CORE, ConfigSource, HomeAssistant, callback from .exceptions import ConfigValidationError, HomeAssistantError from .generated.currencies import HISTORIC_CURRENCIES -from .helpers import ( - config_per_platform, - config_validation as cv, - extract_domain_configs, - issue_registry as ir, -) +from .helpers import config_validation as cv, issue_registry as ir from .helpers.entity_values import EntityValues from .helpers.typing import ConfigType from .loader import ComponentProtocol, Integration, IntegrationNotFound @@ -1222,6 +1218,41 @@ def async_handle_component_errors( ) +def config_per_platform( + config: ConfigType, domain: str +) -> Iterable[tuple[str | None, ConfigType]]: + """Break a component config into different platforms. + + For example, will find 'switch', 'switch 2', 'switch 3', .. etc + Async friendly. + """ + for config_key in extract_domain_configs(config, domain): + if not (platform_config := config[config_key]): + continue + + if not isinstance(platform_config, list): + platform_config = [platform_config] + + item: ConfigType + platform: str | None + for item in platform_config: + try: + platform = item.get(CONF_PLATFORM) + except AttributeError: + platform = None + + yield platform, item + + +def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: + """Extract keys from config for given domain name. + + Async friendly. + """ + pattern = re.compile(rf"^{domain}(| .+)$") + return [key for key in config if pattern.match(key)] + + async def async_process_component_config( # noqa: C901 hass: HomeAssistant, config: ConfigType, diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index c9acdf0d712..52197e83495 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -2,11 +2,8 @@ from __future__ import annotations from collections.abc import Iterable, Sequence -import re from typing import TYPE_CHECKING -from homeassistant.const import CONF_PLATFORM - if TYPE_CHECKING: from .typing import ConfigType @@ -19,22 +16,23 @@ def config_per_platform( For example, will find 'switch', 'switch 2', 'switch 3', .. etc Async friendly. """ - for config_key in extract_domain_configs(config, domain): - if not (platform_config := config[config_key]): - continue + # pylint: disable-next=import-outside-toplevel + from homeassistant import config as ha_config - if not isinstance(platform_config, list): - platform_config = [platform_config] + # pylint: disable-next=import-outside-toplevel + from .deprecation import _print_deprecation_warning - item: ConfigType - platform: str | None - for item in platform_config: - try: - platform = item.get(CONF_PLATFORM) - except AttributeError: - platform = None + _print_deprecation_warning( + config_per_platform, + "config.config_per_platform", + "function", + "called", + "2024.6", + ) + return ha_config.config_per_platform(config, domain) - yield platform, item + +config_per_platform.__name__ = "helpers.config_per_platform" def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: @@ -42,5 +40,20 @@ def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: Async friendly. """ - pattern = re.compile(rf"^{domain}(| .+)$") - return [key for key in config if pattern.match(key)] + # pylint: disable-next=import-outside-toplevel + from homeassistant import config as ha_config + + # pylint: disable-next=import-outside-toplevel + from .deprecation import _print_deprecation_warning + + _print_deprecation_warning( + extract_domain_configs, + "config.extract_domain_configs", + "function", + "called", + "2024.6", + ) + return ha_config.extract_domain_configs(config, domain) + + +extract_domain_configs.__name__ = "helpers.extract_domain_configs" diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 775d0934c36..30e892a8840 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -32,7 +32,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import async_get_integration, bind_hass from homeassistant.setup import async_prepare_setup_platform -from . import config_per_platform, config_validation as cv, discovery, entity, service +from . import config_validation as cv, discovery, entity, service from .entity_platform import EntityPlatform from .typing import ConfigType, DiscoveryInfoType @@ -148,7 +148,7 @@ class EntityComponent(Generic[_EntityT]): self.config = config # Look in config for Domain, Domain 2, Domain 3 etc and load them - for p_type, p_config in config_per_platform(config, self.domain): + for p_type, p_config in conf_util.config_per_platform(config, self.domain): if p_type is not None: self.hass.async_create_task( self.async_setup_platform(p_type, p_config), diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index 42ebc2d0869..983b4e2da52 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -13,7 +13,6 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component -from . import config_per_platform from .entity import Entity from .entity_component import EntityComponent from .entity_platform import EntityPlatform, async_get_platforms @@ -69,7 +68,7 @@ async def _resetup_platform( root_config: dict[str, list[ConfigType]] = {platform_domain: []} # Extract only the config for template, ignore the rest. - for p_type, p_config in config_per_platform(conf, platform_domain): + for p_type, p_config in conf_util.config_per_platform(conf, platform_domain): if p_type != integration_domain: continue diff --git a/tests/helpers/test_init.py b/tests/helpers/test_init.py index c567c6bc7bc..39b387000ca 100644 --- a/tests/helpers/test_init.py +++ b/tests/helpers/test_init.py @@ -2,10 +2,12 @@ from collections import OrderedDict +import pytest + from homeassistant import helpers -def test_extract_domain_configs() -> None: +def test_extract_domain_configs(caplog: pytest.LogCaptureFixture) -> None: """Test the extraction of domain configuration.""" config = { "zone": None, @@ -19,8 +21,13 @@ def test_extract_domain_configs() -> None: helpers.extract_domain_configs(config, "zone") ) + assert ( + "helpers.extract_domain_configs is a deprecated function which will be removed " + "in HA Core 2024.6. Use config.extract_domain_configs instead" in caplog.text + ) -def test_config_per_platform() -> None: + +def test_config_per_platform(caplog: pytest.LogCaptureFixture) -> None: """Test config per platform method.""" config = OrderedDict( [ @@ -36,3 +43,8 @@ def test_config_per_platform() -> None: (None, 1), ("hello 2", config["zone Hallo"][1]), ] == list(helpers.config_per_platform(config, "zone")) + + assert ( + "helpers.config_per_platform is a deprecated function which will be removed " + "in HA Core 2024.6. Use config.config_per_platform instead" in caplog.text + ) diff --git a/tests/test_config.py b/tests/test_config.py index de5e7e0581d..1e309e2908f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2207,3 +2207,36 @@ async def test_yaml_error( if record.levelno == logging.ERROR ] assert error_records == snapshot + + +def test_extract_domain_configs() -> None: + """Test the extraction of domain configuration.""" + config = { + "zone": None, + "zoner": None, + "zone ": None, + "zone Hallo": None, + "zone 100": None, + } + + assert {"zone", "zone Hallo", "zone 100"} == set( + config_util.extract_domain_configs(config, "zone") + ) + + +def test_config_per_platform() -> None: + """Test config per platform method.""" + config = OrderedDict( + [ + ("zone", {"platform": "hello"}), + ("zoner", None), + ("zone Hallo", [1, {"platform": "hello 2"}]), + ("zone 100", None), + ] + ) + + assert [ + ("hello", config["zone"]), + (None, 1), + ("hello 2", config["zone Hallo"][1]), + ] == list(config_util.config_per_platform(config, "zone"))