From 7d9014ae417ed4fc0ef5785f407da3204ce9568d Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Fri, 20 Oct 2023 12:09:52 +0200 Subject: [PATCH] Do not fail MQTT setup if events or sensors configured via yaml can't be validated (#102309) * Add event and sensor * Cleanup unused code * Schema cannot be None for supported platform --- homeassistant/components/mqtt/__init__.py | 3 +-- .../components/mqtt/config_integration.py | 14 ++-------- homeassistant/components/mqtt/event.py | 27 +++++++------------ homeassistant/components/mqtt/mixins.py | 25 +---------------- homeassistant/components/mqtt/sensor.py | 27 +++++++------------ tests/components/mqtt/test_common.py | 10 ++++--- tests/components/mqtt/test_event.py | 13 +++++---- tests/components/mqtt/test_sensor.py | 20 ++++++-------- 8 files changed, 44 insertions(+), 95 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 3d3bb486c02..9f3fe6ef72b 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -235,8 +235,7 @@ async def async_check_config_schema( mqtt_config: list[dict[str, list[ConfigType]]] = config_yaml[DOMAIN] for mqtt_config_item in mqtt_config: for domain, config_items in mqtt_config_item.items(): - if (schema := mqtt_data.reload_schema.get(domain)) is None: - continue + schema = mqtt_data.reload_schema[domain] for config in config_items: try: schema(config) diff --git a/homeassistant/components/mqtt/config_integration.py b/homeassistant/components/mqtt/config_integration.py index 3eca9a12e87..71260dc0239 100644 --- a/homeassistant/components/mqtt/config_integration.py +++ b/homeassistant/components/mqtt/config_integration.py @@ -14,10 +14,6 @@ from homeassistant.const import ( ) from homeassistant.helpers import config_validation as cv -from . import ( - event as event_platform, - sensor as sensor_platform, -) from .const import ( CONF_BIRTH_MESSAGE, CONF_BROKER, @@ -41,10 +37,7 @@ CONFIG_SCHEMA_BASE = vol.Schema( Platform.CLIMATE.value: vol.All(cv.ensure_list, [dict]), Platform.COVER.value: vol.All(cv.ensure_list, [dict]), Platform.DEVICE_TRACKER.value: vol.All(cv.ensure_list, [dict]), - Platform.EVENT.value: vol.All( - cv.ensure_list, - [event_platform.PLATFORM_SCHEMA_MODERN], # type: ignore[has-type] - ), + Platform.EVENT.value: vol.All(cv.ensure_list, [dict]), Platform.FAN.value: vol.All(cv.ensure_list, [dict]), Platform.HUMIDIFIER.value: vol.All(cv.ensure_list, [dict]), Platform.IMAGE.value: vol.All(cv.ensure_list, [dict]), @@ -54,10 +47,7 @@ CONFIG_SCHEMA_BASE = vol.Schema( Platform.NUMBER.value: vol.All(cv.ensure_list, [dict]), Platform.SCENE.value: vol.All(cv.ensure_list, [dict]), Platform.SELECT.value: vol.All(cv.ensure_list, [dict]), - Platform.SENSOR.value: vol.All( - cv.ensure_list, - [sensor_platform.PLATFORM_SCHEMA_MODERN], # type: ignore[has-type] - ), + Platform.SENSOR.value: vol.All(cv.ensure_list, [dict]), Platform.SIREN.value: vol.All(cv.ensure_list, [dict]), Platform.SWITCH.value: vol.All(cv.ensure_list, [dict]), Platform.TEXT.value: vol.All(cv.ensure_list, [dict]), diff --git a/homeassistant/components/mqtt/event.py b/homeassistant/components/mqtt/event.py index c345655eea5..39057314508 100644 --- a/homeassistant/components/mqtt/event.py +++ b/homeassistant/components/mqtt/event.py @@ -2,7 +2,6 @@ from __future__ import annotations from collections.abc import Callable -import functools import logging from typing import Any @@ -19,7 +18,7 @@ from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_VALUE_TEMPLAT from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.typing import ConfigType from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads_object from . import subscription @@ -35,7 +34,7 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, - async_setup_entry_helper, + async_mqtt_entry_helper, write_state_on_attr_change, ) from .models import ( @@ -83,21 +82,15 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up MQTT event through YAML and through MQTT discovery.""" - setup = functools.partial( - _async_setup_entity, hass, async_add_entities, config_entry=config_entry + await async_mqtt_entry_helper( + hass, + config_entry, + MqttEvent, + event.DOMAIN, + async_add_entities, + DISCOVERY_SCHEMA, + PLATFORM_SCHEMA_MODERN, ) - await async_setup_entry_helper(hass, event.DOMAIN, setup, DISCOVERY_SCHEMA) - - -async def _async_setup_entity( - hass: HomeAssistant, - async_add_entities: AddEntitiesCallback, - config: ConfigType, - config_entry: ConfigEntry, - discovery_data: DiscoveryInfoType | None = None, -) -> None: - """Set up MQTT event.""" - async_add_entities([MqttEvent(hass, config, config_entry, discovery_data)]) class MqttEvent(MqttEntity, EventEntity): diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index ddc2703d820..2b86d8b3e87 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -2,7 +2,6 @@ from __future__ import annotations from abc import ABC, abstractmethod -import asyncio from collections.abc import Callable, Coroutine import functools from functools import partial, wraps @@ -312,7 +311,7 @@ async def async_setup_entry_helper( async_setup: partial[Coroutine[Any, Any, None]], discovery_schema: vol.Schema, ) -> None: - """Set up entity, automation or tag creation dynamically through MQTT discovery.""" + """Set up automation or tag creation dynamically through MQTT discovery.""" mqtt_data = get_mqtt_data(hass) async def async_setup_from_discovery( @@ -332,28 +331,6 @@ async def async_setup_entry_helper( ) ) - # The setup of manual configured MQTT entities will be migrated to async_mqtt_entry_helper. - # The following setup code will be cleaned up after the last entity platform has been migrated. - async def _async_setup_entities() -> None: - """Set up MQTT items from configuration.yaml.""" - mqtt_data = get_mqtt_data(hass) - if not (config_yaml := mqtt_data.config): - return - setups: list[Coroutine[Any, Any, None]] = [ - async_setup(config) - for config_item in config_yaml - for config_domain, configs in config_item.items() - for config in configs - if config_domain == domain - ] - if not setups: - return - await asyncio.gather(*setups) - - # discover manual configured MQTT items - mqtt_data.reload_handlers[domain] = _async_setup_entities - await _async_setup_entities() - async def async_mqtt_entry_helper( hass: HomeAssistant, diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 0f73b93f1de..a4a59de4dfb 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Callable from datetime import datetime, timedelta -import functools import logging from typing import Any @@ -33,7 +32,7 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant, State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util from . import subscription @@ -44,7 +43,7 @@ from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, - async_setup_entry_helper, + async_mqtt_entry_helper, write_state_on_attr_change, ) from .models import ( @@ -106,21 +105,15 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up MQTT sensor through YAML and through MQTT discovery.""" - setup = functools.partial( - _async_setup_entity, hass, async_add_entities, config_entry=config_entry + await async_mqtt_entry_helper( + hass, + config_entry, + MqttSensor, + sensor.DOMAIN, + async_add_entities, + DISCOVERY_SCHEMA, + PLATFORM_SCHEMA_MODERN, ) - await async_setup_entry_helper(hass, sensor.DOMAIN, setup, DISCOVERY_SCHEMA) - - -async def _async_setup_entity( - hass: HomeAssistant, - async_add_entities: AddEntitiesCallback, - config: ConfigType, - config_entry: ConfigEntry, - discovery_data: DiscoveryInfoType | None = None, -) -> None: - """Set up MQTT sensor.""" - async_add_entities([MqttSensor(hass, config, config_entry, discovery_data)]) class MqttSensor(MqttEntity, RestoreSensor): diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 7af7aa34647..0664f6e8d6f 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -9,6 +9,7 @@ from typing import Any from unittest.mock import ANY, MagicMock, patch import pytest +import voluptuous as vol import yaml from homeassistant import config as module_hass_config @@ -363,6 +364,7 @@ async def help_test_default_availability_list_payload_any( async def help_test_default_availability_list_single( hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, caplog: pytest.LogCaptureFixture, domain: str, config: ConfigType, @@ -378,10 +380,10 @@ async def help_test_default_availability_list_single( ] config[mqtt.DOMAIN][domain]["availability_topic"] = "availability-topic" - entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) - entry.add_to_hass(hass) - with patch("homeassistant.config.load_yaml_config_file", return_value=config): - await entry.async_setup(hass) + with patch( + "homeassistant.config.load_yaml_config_file", return_value=config + ), suppress(vol.MultipleInvalid): + await mqtt_mock_entry() assert ( "two or more values in the same group of exclusion 'availability'" diff --git a/tests/components/mqtt/test_event.py b/tests/components/mqtt/test_event.py index 37a17ac9a41..4c0e63fec1f 100644 --- a/tests/components/mqtt/test_event.py +++ b/tests/components/mqtt/test_event.py @@ -224,11 +224,13 @@ async def test_default_availability_list_payload_any( async def test_default_availability_list_single( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, ) -> None: """Test availability list and availability_topic are mutually exclusive.""" await help_test_default_availability_list_single( - hass, caplog, event.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock_entry, caplog, event.DOMAIN, DEFAULT_CONFIG ) @@ -271,11 +273,8 @@ async def test_invalid_device_class( caplog: pytest.LogCaptureFixture, ) -> None: """Test device_class option with invalid value.""" - with pytest.raises(AssertionError): - await mqtt_mock_entry() - assert ( - "Invalid config for [mqtt]: expected EventDeviceClass or one of" in caplog.text - ) + assert await mqtt_mock_entry() + assert "expected EventDeviceClass or one of" in caplog.text async def test_setting_attribute_via_mqtt_json_message( diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 06967b7f8a8..0f1be02875c 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -708,11 +708,13 @@ async def test_default_availability_list_payload_any( async def test_default_availability_list_single( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, ) -> None: """Test availability list and availability_topic are mutually exclusive.""" await help_test_default_availability_list_single( - hass, caplog, sensor.DOMAIN, DEFAULT_CONFIG + hass, mqtt_mock_entry, caplog, sensor.DOMAIN, DEFAULT_CONFIG ) @@ -754,11 +756,8 @@ async def test_invalid_device_class( caplog: pytest.LogCaptureFixture, ) -> None: """Test device_class option with invalid value.""" - with pytest.raises(AssertionError): - await mqtt_mock_entry() - assert ( - "Invalid config for [mqtt]: expected SensorDeviceClass or one of" in caplog.text - ) + assert await mqtt_mock_entry() + assert "expected SensorDeviceClass or one of" in caplog.text @pytest.mark.parametrize( @@ -818,11 +817,8 @@ async def test_invalid_state_class( caplog: pytest.LogCaptureFixture, ) -> None: """Test state_class option with invalid value.""" - with pytest.raises(AssertionError): - await mqtt_mock_entry() - assert ( - "Invalid config for [mqtt]: expected SensorStateClass or one of" in caplog.text - ) + assert await mqtt_mock_entry() + assert "expected SensorStateClass or one of" in caplog.text @pytest.mark.parametrize(