diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index be6aa5072d8..a45de7b0c16 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -1,12 +1,13 @@ """The dhcp integration.""" +from dataclasses import dataclass from datetime import timedelta import fnmatch from ipaddress import ip_address as make_ip_address import logging import os import threading -from typing import Final, TypedDict +from typing import Any, Final from aiodiscover import DiscoverHosts from aiodiscover.discovery import ( @@ -32,12 +33,14 @@ from homeassistant.const import ( STATE_HOME, ) from homeassistant.core import Event, HomeAssistant, State, callback +from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.helpers import discovery_flow from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.event import ( async_track_state_added_domain, async_track_time_interval, ) +from homeassistant.helpers.frame import report from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_dhcp from homeassistant.util.async_ import run_callback_threadsafe @@ -55,13 +58,33 @@ SCAN_INTERVAL = timedelta(minutes=60) _LOGGER = logging.getLogger(__name__) -class DhcpServiceInfo(TypedDict): +@dataclass +class DhcpServiceInfo(BaseServiceInfo): """Prepared info from dhcp entries.""" - ip: str + ip: str # pylint: disable=invalid-name hostname: str macaddress: str + # Used to prevent log flooding. To be removed in 2022.6 + _warning_logged: bool = False + + def __getitem__(self, name: str) -> Any: + """ + Allow property access by name for compatibility reason. + + Deprecated, and will be removed in version 2022.6. + """ + if not self._warning_logged: + report( + f"accessed discovery_info['{name}'] instead of discovery_info.{name}; this will fail in version 2022.6", + exclude_integrations={"dhcp"}, + error_if_core=False, + level=logging.DEBUG, + ) + self._warning_logged = True + return getattr(self, name) + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the dhcp component.""" diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index ae5feb3d198..e84689ee269 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -4,6 +4,7 @@ from __future__ import annotations import abc import asyncio from collections.abc import Iterable, Mapping +from dataclasses import dataclass from types import MappingProxyType from typing import Any, TypedDict import uuid @@ -25,6 +26,11 @@ RESULT_TYPE_SHOW_PROGRESS_DONE = "progress_done" EVENT_DATA_ENTRY_FLOW_PROGRESSED = "data_entry_flow_progressed" +@dataclass +class BaseServiceInfo: + """Base class for discovery ServiceInfo.""" + + class FlowError(HomeAssistantError): """Error while configuring an account.""" @@ -301,7 +307,7 @@ class FlowManager(abc.ABC): self, flow: Any, step_id: str, - user_input: dict | None, + user_input: dict | BaseServiceInfo | None, step_done: asyncio.Future | None = None, ) -> FlowResult: """Handle a step of a flow.""" diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index 8105c4f6c0e..3995f24102d 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -51,7 +51,10 @@ class MissingIntegrationFrame(HomeAssistantError): def report( - what: str, exclude_integrations: set | None = None, error_if_core: bool = True + what: str, + exclude_integrations: set | None = None, + error_if_core: bool = True, + level: int = logging.WARNING, ) -> None: """Report incorrect usage. @@ -68,11 +71,13 @@ def report( _LOGGER.warning(msg, stack_info=True) return - report_integration(what, integration_frame) + report_integration(what, integration_frame, level) def report_integration( - what: str, integration_frame: tuple[FrameSummary, str, str] + what: str, + integration_frame: tuple[FrameSummary, str, str], + level: int = logging.WARNING, ) -> None: """Report incorrect usage in an integration. @@ -86,7 +91,8 @@ def report_integration( else: extra = "" - _LOGGER.warning( + _LOGGER.log( + level, "Detected integration that %s. " "Please report issue%s for %s using this method at %s, line %s: %s", what, diff --git a/tests/components/gogogate2/test_config_flow.py b/tests/components/gogogate2/test_config_flow.py index a70959cfd09..9b4f64fa903 100644 --- a/tests/components/gogogate2/test_config_flow.py +++ b/tests/components/gogogate2/test_config_flow.py @@ -191,7 +191,9 @@ async def test_discovered_dhcp( result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip="1.2.3.4", macaddress=MOCK_MAC_ADDR), + data=dhcp.DhcpServiceInfo( + ip="1.2.3.4", macaddress=MOCK_MAC_ADDR, hostname="mock_hostname" + ), ) assert result["type"] == RESULT_TYPE_FORM assert result["errors"] == {} @@ -246,7 +248,9 @@ async def test_discovered_by_homekit_and_dhcp(hass): result2 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip="1.2.3.4", macaddress=MOCK_MAC_ADDR), + data=dhcp.DhcpServiceInfo( + ip="1.2.3.4", macaddress=MOCK_MAC_ADDR, hostname="mock_hostname" + ), ) assert result2["type"] == RESULT_TYPE_ABORT assert result2["reason"] == "already_in_progress" @@ -254,7 +258,9 @@ async def test_discovered_by_homekit_and_dhcp(hass): result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip="1.2.3.4", macaddress="00:00:00:00:00:00"), + data=dhcp.DhcpServiceInfo( + ip="1.2.3.4", macaddress="00:00:00:00:00:00", hostname="mock_hostname" + ), ) assert result3["type"] == RESULT_TYPE_ABORT assert result3["reason"] == "already_in_progress" diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py index 1631cd2cb60..ec82cc0c4d1 100644 --- a/tests/components/hunterdouglas_powerview/test_config_flow.py +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -23,7 +23,9 @@ ZEROCONF_DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( ) DHCP_DISCOVERY_INFO = dhcp.DhcpServiceInfo( - hostname="Hunter Douglas Powerview Hub", ip="1.2.3.4" + hostname="Hunter Douglas Powerview Hub", + ip="1.2.3.4", + macaddress="AA:BB:CC:DD:EE:FF", ) DISCOVERY_DATA = [ diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 432596eeca2..79420d6f303 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -84,7 +84,9 @@ MOCK_SSDP_DATA_WRONGMODEL = { ATTR_UPNP_MODEL_NAME: "HW-Qfake", ATTR_UPNP_UDN: "uuid:0d1cef00-00dc-1000-9c80-4844f7b172df", } -MOCK_DHCP_DATA = dhcp.DhcpServiceInfo(ip="fake_host", macaddress="aa:bb:cc:dd:ee:ff") +MOCK_DHCP_DATA = dhcp.DhcpServiceInfo( + ip="fake_host", macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname" +) EXISTING_IP = "192.168.40.221" MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo( host="fake_host", @@ -1131,7 +1133,9 @@ async def test_update_legacy_missing_mac_from_dhcp(hass, remote: Mock): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff"), + data=dhcp.DhcpServiceInfo( + ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname" + ), ) await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 @@ -1165,7 +1169,9 @@ async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(hass, remote: Mo result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff"), + data=dhcp.DhcpServiceInfo( + ip=EXISTING_IP, macaddress="aa:bb:cc:dd:ee:ff", hostname="fake_hostname" + ), ) await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 diff --git a/tests/components/screenlogic/test_config_flow.py b/tests/components/screenlogic/test_config_flow.py index 9a6715bc401..375bcddac24 100644 --- a/tests/components/screenlogic/test_config_flow.py +++ b/tests/components/screenlogic/test_config_flow.py @@ -141,6 +141,7 @@ async def test_dhcp(hass): data=dhcp.DhcpServiceInfo( hostname="Pentair: 01-01-01", ip="1.1.1.1", + macaddress="AA:BB:CC:DD:EE:FF", ), ) diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index d19b47ffd86..5a4e672a384 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -321,7 +321,9 @@ async def test_discovered_by_discovery_and_dhcp(hass): result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress="00:00:00:00:00:00"), + data=dhcp.DhcpServiceInfo( + ip=IP_ADDRESS, macaddress="00:00:00:00:00:00", hostname="mock_hostname" + ), ) await hass.async_block_till_done() assert result3["type"] == RESULT_TYPE_ABORT @@ -331,7 +333,9 @@ async def test_discovered_by_discovery_and_dhcp(hass): result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip="1.2.3.5", macaddress="00:00:00:00:00:01"), + data=dhcp.DhcpServiceInfo( + ip="1.2.3.5", macaddress="00:00:00:00:00:01", hostname="mock_hostname" + ), ) await hass.async_block_till_done() assert result3["type"] == RESULT_TYPE_ABORT diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py index 41cabb976c9..356cd8fd63c 100644 --- a/tests/components/verisure/test_config_flow.py +++ b/tests/components/verisure/test_config_flow.py @@ -176,7 +176,9 @@ async def test_dhcp(hass: HomeAssistant) -> None: """Test that DHCP discovery works.""" result = await hass.config_entries.flow.async_init( DOMAIN, - data=dhcp.DhcpServiceInfo(macaddress="01:23:45:67:89:ab"), + data=dhcp.DhcpServiceInfo( + ip="1.2.3.4", macaddress="01:23:45:67:89:ab", hostname="mock_hostname" + ), context={"source": config_entries.SOURCE_DHCP}, ) diff --git a/tests/components/yeelight/test_config_flow.py b/tests/components/yeelight/test_config_flow.py index b3466fb2525..cc2a3600337 100644 --- a/tests/components/yeelight/test_config_flow.py +++ b/tests/components/yeelight/test_config_flow.py @@ -471,7 +471,9 @@ async def test_discovered_by_homekit_and_dhcp(hass): result2 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress="aa:bb:cc:dd:ee:ff"), + data=dhcp.DhcpServiceInfo( + ip=IP_ADDRESS, macaddress="aa:bb:cc:dd:ee:ff", hostname="mock_hostname" + ), ) await hass.async_block_till_done() assert result2["type"] == RESULT_TYPE_ABORT @@ -483,7 +485,9 @@ async def test_discovered_by_homekit_and_dhcp(hass): result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress="00:00:00:00:00:00"), + data=dhcp.DhcpServiceInfo( + ip=IP_ADDRESS, macaddress="00:00:00:00:00:00", hostname="mock_hostname" + ), ) await hass.async_block_till_done() assert result3["type"] == RESULT_TYPE_ABORT @@ -497,7 +501,9 @@ async def test_discovered_by_homekit_and_dhcp(hass): result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data=dhcp.DhcpServiceInfo(ip="1.2.3.5", macaddress="00:00:00:00:00:01"), + data=dhcp.DhcpServiceInfo( + ip="1.2.3.5", macaddress="00:00:00:00:00:01", hostname="mock_hostname" + ), ) await hass.async_block_till_done() assert result3["type"] == RESULT_TYPE_ABORT @@ -509,7 +515,9 @@ async def test_discovered_by_homekit_and_dhcp(hass): [ ( config_entries.SOURCE_DHCP, - dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress="aa:bb:cc:dd:ee:ff"), + dhcp.DhcpServiceInfo( + ip=IP_ADDRESS, macaddress="aa:bb:cc:dd:ee:ff", hostname="mock_hostname" + ), ), ( config_entries.SOURCE_HOMEKIT, @@ -570,7 +578,9 @@ async def test_discovered_by_dhcp_or_homekit(hass, source, data): [ ( config_entries.SOURCE_DHCP, - dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress="aa:bb:cc:dd:ee:ff"), + dhcp.DhcpServiceInfo( + ip=IP_ADDRESS, macaddress="aa:bb:cc:dd:ee:ff", hostname="mock_hostname" + ), ), ( config_entries.SOURCE_HOMEKIT,