From 39bf304031fe3ab6d600cf54af3f9dbbc57c3191 Mon Sep 17 00:00:00 2001 From: Yuval Aboulafia Date: Tue, 22 Jun 2021 12:50:50 +0300 Subject: [PATCH] Static typing for PiHole (#51681) Co-authored-by: Martin Hjelmare --- .strict-typing | 1 + homeassistant/components/pi_hole/__init__.py | 30 ++++++++++------ .../components/pi_hole/binary_sensor.py | 15 +++++--- .../components/pi_hole/config_flow.py | 30 +++++++++++----- homeassistant/components/pi_hole/sensor.py | 34 ++++++++++++++----- homeassistant/components/pi_hole/switch.py | 26 +++++++++----- mypy.ini | 11 ++++++ 7 files changed, 107 insertions(+), 40 deletions(-) diff --git a/.strict-typing b/.strict-typing index c87b0b270a8..1112dfba9ae 100644 --- a/.strict-typing +++ b/.strict-typing @@ -56,6 +56,7 @@ homeassistant.components.notify.* homeassistant.components.number.* homeassistant.components.onewire.* homeassistant.components.persistent_notification.* +homeassistant.components.pi_hole.* homeassistant.components.proximity.* homeassistant.components.recorder.purge homeassistant.components.recorder.repack diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py index 34fdc9978c1..ab9191b0f4a 100644 --- a/homeassistant/components/pi_hole/__init__.py +++ b/homeassistant/components/pi_hole/__init__.py @@ -1,11 +1,13 @@ """The pi_hole component.""" +from __future__ import annotations + import logging from hole import Hole from hole.exceptions import HoleError import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -13,10 +15,12 @@ from homeassistant.const import ( CONF_SSL, CONF_VERIFY_SSL, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -60,7 +64,7 @@ CONFIG_SCHEMA = vol.Schema( ) -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Pi-hole integration.""" hass.data[DOMAIN] = {} @@ -77,7 +81,7 @@ async def async_setup(hass, config): return True -async def async_setup_entry(hass, entry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Pi-hole entry.""" name = entry.data[CONF_NAME] host = entry.data[CONF_HOST] @@ -109,7 +113,7 @@ async def async_setup_entry(hass, entry): _LOGGER.warning("Failed to connect: %s", ex) raise ConfigEntryNotReady from ex - async def async_update_data(): + async def async_update_data() -> None: """Fetch data from API endpoint.""" try: await api.get_data() @@ -133,7 +137,7 @@ async def async_setup_entry(hass, entry): return True -async def async_unload_entry(hass, entry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Pi-hole entry.""" unload_ok = await hass.config_entries.async_unload_platforms( entry, _async_platforms(entry) @@ -144,7 +148,7 @@ async def async_unload_entry(hass, entry): @callback -def _async_platforms(entry): +def _async_platforms(entry: ConfigEntry) -> list[str]: """Return platforms to be loaded / unloaded.""" platforms = ["sensor"] if not entry.data[CONF_STATISTICS_ONLY]: @@ -157,7 +161,13 @@ def _async_platforms(entry): class PiHoleEntity(CoordinatorEntity): """Representation of a Pi-hole entity.""" - def __init__(self, api, coordinator, name, server_unique_id): + def __init__( + self, + api: Hole, + coordinator: DataUpdateCoordinator, + name: str, + server_unique_id: str, + ) -> None: """Initialize a Pi-hole entity.""" super().__init__(coordinator) self.api = api @@ -165,12 +175,12 @@ class PiHoleEntity(CoordinatorEntity): self._server_unique_id = server_unique_id @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend, if any.""" return "mdi:pi-hole" @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return the device information of the entity.""" return { "identifiers": {(DOMAIN, self._server_unique_id)}, diff --git a/homeassistant/components/pi_hole/binary_sensor.py b/homeassistant/components/pi_hole/binary_sensor.py index 714a9f669c8..3c322d324d3 100644 --- a/homeassistant/components/pi_hole/binary_sensor.py +++ b/homeassistant/components/pi_hole/binary_sensor.py @@ -1,12 +1,17 @@ """Support for getting status from a Pi-hole system.""" from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import PiHoleEntity from .const import DATA_KEY_API, DATA_KEY_COORDINATOR, DOMAIN as PIHOLE_DOMAIN -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the Pi-hole binary sensor.""" name = entry.data[CONF_NAME] hole_data = hass.data[PIHOLE_DOMAIN][entry.entry_id] @@ -25,16 +30,16 @@ class PiHoleBinarySensor(PiHoleEntity, BinarySensorEntity): """Representation of a Pi-hole binary sensor.""" @property - def name(self): + def name(self) -> str: """Return the name of the sensor.""" return self._name @property - def unique_id(self): + def unique_id(self) -> str: """Return the unique id of the sensor.""" return f"{self._server_unique_id}/Status" @property - def is_on(self): + def is_on(self) -> bool: """Return if the service is on.""" - return self.api.data.get("status") == "enabled" + return self.api.data.get("status") == "enabled" # type: ignore[no-any-return] diff --git a/homeassistant/components/pi_hole/config_flow.py b/homeassistant/components/pi_hole/config_flow.py index 68f0ecbbb2c..cccd80472e3 100644 --- a/homeassistant/components/pi_hole/config_flow.py +++ b/homeassistant/components/pi_hole/config_flow.py @@ -1,5 +1,8 @@ """Config flow to configure the Pi-hole integration.""" +from __future__ import annotations + import logging +from typing import Any from hole import Hole from hole.exceptions import HoleError @@ -24,6 +27,7 @@ from homeassistant.const import ( CONF_SSL, CONF_VERIFY_SSL, ) +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession _LOGGER = logging.getLogger(__name__) @@ -34,19 +38,25 @@ class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): + def __init__(self) -> None: """Initialize the config flow.""" - self._config = None + self._config: dict = {} - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initiated by the user.""" return await self.async_step_init(user_input) - async def async_step_import(self, user_input=None): + async def async_step_import( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initiated by import.""" return await self.async_step_init(user_input, is_import=True) - async def async_step_init(self, user_input, is_import=False): + async def async_step_init( + self, user_input: dict[str, Any] | None, is_import: bool = False + ) -> FlowResult: """Handle init step of a flow.""" errors = {} @@ -131,7 +141,9 @@ class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_api_key(self, user_input=None): + async def async_step_api_key( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle step to setup API key.""" if user_input is not None: return self.async_create_entry( @@ -147,14 +159,16 @@ class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema=vol.Schema({vol.Optional(CONF_API_KEY): str}), ) - async def _async_endpoint_existed(self, endpoint): + async def _async_endpoint_existed(self, endpoint: str) -> bool: existing_endpoints = [ f"{entry.data.get(CONF_HOST)}/{entry.data.get(CONF_LOCATION)}" for entry in self._async_current_entries() ] return endpoint in existing_endpoints - async def _async_try_connect(self, host, location, tls, verify_tls): + async def _async_try_connect( + self, host: str, location: str, tls: bool, verify_tls: bool + ) -> None: session = async_get_clientsession(self.hass, verify_tls) pi_hole = Hole(host, self.hass.loop, session, location=location, tls=tls) await pi_hole.get_data() diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index 517e8cfcf17..95aee56f7cc 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -1,7 +1,16 @@ """Support for getting statistical data from a Pi-hole system.""" +from __future__ import annotations + +from typing import Any + +from hole import Hole from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from . import PiHoleEntity from .const import ( @@ -14,7 +23,9 @@ from .const import ( ) -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the Pi-hole sensor.""" name = entry.data[CONF_NAME] hole_data = hass.data[PIHOLE_DOMAIN][entry.entry_id] @@ -34,7 +45,14 @@ async def async_setup_entry(hass, entry, async_add_entities): class PiHoleSensor(PiHoleEntity, SensorEntity): """Representation of a Pi-hole sensor.""" - def __init__(self, api, coordinator, name, sensor_name, server_unique_id): + def __init__( + self, + api: Hole, + coordinator: DataUpdateCoordinator, + name: str, + sensor_name: str, + server_unique_id: str, + ) -> None: """Initialize a Pi-hole sensor.""" super().__init__(api, coordinator, name, server_unique_id) @@ -46,27 +64,27 @@ class PiHoleSensor(PiHoleEntity, SensorEntity): self._icon = variable_info[2] @property - def name(self): + def name(self) -> str: """Return the name of the sensor.""" return f"{self._name} {self._condition_name}" @property - def unique_id(self): + def unique_id(self) -> str: """Return the unique id of the sensor.""" return f"{self._server_unique_id}/{self._condition_name}" @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend, if any.""" return self._icon @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit the value is expressed in.""" return self._unit_of_measurement @property - def state(self): + def state(self) -> Any: """Return the state of the device.""" try: return round(self.api.data[self._condition], 2) @@ -74,6 +92,6 @@ class PiHoleSensor(PiHoleEntity, SensorEntity): return self.api.data[self._condition] @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the Pi-hole.""" return {ATTR_BLOCKED_DOMAINS: self.api.data["domains_being_blocked"]} diff --git a/homeassistant/components/pi_hole/switch.py b/homeassistant/components/pi_hole/switch.py index 955585243cf..b0c4b09c2e7 100644 --- a/homeassistant/components/pi_hole/switch.py +++ b/homeassistant/components/pi_hole/switch.py @@ -1,12 +1,18 @@ """Support for turning on and off Pi-hole system.""" +from __future__ import annotations + import logging +from typing import Any from hole.exceptions import HoleError import voluptuous as vol from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import PiHoleEntity from .const import ( @@ -20,7 +26,9 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the Pi-hole switch.""" name = entry.data[CONF_NAME] hole_data = hass.data[PIHOLE_DOMAIN][entry.entry_id] @@ -51,26 +59,26 @@ class PiHoleSwitch(PiHoleEntity, SwitchEntity): """Representation of a Pi-hole switch.""" @property - def name(self): + def name(self) -> str: """Return the name of the switch.""" return self._name @property - def unique_id(self): + def unique_id(self) -> str: """Return the unique id of the switch.""" return f"{self._server_unique_id}/Switch" @property - def icon(self): + def icon(self) -> str: """Icon to use in the frontend, if any.""" return "mdi:pi-hole" @property - def is_on(self): + def is_on(self) -> bool: """Return if the service is on.""" - return self.api.data.get("status") == "enabled" + return self.api.data.get("status") == "enabled" # type: ignore[no-any-return] - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the service.""" try: await self.api.enable() @@ -78,11 +86,11 @@ class PiHoleSwitch(PiHoleEntity, SwitchEntity): except HoleError as err: _LOGGER.error("Unable to enable Pi-hole: %s", err) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the service.""" await self.async_disable() - async def async_disable(self, duration=None): + async def async_disable(self, duration: Any = None) -> None: """Disable the service for a given duration.""" duration_seconds = True # Disable infinitely by default if duration is not None: diff --git a/mypy.ini b/mypy.ini index f80f2baa9e3..50257141179 100644 --- a/mypy.ini +++ b/mypy.ini @@ -627,6 +627,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.pi_hole.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.proximity.*] check_untyped_defs = true disallow_incomplete_defs = true