Remove freebox from mypy ignore list (#65126)

* Add type hints to freebox

* Remove freebox from mypy ignore list

* Adjust type hints

* Refactor FreeboxRouter setup/close

* Remove unnecessary assert

* Remove unused constant

* Rework unload routine

* Bring back close method

* Suppress NotOpenError

* Use async_on_unload on signal_device_new

Co-authored-by: epenet <epenet@users.noreply.github.com>
This commit is contained in:
epenet 2022-02-03 10:01:41 +01:00 committed by GitHub
parent c8504bd21d
commit 9fde84ab41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 55 additions and 70 deletions

View file

@ -1,18 +1,19 @@
"""Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" """Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
import logging from datetime import timedelta
from freebox_api.exceptions import HttpRequestError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN, PLATFORMS, SERVICE_REBOOT from .const import DOMAIN, PLATFORMS, SERVICE_REBOOT
from .router import FreeboxRouter from .router import FreeboxRouter, get_api
_LOGGER = logging.getLogger(__name__)
FREEBOX_SCHEMA = vol.Schema( FREEBOX_SCHEMA = vol.Schema(
{vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port} {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port}
@ -26,6 +27,8 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
SCAN_INTERVAL = timedelta(seconds=30)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Freebox integration.""" """Set up the Freebox integration."""
@ -42,8 +45,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Freebox entry.""" """Set up Freebox entry."""
router = FreeboxRouter(hass, entry) api = await get_api(hass, entry.data[CONF_HOST])
await router.setup() try:
await api.open(entry.data[CONF_HOST], entry.data[CONF_PORT])
except HttpRequestError as err:
raise ConfigEntryNotReady from err
freebox_config = await api.system.get_config()
router = FreeboxRouter(hass, entry, api, freebox_config)
await router.update_all()
entry.async_on_unload(
async_track_time_interval(hass, router.update_all, SCAN_INTERVAL)
)
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.unique_id] = router hass.data[DOMAIN][entry.unique_id] = router
@ -57,7 +71,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot) hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot)
async def async_close_connection(event): async def async_close_connection(event: Event) -> None:
"""Close Freebox connection on HA Stop.""" """Close Freebox connection on HA Stop."""
await router.close() await router.close()
@ -72,7 +86,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok: if unload_ok:
router = hass.data[DOMAIN].pop(entry.unique_id) router: FreeboxRouter = hass.data[DOMAIN].pop(entry.unique_id)
await router.close() await router.close()
hass.services.async_remove(DOMAIN, SERVICE_REBOOT) hass.services.async_remove(DOMAIN, SERVICE_REBOOT)

View file

@ -19,15 +19,15 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up device tracker for Freebox component.""" """Set up device tracker for Freebox component."""
router = hass.data[DOMAIN][entry.unique_id] router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
tracked = set() tracked: set[str] = set()
@callback @callback
def update_router(): def update_router() -> None:
"""Update the values of the router.""" """Update the values of the router."""
add_entities(router, async_add_entities, tracked) add_entities(router, async_add_entities, tracked)
router.listeners.append( entry.async_on_unload(
async_dispatcher_connect(hass, router.signal_device_new, update_router) async_dispatcher_connect(hass, router.signal_device_new, update_router)
) )
@ -35,7 +35,9 @@ async def async_setup_entry(
@callback @callback
def add_entities(router, async_add_entities, tracked): def add_entities(
router: FreeboxRouter, async_add_entities: AddEntitiesCallback, tracked: set[str]
) -> None:
"""Add new tracker entities from the router.""" """Add new tracker entities from the router."""
new_tracked = [] new_tracked = []
@ -61,7 +63,7 @@ class FreeboxDevice(ScannerEntity):
self._manufacturer = device["vendor_name"] self._manufacturer = device["vendor_name"]
self._icon = icon_for_freebox_device(device) self._icon = icon_for_freebox_device(device)
self._active = False self._active = False
self._attrs = {} self._attrs: dict[str, Any] = {}
@callback @callback
def async_update_state(self) -> None: def async_update_state(self) -> None:

View file

@ -1,24 +1,23 @@
"""Represent the Freebox router and its devices and sensors.""" """Represent the Freebox router and its devices and sensors."""
from __future__ import annotations from __future__ import annotations
from datetime import datetime, timedelta from collections.abc import Mapping
import logging from contextlib import suppress
from datetime import datetime
import os import os
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from freebox_api import Freepybox from freebox_api import Freepybox
from freebox_api.api.wifi import Wifi from freebox_api.api.wifi import Wifi
from freebox_api.exceptions import HttpRequestError from freebox_api.exceptions import NotOpenError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import slugify from homeassistant.util import slugify
from .const import ( from .const import (
@ -30,10 +29,6 @@ from .const import (
STORAGE_VERSION, STORAGE_VERSION,
) )
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=30)
async def get_api(hass: HomeAssistant, host: str) -> Freepybox: async def get_api(hass: HomeAssistant, host: str) -> Freepybox:
"""Get the Freebox API.""" """Get the Freebox API."""
@ -50,18 +45,23 @@ async def get_api(hass: HomeAssistant, host: str) -> Freepybox:
class FreeboxRouter: class FreeboxRouter:
"""Representation of a Freebox router.""" """Representation of a Freebox router."""
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
api: Freepybox,
freebox_config: Mapping[str, Any],
) -> None:
"""Initialize a Freebox router.""" """Initialize a Freebox router."""
self.hass = hass self.hass = hass
self._entry = entry
self._host = entry.data[CONF_HOST] self._host = entry.data[CONF_HOST]
self._port = entry.data[CONF_PORT] self._port = entry.data[CONF_PORT]
self._api: Freepybox = None self._api: Freepybox = api
self.name = None self.name: str = freebox_config["model_info"]["pretty_name"]
self.mac = None self.mac: str = freebox_config["mac"]
self._sw_v = None self._sw_v: str = freebox_config["firmware_version"]
self._attrs = {} self._attrs: dict[str, Any] = {}
self.devices: dict[str, dict[str, Any]] = {} self.devices: dict[str, dict[str, Any]] = {}
self.disks: dict[int, dict[str, Any]] = {} self.disks: dict[int, dict[str, Any]] = {}
@ -69,31 +69,6 @@ class FreeboxRouter:
self.sensors_connection: dict[str, float] = {} self.sensors_connection: dict[str, float] = {}
self.call_list: list[dict[str, Any]] = [] self.call_list: list[dict[str, Any]] = []
self._unsub_dispatcher = None
self.listeners = []
async def setup(self) -> None:
"""Set up a Freebox router."""
self._api = await get_api(self.hass, self._host)
try:
await self._api.open(self._host, self._port)
except HttpRequestError:
_LOGGER.exception("Failed to connect to Freebox")
return ConfigEntryNotReady
# System
fbx_config = await self._api.system.get_config()
self.mac = fbx_config["mac"]
self.name = fbx_config["model_info"]["pretty_name"]
self._sw_v = fbx_config["firmware_version"]
# Devices & sensors
await self.update_all()
self._unsub_dispatcher = async_track_time_interval(
self.hass, self.update_all, SCAN_INTERVAL
)
async def update_all(self, now: datetime | None = None) -> None: async def update_all(self, now: datetime | None = None) -> None:
"""Update all Freebox platforms.""" """Update all Freebox platforms."""
await self.update_device_trackers() await self.update_device_trackers()
@ -102,7 +77,7 @@ class FreeboxRouter:
async def update_device_trackers(self) -> None: async def update_device_trackers(self) -> None:
"""Update Freebox devices.""" """Update Freebox devices."""
new_device = False new_device = False
fbx_devices: [dict[str, Any]] = await self._api.lan.get_hosts_list() fbx_devices: list[dict[str, Any]] = await self._api.lan.get_hosts_list()
# Adds the Freebox itself # Adds the Freebox itself
fbx_devices.append( fbx_devices.append(
@ -164,7 +139,7 @@ class FreeboxRouter:
async def _update_disks_sensors(self) -> None: async def _update_disks_sensors(self) -> None:
"""Update Freebox disks.""" """Update Freebox disks."""
# None at first request # None at first request
fbx_disks: [dict[str, Any]] = await self._api.storage.get_disks() or [] fbx_disks: list[dict[str, Any]] = await self._api.storage.get_disks() or []
for fbx_disk in fbx_disks: for fbx_disk in fbx_disks:
self.disks[fbx_disk["id"]] = fbx_disk self.disks[fbx_disk["id"]] = fbx_disk
@ -175,10 +150,8 @@ class FreeboxRouter:
async def close(self) -> None: async def close(self) -> None:
"""Close the connection.""" """Close the connection."""
if self._api is not None: with suppress(NotOpenError):
await self._api.close() await self._api.close()
self._unsub_dispatcher()
self._api = None
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:

View file

@ -27,7 +27,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up the sensors.""" """Set up the sensors."""
router = hass.data[DOMAIN][entry.unique_id] router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
entities = [] entities = []
_LOGGER.debug( _LOGGER.debug(
@ -120,7 +120,7 @@ class FreeboxCallSensor(FreeboxSensor):
) -> None: ) -> None:
"""Initialize a Freebox call sensor.""" """Initialize a Freebox call sensor."""
super().__init__(router, description) super().__init__(router, description)
self._call_list_for_type = [] self._call_list_for_type: list[dict[str, Any]] = []
@callback @callback
def async_update_state(self) -> None: def async_update_state(self) -> None:

View file

@ -21,7 +21,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up the switch.""" """Set up the switch."""
router = hass.data[DOMAIN][entry.unique_id] router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
async_add_entities([FreeboxWifiSwitch(router)], True) async_add_entities([FreeboxWifiSwitch(router)], True)
@ -31,7 +31,7 @@ class FreeboxWifiSwitch(SwitchEntity):
def __init__(self, router: FreeboxRouter) -> None: def __init__(self, router: FreeboxRouter) -> None:
"""Initialize the Wifi switch.""" """Initialize the Wifi switch."""
self._name = "Freebox WiFi" self._name = "Freebox WiFi"
self._state = None self._state: bool | None = None
self._router = router self._router = router
self._unique_id = f"{self._router.mac} {self._name}" self._unique_id = f"{self._router.mac} {self._name}"
@ -46,7 +46,7 @@ class FreeboxWifiSwitch(SwitchEntity):
return self._name return self._name
@property @property
def is_on(self) -> bool: def is_on(self) -> bool | None:
"""Return true if device is on.""" """Return true if device is on."""
return self._state return self._state

View file

@ -2050,9 +2050,6 @@ ignore_errors = true
[mypy-homeassistant.components.firmata.*] [mypy-homeassistant.components.firmata.*]
ignore_errors = true ignore_errors = true
[mypy-homeassistant.components.freebox.*]
ignore_errors = true
[mypy-homeassistant.components.geniushub.*] [mypy-homeassistant.components.geniushub.*]
ignore_errors = true ignore_errors = true

View file

@ -26,7 +26,6 @@ IGNORED_MODULES: Final[list[str]] = [
"homeassistant.components.evohome.*", "homeassistant.components.evohome.*",
"homeassistant.components.fireservicerota.*", "homeassistant.components.fireservicerota.*",
"homeassistant.components.firmata.*", "homeassistant.components.firmata.*",
"homeassistant.components.freebox.*",
"homeassistant.components.geniushub.*", "homeassistant.components.geniushub.*",
"homeassistant.components.google_assistant.*", "homeassistant.components.google_assistant.*",
"homeassistant.components.gree.*", "homeassistant.components.gree.*",