mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Enable strict typing for zeroconf (#48450)
* Enable strict typing for zeroconf * Fix lutron_caseta * Fix pylint warning * Fix tests * Fix xiaomi_aqara test * Add __init__.py in homeassistant.generated module * Restore add_job with type: ignore
This commit is contained in:
parent
338be8c70b
commit
82c94826fb
@ -10,7 +10,6 @@ from pylutron_caseta.smartbridge import Smartbridge
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.zeroconf import ATTR_HOSTNAME
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
|
||||||
@ -66,7 +65,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_zeroconf(self, discovery_info):
|
async def async_step_zeroconf(self, discovery_info):
|
||||||
"""Handle a flow initialized by zeroconf discovery."""
|
"""Handle a flow initialized by zeroconf discovery."""
|
||||||
hostname = discovery_info[ATTR_HOSTNAME]
|
hostname = discovery_info["hostname"]
|
||||||
if hostname is None or not hostname.startswith("lutron-"):
|
if hostname is None or not hostname.startswith("lutron-"):
|
||||||
return self.async_abort(reason="not_lutron_device")
|
return self.async_abort(reason="not_lutron_device")
|
||||||
|
|
||||||
|
@ -7,16 +7,14 @@ from functools import partial
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from zeroconf import (
|
from zeroconf import (
|
||||||
DNSPointer,
|
|
||||||
DNSRecord,
|
|
||||||
Error as ZeroconfError,
|
Error as ZeroconfError,
|
||||||
InterfaceChoice,
|
InterfaceChoice,
|
||||||
IPVersion,
|
IPVersion,
|
||||||
NonUniqueNameException,
|
NonUniqueNameException,
|
||||||
ServiceBrowser,
|
|
||||||
ServiceInfo,
|
ServiceInfo,
|
||||||
ServiceStateChange,
|
ServiceStateChange,
|
||||||
Zeroconf,
|
Zeroconf,
|
||||||
@ -24,29 +22,24 @@ from zeroconf import (
|
|||||||
|
|
||||||
from homeassistant import util
|
from homeassistant import util
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_NAME,
|
|
||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STARTED,
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
|
from homeassistant.core import Event, HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.network import NoURLAvailableError, get_url
|
from homeassistant.helpers.network import NoURLAvailableError, get_url
|
||||||
from homeassistant.helpers.singleton import singleton
|
from homeassistant.helpers.singleton import singleton
|
||||||
from homeassistant.loader import async_get_homekit, async_get_zeroconf
|
from homeassistant.loader import async_get_homekit, async_get_zeroconf
|
||||||
|
|
||||||
|
from .models import HaServiceBrowser, HaZeroconf
|
||||||
from .usage import install_multiple_zeroconf_catcher
|
from .usage import install_multiple_zeroconf_catcher
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "zeroconf"
|
DOMAIN = "zeroconf"
|
||||||
|
|
||||||
ATTR_HOST = "host"
|
|
||||||
ATTR_PORT = "port"
|
|
||||||
ATTR_HOSTNAME = "hostname"
|
|
||||||
ATTR_TYPE = "type"
|
|
||||||
ATTR_PROPERTIES = "properties"
|
|
||||||
|
|
||||||
ZEROCONF_TYPE = "_home-assistant._tcp.local."
|
ZEROCONF_TYPE = "_home-assistant._tcp.local."
|
||||||
HOMEKIT_TYPES = [
|
HOMEKIT_TYPES = [
|
||||||
"_hap._tcp.local.",
|
"_hap._tcp.local.",
|
||||||
@ -59,7 +52,6 @@ CONF_IPV6 = "ipv6"
|
|||||||
DEFAULT_DEFAULT_INTERFACE = True
|
DEFAULT_DEFAULT_INTERFACE = True
|
||||||
DEFAULT_IPV6 = True
|
DEFAULT_IPV6 = True
|
||||||
|
|
||||||
HOMEKIT_PROPERTIES = "properties"
|
|
||||||
HOMEKIT_PAIRED_STATUS_FLAG = "sf"
|
HOMEKIT_PAIRED_STATUS_FLAG = "sf"
|
||||||
HOMEKIT_MODEL = "md"
|
HOMEKIT_MODEL = "md"
|
||||||
|
|
||||||
@ -85,20 +77,31 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HaServiceInfo(TypedDict):
|
||||||
|
"""Prepared info from mDNS entries."""
|
||||||
|
|
||||||
|
host: str
|
||||||
|
port: int | None
|
||||||
|
hostname: str
|
||||||
|
type: str
|
||||||
|
name: str
|
||||||
|
properties: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
@singleton(DOMAIN)
|
@singleton(DOMAIN)
|
||||||
async def async_get_instance(hass):
|
async def async_get_instance(hass: HomeAssistant) -> HaZeroconf:
|
||||||
"""Zeroconf instance to be shared with other integrations that use it."""
|
"""Zeroconf instance to be shared with other integrations that use it."""
|
||||||
return await _async_get_instance(hass)
|
return await _async_get_instance(hass)
|
||||||
|
|
||||||
|
|
||||||
async def _async_get_instance(hass, **zcargs):
|
async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaZeroconf:
|
||||||
logging.getLogger("zeroconf").setLevel(logging.NOTSET)
|
logging.getLogger("zeroconf").setLevel(logging.NOTSET)
|
||||||
|
|
||||||
zeroconf = await hass.async_add_executor_job(partial(HaZeroconf, **zcargs))
|
zeroconf = await hass.async_add_executor_job(partial(HaZeroconf, **zcargs))
|
||||||
|
|
||||||
install_multiple_zeroconf_catcher(zeroconf)
|
install_multiple_zeroconf_catcher(zeroconf)
|
||||||
|
|
||||||
def _stop_zeroconf(_):
|
def _stop_zeroconf(_event: Event) -> None:
|
||||||
"""Stop Zeroconf."""
|
"""Stop Zeroconf."""
|
||||||
zeroconf.ha_close()
|
zeroconf.ha_close()
|
||||||
|
|
||||||
@ -107,40 +110,10 @@ async def _async_get_instance(hass, **zcargs):
|
|||||||
return zeroconf
|
return zeroconf
|
||||||
|
|
||||||
|
|
||||||
class HaServiceBrowser(ServiceBrowser):
|
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||||
"""ServiceBrowser that only consumes DNSPointer records."""
|
|
||||||
|
|
||||||
def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
|
|
||||||
"""Pre-Filter update_record to DNSPointers for the configured type."""
|
|
||||||
|
|
||||||
#
|
|
||||||
# Each ServerBrowser currently runs in its own thread which
|
|
||||||
# processes every A or AAAA record update per instance.
|
|
||||||
#
|
|
||||||
# As the list of zeroconf names we watch for grows, each additional
|
|
||||||
# ServiceBrowser would process all the A and AAAA updates on the network.
|
|
||||||
#
|
|
||||||
# To avoid overwhemling the system we pre-filter here and only process
|
|
||||||
# DNSPointers for the configured record name (type)
|
|
||||||
#
|
|
||||||
if record.name not in self.types or not isinstance(record, DNSPointer):
|
|
||||||
return
|
|
||||||
super().update_record(zc, now, record)
|
|
||||||
|
|
||||||
|
|
||||||
class HaZeroconf(Zeroconf):
|
|
||||||
"""Zeroconf that cannot be closed."""
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Fake method to avoid integrations closing it."""
|
|
||||||
|
|
||||||
ha_close = Zeroconf.close
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
|
||||||
"""Set up Zeroconf and make Home Assistant discoverable."""
|
"""Set up Zeroconf and make Home Assistant discoverable."""
|
||||||
zc_config = config.get(DOMAIN, {})
|
zc_config = config.get(DOMAIN, {})
|
||||||
zc_args = {}
|
zc_args: dict = {}
|
||||||
if zc_config.get(CONF_DEFAULT_INTERFACE, DEFAULT_DEFAULT_INTERFACE):
|
if zc_config.get(CONF_DEFAULT_INTERFACE, DEFAULT_DEFAULT_INTERFACE):
|
||||||
zc_args["interfaces"] = InterfaceChoice.Default
|
zc_args["interfaces"] = InterfaceChoice.Default
|
||||||
if not zc_config.get(CONF_IPV6, DEFAULT_IPV6):
|
if not zc_config.get(CONF_IPV6, DEFAULT_IPV6):
|
||||||
@ -148,7 +121,7 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
zeroconf = hass.data[DOMAIN] = await _async_get_instance(hass, **zc_args)
|
zeroconf = hass.data[DOMAIN] = await _async_get_instance(hass, **zc_args)
|
||||||
|
|
||||||
async def _async_zeroconf_hass_start(_event):
|
async def _async_zeroconf_hass_start(_event: Event) -> None:
|
||||||
"""Expose Home Assistant on zeroconf when it starts.
|
"""Expose Home Assistant on zeroconf when it starts.
|
||||||
|
|
||||||
Wait till started or otherwise HTTP is not up and running.
|
Wait till started or otherwise HTTP is not up and running.
|
||||||
@ -158,7 +131,7 @@ async def async_setup(hass, config):
|
|||||||
_register_hass_zc_service, hass, zeroconf, uuid
|
_register_hass_zc_service, hass, zeroconf, uuid
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_zeroconf_hass_started(_event):
|
async def _async_zeroconf_hass_started(_event: Event) -> None:
|
||||||
"""Start the service browser."""
|
"""Start the service browser."""
|
||||||
|
|
||||||
await _async_start_zeroconf_browser(hass, zeroconf)
|
await _async_start_zeroconf_browser(hass, zeroconf)
|
||||||
@ -171,7 +144,9 @@ async def async_setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _register_hass_zc_service(hass, zeroconf, uuid):
|
def _register_hass_zc_service(
|
||||||
|
hass: HomeAssistant, zeroconf: HaZeroconf, uuid: str
|
||||||
|
) -> None:
|
||||||
# Get instance UUID
|
# Get instance UUID
|
||||||
valid_location_name = _truncate_location_name_to_valid(hass.config.location_name)
|
valid_location_name = _truncate_location_name_to_valid(hass.config.location_name)
|
||||||
|
|
||||||
@ -224,7 +199,9 @@ def _register_hass_zc_service(hass, zeroconf, uuid):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _async_start_zeroconf_browser(hass, zeroconf):
|
async def _async_start_zeroconf_browser(
|
||||||
|
hass: HomeAssistant, zeroconf: HaZeroconf
|
||||||
|
) -> None:
|
||||||
"""Start the zeroconf browser."""
|
"""Start the zeroconf browser."""
|
||||||
|
|
||||||
zeroconf_types = await async_get_zeroconf(hass)
|
zeroconf_types = await async_get_zeroconf(hass)
|
||||||
@ -236,7 +213,12 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
|
|||||||
if hk_type not in zeroconf_types:
|
if hk_type not in zeroconf_types:
|
||||||
types.append(hk_type)
|
types.append(hk_type)
|
||||||
|
|
||||||
def service_update(zeroconf, service_type, name, state_change):
|
def service_update(
|
||||||
|
zeroconf: Zeroconf,
|
||||||
|
service_type: str,
|
||||||
|
name: str,
|
||||||
|
state_change: ServiceStateChange,
|
||||||
|
) -> None:
|
||||||
"""Service state changed."""
|
"""Service state changed."""
|
||||||
nonlocal zeroconf_types
|
nonlocal zeroconf_types
|
||||||
nonlocal homekit_models
|
nonlocal homekit_models
|
||||||
@ -276,12 +258,11 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
|
|||||||
# offering a second discovery for the same device
|
# offering a second discovery for the same device
|
||||||
if (
|
if (
|
||||||
discovery_was_forwarded
|
discovery_was_forwarded
|
||||||
and HOMEKIT_PROPERTIES in info
|
and HOMEKIT_PAIRED_STATUS_FLAG in info["properties"]
|
||||||
and HOMEKIT_PAIRED_STATUS_FLAG in info[HOMEKIT_PROPERTIES]
|
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# 0 means paired and not discoverable by iOS clients)
|
# 0 means paired and not discoverable by iOS clients)
|
||||||
if int(info[HOMEKIT_PROPERTIES][HOMEKIT_PAIRED_STATUS_FLAG]):
|
if int(info["properties"][HOMEKIT_PAIRED_STATUS_FLAG]):
|
||||||
return
|
return
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# HomeKit pairing status unknown
|
# HomeKit pairing status unknown
|
||||||
@ -289,12 +270,12 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if "name" in info:
|
if "name" in info:
|
||||||
lowercase_name = info["name"].lower()
|
lowercase_name: str | None = info["name"].lower()
|
||||||
else:
|
else:
|
||||||
lowercase_name = None
|
lowercase_name = None
|
||||||
|
|
||||||
if "macaddress" in info.get("properties", {}):
|
if "macaddress" in info["properties"]:
|
||||||
uppercase_mac = info["properties"]["macaddress"].upper()
|
uppercase_mac: str | None = info["properties"]["macaddress"].upper()
|
||||||
else:
|
else:
|
||||||
uppercase_mac = None
|
uppercase_mac = None
|
||||||
|
|
||||||
@ -318,20 +299,22 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
|
|||||||
hass.add_job(
|
hass.add_job(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
entry["domain"], context={"source": DOMAIN}, data=info
|
entry["domain"], context={"source": DOMAIN}, data=info
|
||||||
)
|
) # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Starting Zeroconf browser")
|
_LOGGER.debug("Starting Zeroconf browser")
|
||||||
HaServiceBrowser(zeroconf, types, handlers=[service_update])
|
HaServiceBrowser(zeroconf, types, handlers=[service_update])
|
||||||
|
|
||||||
|
|
||||||
def handle_homekit(hass, homekit_models, info) -> bool:
|
def handle_homekit(
|
||||||
|
hass: HomeAssistant, homekit_models: dict[str, str], info: HaServiceInfo
|
||||||
|
) -> bool:
|
||||||
"""Handle a HomeKit discovery.
|
"""Handle a HomeKit discovery.
|
||||||
|
|
||||||
Return if discovery was forwarded.
|
Return if discovery was forwarded.
|
||||||
"""
|
"""
|
||||||
model = None
|
model = None
|
||||||
props = info.get(HOMEKIT_PROPERTIES, {})
|
props = info["properties"]
|
||||||
|
|
||||||
for key in props:
|
for key in props:
|
||||||
if key.lower() == HOMEKIT_MODEL:
|
if key.lower() == HOMEKIT_MODEL:
|
||||||
@ -352,16 +335,16 @@ def handle_homekit(hass, homekit_models, info) -> bool:
|
|||||||
hass.add_job(
|
hass.add_job(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
homekit_models[test_model], context={"source": "homekit"}, data=info
|
homekit_models[test_model], context={"source": "homekit"}, data=info
|
||||||
)
|
) # type: ignore
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def info_from_service(service):
|
def info_from_service(service: ServiceInfo) -> HaServiceInfo | None:
|
||||||
"""Return prepared info from mDNS entries."""
|
"""Return prepared info from mDNS entries."""
|
||||||
properties = {"_raw": {}}
|
properties: dict[str, Any] = {"_raw": {}}
|
||||||
|
|
||||||
for key, value in service.properties.items():
|
for key, value in service.properties.items():
|
||||||
# See https://ietf.org/rfc/rfc6763.html#section-6.4 and
|
# See https://ietf.org/rfc/rfc6763.html#section-6.4 and
|
||||||
@ -386,19 +369,17 @@ def info_from_service(service):
|
|||||||
|
|
||||||
address = service.addresses[0]
|
address = service.addresses[0]
|
||||||
|
|
||||||
info = {
|
return {
|
||||||
ATTR_HOST: str(ipaddress.ip_address(address)),
|
"host": str(ipaddress.ip_address(address)),
|
||||||
ATTR_PORT: service.port,
|
"port": service.port,
|
||||||
ATTR_HOSTNAME: service.server,
|
"hostname": service.server,
|
||||||
ATTR_TYPE: service.type,
|
"type": service.type,
|
||||||
ATTR_NAME: service.name,
|
"name": service.name,
|
||||||
ATTR_PROPERTIES: properties,
|
"properties": properties,
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
|
def _suppress_invalid_properties(properties: dict) -> None:
|
||||||
def _suppress_invalid_properties(properties):
|
|
||||||
"""Suppress any properties that will cause zeroconf to fail to startup."""
|
"""Suppress any properties that will cause zeroconf to fail to startup."""
|
||||||
|
|
||||||
for prop, prop_value in properties.items():
|
for prop, prop_value in properties.items():
|
||||||
@ -415,7 +396,7 @@ def _suppress_invalid_properties(properties):
|
|||||||
properties[prop] = ""
|
properties[prop] = ""
|
||||||
|
|
||||||
|
|
||||||
def _truncate_location_name_to_valid(location_name):
|
def _truncate_location_name_to_valid(location_name: str) -> str:
|
||||||
"""Truncate or return the location name usable for zeroconf."""
|
"""Truncate or return the location name usable for zeroconf."""
|
||||||
if len(location_name.encode("utf-8")) < MAX_NAME_LEN:
|
if len(location_name.encode("utf-8")) < MAX_NAME_LEN:
|
||||||
return location_name
|
return location_name
|
||||||
|
33
homeassistant/components/zeroconf/models.py
Normal file
33
homeassistant/components/zeroconf/models.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""Models for Zeroconf."""
|
||||||
|
|
||||||
|
from zeroconf import DNSPointer, DNSRecord, ServiceBrowser, Zeroconf
|
||||||
|
|
||||||
|
|
||||||
|
class HaZeroconf(Zeroconf):
|
||||||
|
"""Zeroconf that cannot be closed."""
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
"""Fake method to avoid integrations closing it."""
|
||||||
|
|
||||||
|
ha_close = Zeroconf.close
|
||||||
|
|
||||||
|
|
||||||
|
class HaServiceBrowser(ServiceBrowser):
|
||||||
|
"""ServiceBrowser that only consumes DNSPointer records."""
|
||||||
|
|
||||||
|
def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
|
||||||
|
"""Pre-Filter update_record to DNSPointers for the configured type."""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Each ServerBrowser currently runs in its own thread which
|
||||||
|
# processes every A or AAAA record update per instance.
|
||||||
|
#
|
||||||
|
# As the list of zeroconf names we watch for grows, each additional
|
||||||
|
# ServiceBrowser would process all the A and AAAA updates on the network.
|
||||||
|
#
|
||||||
|
# To avoid overwhemling the system we pre-filter here and only process
|
||||||
|
# DNSPointers for the configured record name (type)
|
||||||
|
#
|
||||||
|
if record.name not in self.types or not isinstance(record, DNSPointer):
|
||||||
|
return
|
||||||
|
super().update_record(zc, now, record)
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import zeroconf
|
import zeroconf
|
||||||
|
|
||||||
@ -11,23 +12,25 @@ from homeassistant.helpers.frame import (
|
|||||||
report_integration,
|
report_integration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .models import HaZeroconf
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def install_multiple_zeroconf_catcher(hass_zc) -> None:
|
def install_multiple_zeroconf_catcher(hass_zc: HaZeroconf) -> None:
|
||||||
"""Wrap the Zeroconf class to return the shared instance if multiple instances are detected."""
|
"""Wrap the Zeroconf class to return the shared instance if multiple instances are detected."""
|
||||||
|
|
||||||
def new_zeroconf_new(self, *k, **kw):
|
def new_zeroconf_new(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> HaZeroconf:
|
||||||
_report(
|
_report(
|
||||||
"attempted to create another Zeroconf instance. Please use the shared Zeroconf via await homeassistant.components.zeroconf.async_get_instance(hass)",
|
"attempted to create another Zeroconf instance. Please use the shared Zeroconf via await homeassistant.components.zeroconf.async_get_instance(hass)",
|
||||||
)
|
)
|
||||||
return hass_zc
|
return hass_zc
|
||||||
|
|
||||||
def new_zeroconf_init(self, *k, **kw):
|
def new_zeroconf_init(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
zeroconf.Zeroconf.__new__ = new_zeroconf_new
|
zeroconf.Zeroconf.__new__ = new_zeroconf_new # type: ignore
|
||||||
zeroconf.Zeroconf.__init__ = new_zeroconf_init
|
zeroconf.Zeroconf.__init__ = new_zeroconf_init # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def _report(what: str) -> None:
|
def _report(what: str) -> None:
|
||||||
|
4
homeassistant/generated/__init__.py
Normal file
4
homeassistant/generated/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
"""All files in this module are automatically generated by hassfest.
|
||||||
|
|
||||||
|
To update, run python3 -m script.hassfest
|
||||||
|
"""
|
@ -43,7 +43,7 @@ warn_redundant_casts = true
|
|||||||
warn_unused_configs = true
|
warn_unused_configs = true
|
||||||
|
|
||||||
|
|
||||||
[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*]
|
[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zeroconf.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*]
|
||||||
strict = true
|
strict = true
|
||||||
ignore_errors = false
|
ignore_errors = false
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
@ -17,11 +17,12 @@ from homeassistant.components.lutron_caseta.const import (
|
|||||||
ERROR_CANNOT_CONNECT,
|
ERROR_CANNOT_CONNECT,
|
||||||
STEP_IMPORT_FAILED,
|
STEP_IMPORT_FAILED,
|
||||||
)
|
)
|
||||||
from homeassistant.components.zeroconf import ATTR_HOSTNAME
|
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
ATTR_HOSTNAME = "hostname"
|
||||||
|
|
||||||
EMPTY_MOCK_CONFIG_ENTRY = {
|
EMPTY_MOCK_CONFIG_ENTRY = {
|
||||||
CONF_HOST: "",
|
CONF_HOST: "",
|
||||||
CONF_KEYFILE: "",
|
CONF_KEYFILE: "",
|
||||||
|
@ -5,7 +5,6 @@ from unittest.mock import Mock, patch
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import zeroconf
|
|
||||||
from homeassistant.components.xiaomi_aqara import config_flow, const
|
from homeassistant.components.xiaomi_aqara import config_flow, const
|
||||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_PROTOCOL
|
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_PROTOCOL
|
||||||
|
|
||||||
@ -402,7 +401,7 @@ async def test_zeroconf_success(hass):
|
|||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
data={
|
data={
|
||||||
zeroconf.ATTR_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
ZEROCONF_NAME: TEST_ZEROCONF_NAME,
|
ZEROCONF_NAME: TEST_ZEROCONF_NAME,
|
||||||
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
||||||
},
|
},
|
||||||
@ -444,7 +443,7 @@ async def test_zeroconf_missing_data(hass):
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
data={zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME},
|
data={CONF_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
@ -457,7 +456,7 @@ async def test_zeroconf_unknown_device(hass):
|
|||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
data={
|
data={
|
||||||
zeroconf.ATTR_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
ZEROCONF_NAME: "not-a-xiaomi-aqara-gateway",
|
ZEROCONF_NAME: "not-a-xiaomi-aqara-gateway",
|
||||||
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,6 @@ from unittest.mock import Mock, patch
|
|||||||
from miio import DeviceException
|
from miio import DeviceException
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import zeroconf
|
|
||||||
from homeassistant.components.xiaomi_miio import const
|
from homeassistant.components.xiaomi_miio import const
|
||||||
from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME
|
from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
|
||||||
@ -106,7 +105,7 @@ async def test_zeroconf_gateway_success(hass):
|
|||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
data={
|
data={
|
||||||
zeroconf.ATTR_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
ZEROCONF_NAME: TEST_ZEROCONF_NAME,
|
ZEROCONF_NAME: TEST_ZEROCONF_NAME,
|
||||||
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
||||||
},
|
},
|
||||||
@ -146,7 +145,7 @@ async def test_zeroconf_unknown_device(hass):
|
|||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
data={
|
data={
|
||||||
zeroconf.ATTR_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
ZEROCONF_NAME: "not-a-xiaomi-miio-device",
|
ZEROCONF_NAME: "not-a-xiaomi-miio-device",
|
||||||
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
|
||||||
},
|
},
|
||||||
@ -171,7 +170,7 @@ async def test_zeroconf_missing_data(hass):
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
data={zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME},
|
data={CONF_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "abort"
|
assert result["type"] == "abort"
|
||||||
@ -342,7 +341,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test):
|
|||||||
const.DOMAIN,
|
const.DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
data={
|
data={
|
||||||
zeroconf.ATTR_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
ZEROCONF_NAME: zeroconf_name_to_test,
|
ZEROCONF_NAME: zeroconf_name_to_test,
|
||||||
ZEROCONF_PROP: {"poch": f"0:mac={TEST_MAC_DEVICE}\x00"},
|
ZEROCONF_PROP: {"poch": f"0:mac={TEST_MAC_DEVICE}\x00"},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user