Strict typing of UniFi integration (#90278)
* Fix typing of UniFi controller * Strict typing of unifi.__init__ * Strict typing of UniFi config_flow * Strict typing of UniFi switch * Strict typing UniFi sensor * Strict typing UniFi device tracker * Strict typing of UniFi * Fix library issues related to typing
This commit is contained in:
parent
e8f3b9c09a
commit
a0b6da33ab
9 changed files with 57 additions and 54 deletions
|
@ -311,7 +311,7 @@ homeassistant.components.trafikverket_train.*
|
||||||
homeassistant.components.trafikverket_weatherstation.*
|
homeassistant.components.trafikverket_weatherstation.*
|
||||||
homeassistant.components.tts.*
|
homeassistant.components.tts.*
|
||||||
homeassistant.components.twentemilieu.*
|
homeassistant.components.twentemilieu.*
|
||||||
homeassistant.components.unifi.update
|
homeassistant.components.unifi.*
|
||||||
homeassistant.components.unifiprotect.*
|
homeassistant.components.unifiprotect.*
|
||||||
homeassistant.components.upcloud.*
|
homeassistant.components.upcloud.*
|
||||||
homeassistant.components.update.*
|
homeassistant.components.update.*
|
||||||
|
|
|
@ -64,7 +64,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
controller = hass.data[UNIFI_DOMAIN].pop(config_entry.entry_id)
|
controller: UniFiController = hass.data[UNIFI_DOMAIN].pop(config_entry.entry_id)
|
||||||
|
|
||||||
if not hass.data[UNIFI_DOMAIN]:
|
if not hass.data[UNIFI_DOMAIN]:
|
||||||
async_unload_services(hass)
|
async_unload_services(hass)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from typing import Any
|
||||||
from aiohttp import CookieJar
|
from aiohttp import CookieJar
|
||||||
import aiounifi
|
import aiounifi
|
||||||
from aiounifi.interfaces.api_handlers import ItemEvent
|
from aiounifi.interfaces.api_handlers import ItemEvent
|
||||||
from aiounifi.websocket import WebsocketSignal, WebsocketState
|
from aiounifi.websocket import WebsocketState
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
@ -22,7 +22,7 @@ from homeassistant.const import (
|
||||||
CONF_VERIFY_SSL,
|
CONF_VERIFY_SSL,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
aiohttp_client,
|
aiohttp_client,
|
||||||
device_registry as dr,
|
device_registry as dr,
|
||||||
|
@ -75,31 +75,32 @@ CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1)
|
||||||
class UniFiController:
|
class UniFiController:
|
||||||
"""Manages a single UniFi Network instance."""
|
"""Manages a single UniFi Network instance."""
|
||||||
|
|
||||||
def __init__(self, hass, config_entry, api):
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, config_entry: ConfigEntry, api: aiounifi.Controller
|
||||||
|
) -> None:
|
||||||
"""Initialize the system."""
|
"""Initialize the system."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.config_entry = config_entry
|
self.config_entry = config_entry
|
||||||
self.api = api
|
self.api = api
|
||||||
|
|
||||||
api.callback = self.async_unifi_signalling_callback
|
api.ws_state_callback = self.async_unifi_ws_state_callback
|
||||||
|
|
||||||
self.available = True
|
self.available = True
|
||||||
self.wireless_clients = hass.data[UNIFI_WIRELESS_CLIENTS]
|
self.wireless_clients = hass.data[UNIFI_WIRELESS_CLIENTS]
|
||||||
|
|
||||||
self.site_id: str = ""
|
self.site_id: str = ""
|
||||||
self._site_name = None
|
self._site_name: str | None = None
|
||||||
self._site_role = None
|
self._site_role: str | None = None
|
||||||
|
|
||||||
self._cancel_heartbeat_check = None
|
self._cancel_heartbeat_check: CALLBACK_TYPE | None = None
|
||||||
self._heartbeat_dispatch = {}
|
self._heartbeat_time: dict[str, datetime] = {}
|
||||||
self._heartbeat_time = {}
|
|
||||||
|
|
||||||
self.load_config_entry_options()
|
self.load_config_entry_options()
|
||||||
|
|
||||||
self.entities = {}
|
self.entities: dict[str, str] = {}
|
||||||
self.known_objects: set[tuple[str, str]] = set()
|
self.known_objects: set[tuple[str, str]] = set()
|
||||||
|
|
||||||
def load_config_entry_options(self):
|
def load_config_entry_options(self) -> None:
|
||||||
"""Store attributes to avoid property call overhead since they are called frequently."""
|
"""Store attributes to avoid property call overhead since they are called frequently."""
|
||||||
options = self.config_entry.options
|
options = self.config_entry.options
|
||||||
|
|
||||||
|
@ -114,7 +115,7 @@ class UniFiController:
|
||||||
CONF_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS
|
CONF_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS
|
||||||
)
|
)
|
||||||
# Config entry option to not track devices.
|
# Config entry option to not track devices.
|
||||||
self.option_track_devices = options.get(
|
self.option_track_devices: bool = options.get(
|
||||||
CONF_TRACK_DEVICES, DEFAULT_TRACK_DEVICES
|
CONF_TRACK_DEVICES, DEFAULT_TRACK_DEVICES
|
||||||
)
|
)
|
||||||
# Config entry option listing what SSIDs are being used to track clients.
|
# Config entry option listing what SSIDs are being used to track clients.
|
||||||
|
@ -133,43 +134,45 @@ class UniFiController:
|
||||||
# Config entry option with list of clients to control network access.
|
# Config entry option with list of clients to control network access.
|
||||||
self.option_block_clients = options.get(CONF_BLOCK_CLIENT, [])
|
self.option_block_clients = options.get(CONF_BLOCK_CLIENT, [])
|
||||||
# Config entry option to control DPI restriction groups.
|
# Config entry option to control DPI restriction groups.
|
||||||
self.option_dpi_restrictions = options.get(
|
self.option_dpi_restrictions: bool = options.get(
|
||||||
CONF_DPI_RESTRICTIONS, DEFAULT_DPI_RESTRICTIONS
|
CONF_DPI_RESTRICTIONS, DEFAULT_DPI_RESTRICTIONS
|
||||||
)
|
)
|
||||||
|
|
||||||
# Statistics sensor options
|
# Statistics sensor options
|
||||||
|
|
||||||
# Config entry option to allow bandwidth sensors.
|
# Config entry option to allow bandwidth sensors.
|
||||||
self.option_allow_bandwidth_sensors = options.get(
|
self.option_allow_bandwidth_sensors: bool = options.get(
|
||||||
CONF_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_BANDWIDTH_SENSORS
|
CONF_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_BANDWIDTH_SENSORS
|
||||||
)
|
)
|
||||||
# Config entry option to allow uptime sensors.
|
# Config entry option to allow uptime sensors.
|
||||||
self.option_allow_uptime_sensors = options.get(
|
self.option_allow_uptime_sensors: bool = options.get(
|
||||||
CONF_ALLOW_UPTIME_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS
|
CONF_ALLOW_UPTIME_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self):
|
def host(self) -> str:
|
||||||
"""Return the host of this controller."""
|
"""Return the host of this controller."""
|
||||||
return self.config_entry.data[CONF_HOST]
|
host: str = self.config_entry.data[CONF_HOST]
|
||||||
|
return host
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def site(self):
|
def site(self) -> str:
|
||||||
"""Return the site of this config entry."""
|
"""Return the site of this config entry."""
|
||||||
return self.config_entry.data[CONF_SITE_ID]
|
site_id: str = self.config_entry.data[CONF_SITE_ID]
|
||||||
|
return site_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def site_name(self):
|
def site_name(self) -> str | None:
|
||||||
"""Return the nice name of site."""
|
"""Return the nice name of site."""
|
||||||
return self._site_name
|
return self._site_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def site_role(self):
|
def site_role(self) -> str | None:
|
||||||
"""Return the site user role of this controller."""
|
"""Return the site user role of this controller."""
|
||||||
return self._site_role
|
return self._site_role
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mac(self):
|
def mac(self) -> str | None:
|
||||||
"""Return the mac address of this controller."""
|
"""Return the mac address of this controller."""
|
||||||
for client in self.api.clients.values():
|
for client in self.api.clients.values():
|
||||||
if self.host == client.ip:
|
if self.host == client.ip:
|
||||||
|
@ -227,16 +230,15 @@ class UniFiController:
|
||||||
async_load_entities(description)
|
async_load_entities(description)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_unifi_signalling_callback(self, signal, data):
|
def async_unifi_ws_state_callback(self, state: WebsocketState) -> None:
|
||||||
"""Handle messages back from UniFi library."""
|
"""Handle messages back from UniFi library."""
|
||||||
if signal == WebsocketSignal.CONNECTION_STATE:
|
if state == WebsocketState.DISCONNECTED and self.available:
|
||||||
if data == WebsocketState.DISCONNECTED and self.available:
|
|
||||||
LOGGER.warning("Lost connection to UniFi Network")
|
LOGGER.warning("Lost connection to UniFi Network")
|
||||||
|
|
||||||
if (data == WebsocketState.RUNNING and not self.available) or (
|
if (state == WebsocketState.RUNNING and not self.available) or (
|
||||||
data == WebsocketState.DISCONNECTED and self.available
|
state == WebsocketState.DISCONNECTED and self.available
|
||||||
):
|
):
|
||||||
self.available = data == WebsocketState.RUNNING
|
self.available = state == WebsocketState.RUNNING
|
||||||
async_dispatcher_send(self.hass, self.signal_reachable)
|
async_dispatcher_send(self.hass, self.signal_reachable)
|
||||||
|
|
||||||
if not self.available:
|
if not self.available:
|
||||||
|
@ -259,7 +261,7 @@ class UniFiController:
|
||||||
"""Event specific per UniFi device tracker to signal new heartbeat missed."""
|
"""Event specific per UniFi device tracker to signal new heartbeat missed."""
|
||||||
return "unifi-heartbeat-missed"
|
return "unifi-heartbeat-missed"
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self) -> None:
|
||||||
"""Set up a UniFi Network instance."""
|
"""Set up a UniFi Network instance."""
|
||||||
await self.api.initialize()
|
await self.api.initialize()
|
||||||
|
|
||||||
|
@ -291,7 +293,7 @@ class UniFiController:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
client = self.api.clients_all[mac]
|
client = self.api.clients_all[mac]
|
||||||
self.api.clients.process_raw([client.raw])
|
self.api.clients.process_raw([dict(client.raw)])
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Restore disconnected client %s (%s)",
|
"Restore disconnected client %s (%s)",
|
||||||
entry.entity_id,
|
entry.entity_id,
|
||||||
|
@ -319,7 +321,7 @@ class UniFiController:
|
||||||
del self._heartbeat_time[unique_id]
|
del self._heartbeat_time[unique_id]
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_check_for_stale(self, *_) -> None:
|
def _async_check_for_stale(self, *_: datetime) -> None:
|
||||||
"""Check for any devices scheduled to be marked disconnected."""
|
"""Check for any devices scheduled to be marked disconnected."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
|
@ -365,7 +367,7 @@ class UniFiController:
|
||||||
async_dispatcher_send(hass, controller.signal_options_update)
|
async_dispatcher_send(hass, controller.signal_options_update)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def reconnect(self, log=False) -> None:
|
def reconnect(self, log: bool = False) -> None:
|
||||||
"""Prepare to reconnect UniFi session."""
|
"""Prepare to reconnect UniFi session."""
|
||||||
if log:
|
if log:
|
||||||
LOGGER.info("Will try to reconnect to UniFi Network")
|
LOGGER.info("Will try to reconnect to UniFi Network")
|
||||||
|
@ -387,14 +389,14 @@ class UniFiController:
|
||||||
self.hass.loop.call_later(RETRY_TIMER, self.reconnect)
|
self.hass.loop.call_later(RETRY_TIMER, self.reconnect)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def shutdown(self, event) -> None:
|
def shutdown(self, event: Event) -> None:
|
||||||
"""Wrap the call to unifi.close.
|
"""Wrap the call to unifi.close.
|
||||||
|
|
||||||
Used as an argument to EventBus.async_listen_once.
|
Used as an argument to EventBus.async_listen_once.
|
||||||
"""
|
"""
|
||||||
self.api.stop_websocket()
|
self.api.stop_websocket()
|
||||||
|
|
||||||
async def async_reset(self):
|
async def async_reset(self) -> bool:
|
||||||
"""Reset this controller to default state.
|
"""Reset this controller to default state.
|
||||||
|
|
||||||
Will cancel any scheduled setup retry and will unload
|
Will cancel any scheduled setup retry and will unload
|
||||||
|
@ -421,15 +423,15 @@ async def get_unifi_controller(
|
||||||
config: MappingProxyType[str, Any],
|
config: MappingProxyType[str, Any],
|
||||||
) -> aiounifi.Controller:
|
) -> aiounifi.Controller:
|
||||||
"""Create a controller object and verify authentication."""
|
"""Create a controller object and verify authentication."""
|
||||||
ssl_context = False
|
ssl_context: ssl.SSLContext | bool = False
|
||||||
|
|
||||||
if verify_ssl := bool(config.get(CONF_VERIFY_SSL)):
|
if verify_ssl := config.get(CONF_VERIFY_SSL):
|
||||||
session = aiohttp_client.async_get_clientsession(hass)
|
session = aiohttp_client.async_get_clientsession(hass)
|
||||||
if isinstance(verify_ssl, str):
|
if isinstance(verify_ssl, str):
|
||||||
ssl_context = ssl.create_default_context(cafile=verify_ssl)
|
ssl_context = ssl.create_default_context(cafile=verify_ssl)
|
||||||
else:
|
else:
|
||||||
session = aiohttp_client.async_create_clientsession(
|
session = aiohttp_client.async_create_clientsession(
|
||||||
hass, verify_ssl=verify_ssl, cookie_jar=CookieJar(unsafe=True)
|
hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
controller = aiounifi.Controller(
|
controller = aiounifi.Controller(
|
||||||
|
|
|
@ -19,7 +19,7 @@ from aiounifi.models.event import Event, EventKey
|
||||||
|
|
||||||
from homeassistant.components.device_tracker import ScannerEntity, SourceType
|
from homeassistant.components.device_tracker import ScannerEntity, SourceType
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import Event as core_Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
@ -268,7 +268,7 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity):
|
||||||
return self._attr_unique_id
|
return self._attr_unique_id
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _make_disconnected(self, *_) -> None:
|
def _make_disconnected(self, *_: core_Event) -> None:
|
||||||
"""No heart beat by device."""
|
"""No heart beat by device."""
|
||||||
self._is_connected = False
|
self._is_connected = False
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiounifi"],
|
"loggers": ["aiounifi"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aiounifi==45"],
|
"requirements": ["aiounifi==46"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Ubiquiti Networks",
|
"manufacturer": "Ubiquiti Networks",
|
||||||
|
|
|
@ -247,8 +247,9 @@ async def async_setup_entry(
|
||||||
|
|
||||||
for mac in controller.option_block_clients:
|
for mac in controller.option_block_clients:
|
||||||
if mac not in controller.api.clients and mac in controller.api.clients_all:
|
if mac not in controller.api.clients and mac in controller.api.clients_all:
|
||||||
client = controller.api.clients_all[mac]
|
controller.api.clients.process_raw(
|
||||||
controller.api.clients.process_raw([client.raw])
|
[dict(controller.api.clients_all[mac].raw)]
|
||||||
|
)
|
||||||
|
|
||||||
controller.register_platform_add_entities(
|
controller.register_platform_add_entities(
|
||||||
UnifiSwitchEntity, ENTITY_DESCRIPTIONS, async_add_entities
|
UnifiSwitchEntity, ENTITY_DESCRIPTIONS, async_add_entities
|
||||||
|
|
2
mypy.ini
2
mypy.ini
|
@ -2873,7 +2873,7 @@ disallow_untyped_defs = true
|
||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.unifi.update]
|
[mypy-homeassistant.components.unifi.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
disallow_subclassing_any = true
|
disallow_subclassing_any = true
|
||||||
|
|
|
@ -291,7 +291,7 @@ aiosyncthing==0.5.1
|
||||||
aiotractive==0.5.5
|
aiotractive==0.5.5
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==45
|
aiounifi==46
|
||||||
|
|
||||||
# homeassistant.components.vlc_telnet
|
# homeassistant.components.vlc_telnet
|
||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
|
|
|
@ -272,7 +272,7 @@ aiosyncthing==0.5.1
|
||||||
aiotractive==0.5.5
|
aiotractive==0.5.5
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==45
|
aiounifi==46
|
||||||
|
|
||||||
# homeassistant.components.vlc_telnet
|
# homeassistant.components.vlc_telnet
|
||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue