From 666f715e76faf1061aed7cb4ec59611b7b2f4455 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Jul 2022 18:09:09 +0200 Subject: [PATCH] Avoid importing MQTT into core for ServiceInfo dataclass (#74418) * Avoid importing MQTT into core for discovery dataclass Likely fixes #73863 * relo * adjust * rename * rename * rename * adjust missed imports * drop compat * fix conflict correctly * Update homeassistant/helpers/config_entry_flow.py * fix black from trying to fix the conflict in github --- homeassistant/components/mqtt/__init__.py | 15 -------------- homeassistant/components/mqtt/discovery.py | 3 ++- homeassistant/components/mqtt/models.py | 2 +- .../components/tasmota/config_flow.py | 3 ++- homeassistant/config_entries.py | 2 +- homeassistant/helpers/config_entry_flow.py | 20 +++++++++++-------- homeassistant/helpers/service_info/mqtt.py | 20 +++++++++++++++++++ tests/components/tasmota/test_config_flow.py | 10 +++++----- 8 files changed, 43 insertions(+), 32 deletions(-) create mode 100644 homeassistant/helpers/service_info/mqtt.py diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 906923138b9..394e4af3e2e 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -3,8 +3,6 @@ from __future__ import annotations import asyncio from collections.abc import Callable -from dataclasses import dataclass -import datetime as dt import logging from typing import Any, cast @@ -23,7 +21,6 @@ from homeassistant.const import ( SERVICE_RELOAD, ) from homeassistant.core import HassJob, HomeAssistant, ServiceCall, callback -from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.exceptions import TemplateError, Unauthorized from homeassistant.helpers import config_validation as cv, event, template from homeassistant.helpers.device_registry import DeviceEntry @@ -145,18 +142,6 @@ MQTT_PUBLISH_SCHEMA = vol.All( ) -@dataclass -class MqttServiceInfo(BaseServiceInfo): - """Prepared info from mqtt entries.""" - - topic: str - payload: ReceivePayloadType - qos: int - retain: bool - subscribed_topic: str - timestamp: dt.datetime - - async def _async_setup_discovery( hass: HomeAssistant, conf: ConfigType, config_entry ) -> None: diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index a480cdd5680..ebc3a170aeb 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -17,6 +17,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, ) from homeassistant.helpers.json import json_loads +from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from homeassistant.loader import async_get_mqtt from .. import mqtt @@ -267,7 +268,7 @@ async def async_start( # noqa: C901 if key not in hass.data[INTEGRATION_UNSUBSCRIBE]: return - data = mqtt.MqttServiceInfo( + data = MqttServiceInfo( topic=msg.topic, payload=msg.payload, qos=msg.qos, diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 9bce6baab8b..d5560f6954e 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -12,12 +12,12 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import template from homeassistant.helpers.entity import Entity +from homeassistant.helpers.service_info.mqtt import ReceivePayloadType from homeassistant.helpers.typing import TemplateVarsType _SENTINEL = object() PublishPayloadType = Union[str, bytes, int, float, None] -ReceivePayloadType = Union[str, bytes] @attr.s(slots=True, frozen=True) diff --git a/homeassistant/components/tasmota/config_flow.py b/homeassistant/components/tasmota/config_flow.py index e2109a16afe..d8981090d58 100644 --- a/homeassistant/components/tasmota/config_flow.py +++ b/homeassistant/components/tasmota/config_flow.py @@ -6,8 +6,9 @@ from typing import Any import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.mqtt import MqttServiceInfo, valid_subscribe_topic +from homeassistant.components.mqtt import valid_subscribe_topic from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from .const import CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX, DOMAIN diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 4b76f63681a..6e2b70be7cc 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -31,10 +31,10 @@ if TYPE_CHECKING: from .components.bluetooth import BluetoothServiceInfo from .components.dhcp import DhcpServiceInfo from .components.hassio import HassioServiceInfo - from .components.mqtt import MqttServiceInfo from .components.ssdp import SsdpServiceInfo from .components.usb import UsbServiceInfo from .components.zeroconf import ZeroconfServiceInfo + from .helpers.service_info.mqtt import MqttServiceInfo _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 75a4dcd20f4..6ee04ea6d9b 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -6,7 +6,7 @@ import logging from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast from homeassistant import config_entries -from homeassistant.components import bluetooth, dhcp, onboarding, ssdp, zeroconf +from homeassistant.components import onboarding from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult @@ -15,8 +15,12 @@ from .typing import UNDEFINED, DiscoveryInfoType, UndefinedType if TYPE_CHECKING: import asyncio - from homeassistant.components import mqtt + from homeassistant.components.bluetooth import BluetoothServiceInfo + from homeassistant.components.dhcp import DhcpServiceInfo + from homeassistant.components.ssdp import SsdpServiceInfo + from homeassistant.components.zeroconf import ZeroconfServiceInfo + from .service_info.mqtt import MqttServiceInfo _R = TypeVar("_R", bound="Awaitable[bool] | bool") DiscoveryFunctionType = Callable[[HomeAssistant], _R] @@ -93,7 +97,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() async def async_step_bluetooth( - self, discovery_info: bluetooth.BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfo ) -> FlowResult: """Handle a flow initialized by bluetooth discovery.""" if self._async_in_progress() or self._async_current_entries(): @@ -103,7 +107,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: """Handle a flow initialized by dhcp discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -113,7 +117,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() async def async_step_homekit( - self, discovery_info: zeroconf.ZeroconfServiceInfo + self, discovery_info: ZeroconfServiceInfo ) -> FlowResult: """Handle a flow initialized by Homekit discovery.""" if self._async_in_progress() or self._async_current_entries(): @@ -123,7 +127,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() - async def async_step_mqtt(self, discovery_info: mqtt.MqttServiceInfo) -> FlowResult: + async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: """Handle a flow initialized by mqtt discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -133,7 +137,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() async def async_step_zeroconf( - self, discovery_info: zeroconf.ZeroconfServiceInfo + self, discovery_info: ZeroconfServiceInfo ) -> FlowResult: """Handle a flow initialized by Zeroconf discovery.""" if self._async_in_progress() or self._async_current_entries(): @@ -143,7 +147,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp(self, discovery_info: SsdpServiceInfo) -> FlowResult: """Handle a flow initialized by Ssdp discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/helpers/service_info/mqtt.py b/homeassistant/helpers/service_info/mqtt.py new file mode 100644 index 00000000000..fcf5d4744f1 --- /dev/null +++ b/homeassistant/helpers/service_info/mqtt.py @@ -0,0 +1,20 @@ +"""MQTT Discovery data.""" +from dataclasses import dataclass +import datetime as dt +from typing import Union + +from homeassistant.data_entry_flow import BaseServiceInfo + +ReceivePayloadType = Union[str, bytes] + + +@dataclass +class MqttServiceInfo(BaseServiceInfo): + """Prepared info from mqtt entries.""" + + topic: str + payload: ReceivePayloadType + qos: int + retain: bool + subscribed_topic: str + timestamp: dt.datetime diff --git a/tests/components/tasmota/test_config_flow.py b/tests/components/tasmota/test_config_flow.py index 3413817892b..77a2787c0d5 100644 --- a/tests/components/tasmota/test_config_flow.py +++ b/tests/components/tasmota/test_config_flow.py @@ -1,6 +1,6 @@ """Test config flow.""" from homeassistant import config_entries -from homeassistant.components import mqtt +from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from tests.common import MockConfigEntry @@ -19,7 +19,7 @@ async def test_mqtt_abort_if_existing_entry(hass, mqtt_mock): async def test_mqtt_abort_invalid_topic(hass, mqtt_mock): """Check MQTT flow aborts if discovery topic is invalid.""" - discovery_info = mqtt.MqttServiceInfo( + discovery_info = MqttServiceInfo( topic="tasmota/discovery/DC4F220848A2/bla", payload=( '{"ip":"192.168.0.136","dn":"Tasmota","fn":["Tasmota",null,null,null,null,' @@ -42,7 +42,7 @@ async def test_mqtt_abort_invalid_topic(hass, mqtt_mock): assert result["type"] == "abort" assert result["reason"] == "invalid_discovery_info" - discovery_info = mqtt.MqttServiceInfo( + discovery_info = MqttServiceInfo( topic="tasmota/discovery/DC4F220848A2/config", payload="", qos=0, @@ -56,7 +56,7 @@ async def test_mqtt_abort_invalid_topic(hass, mqtt_mock): assert result["type"] == "abort" assert result["reason"] == "invalid_discovery_info" - discovery_info = mqtt.MqttServiceInfo( + discovery_info = MqttServiceInfo( topic="tasmota/discovery/DC4F220848A2/config", payload=( '{"ip":"192.168.0.136","dn":"Tasmota","fn":["Tasmota",null,null,null,null,' @@ -81,7 +81,7 @@ async def test_mqtt_abort_invalid_topic(hass, mqtt_mock): async def test_mqtt_setup(hass, mqtt_mock) -> None: """Test we can finish a config flow through MQTT with custom prefix.""" - discovery_info = mqtt.MqttServiceInfo( + discovery_info = MqttServiceInfo( topic="tasmota/discovery/DC4F220848A2/config", payload=( '{"ip":"192.168.0.136","dn":"Tasmota","fn":["Tasmota",null,null,null,null,'