Static typing for PiHole (#51681)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Yuval Aboulafia 2021-06-22 12:50:50 +03:00 committed by GitHub
parent 9cd3ffbd47
commit 39bf304031
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 107 additions and 40 deletions

View File

@ -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

View File

@ -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)},

View File

@ -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]

View File

@ -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()

View File

@ -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"]}

View File

@ -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:

View File

@ -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