Enforce strict typing for IQVIA (#53408)

* Enforce strict typing for IQVIA

* Cleanup

* Code review

* Ignore untyped numpy function
This commit is contained in:
Aaron Bach 2021-09-11 12:27:13 -06:00 committed by GitHub
parent 1b46190a0c
commit ed9b271fd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 17 deletions

View File

@ -54,6 +54,7 @@ homeassistant.components.huawei_lte.*
homeassistant.components.hyperion.* homeassistant.components.hyperion.*
homeassistant.components.image_processing.* homeassistant.components.image_processing.*
homeassistant.components.integration.* homeassistant.components.integration.*
homeassistant.components.iqvia.*
homeassistant.components.knx.* homeassistant.components.knx.*
homeassistant.components.kraken.* homeassistant.components.kraken.*
homeassistant.components.lcn.* homeassistant.components.lcn.*

View File

@ -1,14 +1,19 @@
"""Support for IQVIA.""" """Support for IQVIA."""
from __future__ import annotations
import asyncio import asyncio
from collections.abc import Awaitable
from datetime import timedelta from datetime import timedelta
from functools import partial from functools import partial
from typing import Any, Callable, Dict, cast
from pyiqvia import Client from pyiqvia import Client
from pyiqvia.errors import IQVIAError from pyiqvia.errors import IQVIAError
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
@ -37,7 +42,7 @@ DEFAULT_SCAN_INTERVAL = timedelta(minutes=30)
PLATFORMS = ["sensor"] PLATFORMS = ["sensor"]
async def async_setup_entry(hass, entry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up IQVIA as config entry.""" """Set up IQVIA as config entry."""
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
coordinators = {} coordinators = {}
@ -51,13 +56,17 @@ async def async_setup_entry(hass, entry):
websession = aiohttp_client.async_get_clientsession(hass) websession = aiohttp_client.async_get_clientsession(hass)
client = Client(entry.data[CONF_ZIP_CODE], session=websession) client = Client(entry.data[CONF_ZIP_CODE], session=websession)
async def async_get_data_from_api(api_coro): async def async_get_data_from_api(
api_coro: Callable[..., Awaitable]
) -> dict[str, Any]:
"""Get data from a particular API coroutine.""" """Get data from a particular API coroutine."""
try: try:
return await api_coro() data = await api_coro()
except IQVIAError as err: except IQVIAError as err:
raise UpdateFailed from err raise UpdateFailed from err
return cast(Dict[str, Any], data)
init_data_update_tasks = [] init_data_update_tasks = []
for sensor_type, api_coro in ( for sensor_type, api_coro in (
(TYPE_ALLERGY_FORECAST, client.allergens.extended), (TYPE_ALLERGY_FORECAST, client.allergens.extended),
@ -90,7 +99,7 @@ async def async_setup_entry(hass, entry):
return True return True
async def async_unload_entry(hass, entry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload an OpenUV config entry.""" """Unload an OpenUV 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:
@ -101,7 +110,14 @@ async def async_unload_entry(hass, entry):
class IQVIAEntity(CoordinatorEntity, SensorEntity): class IQVIAEntity(CoordinatorEntity, SensorEntity):
"""Define a base IQVIA entity.""" """Define a base IQVIA entity."""
def __init__(self, coordinator, entry, sensor_type, name, icon): def __init__(
self,
coordinator: DataUpdateCoordinator,
entry: ConfigEntry,
sensor_type: str,
name: str,
icon: str,
) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
@ -122,7 +138,7 @@ class IQVIAEntity(CoordinatorEntity, SensorEntity):
self.update_from_latest_data() self.update_from_latest_data()
self.async_write_ha_state() self.async_write_ha_state()
async def async_added_to_hass(self): async def async_added_to_hass(self) -> None:
"""Register callbacks.""" """Register callbacks."""
await super().async_added_to_hass() await super().async_added_to_hass()
@ -136,6 +152,6 @@ class IQVIAEntity(CoordinatorEntity, SensorEntity):
self.update_from_latest_data() self.update_from_latest_data()
@callback @callback
def update_from_latest_data(self): def update_from_latest_data(self) -> None:
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
raise NotImplementedError raise NotImplementedError

View File

@ -1,9 +1,14 @@
"""Config flow to configure the IQVIA component.""" """Config flow to configure the IQVIA component."""
from __future__ import annotations
from typing import Any
from pyiqvia import Client from pyiqvia import Client
from pyiqvia.errors import InvalidZipError from pyiqvia.errors import InvalidZipError
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import CONF_ZIP_CODE, DOMAIN from .const import CONF_ZIP_CODE, DOMAIN
@ -14,11 +19,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
def __init__(self): def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self.data_schema = vol.Schema({vol.Required(CONF_ZIP_CODE): str}) self.data_schema = vol.Schema({vol.Required(CONF_ZIP_CODE): str})
async def async_step_user(self, user_input=None): async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the start of the config flow.""" """Handle the start of the config flow."""
if not user_input: if not user_input:
return self.async_show_form(step_id="user", data_schema=self.data_schema) return self.async_show_form(step_id="user", data_schema=self.data_schema)

View File

@ -1,10 +1,14 @@
"""Support for IQVIA sensors.""" """Support for IQVIA sensors."""
from __future__ import annotations
from statistics import mean from statistics import mean
import numpy as np import numpy as np
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_STATE from homeassistant.const import ATTR_STATE
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import IQVIAEntity from . import IQVIAEntity
from .const import ( from .const import (
@ -58,7 +62,9 @@ TREND_INCREASING = "Increasing"
TREND_SUBSIDING = "Subsiding" TREND_SUBSIDING = "Subsiding"
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 IQVIA sensors based on a config entry.""" """Set up IQVIA sensors based on a config entry."""
sensor_class_mapping = { sensor_class_mapping = {
TYPE_ALLERGY_FORECAST: ForecastSensor, TYPE_ALLERGY_FORECAST: ForecastSensor,
@ -76,17 +82,17 @@ async def async_setup_entry(hass, entry, async_add_entities):
api_category = API_CATEGORY_MAPPING.get(sensor_type, sensor_type) api_category = API_CATEGORY_MAPPING.get(sensor_type, sensor_type)
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][api_category] coordinator = hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][api_category]
sensor_class = sensor_class_mapping[sensor_type] sensor_class = sensor_class_mapping[sensor_type]
sensors.append(sensor_class(coordinator, entry, sensor_type, name, icon)) sensors.append(sensor_class(coordinator, entry, sensor_type, name, icon))
async_add_entities(sensors) async_add_entities(sensors)
def calculate_trend(indices): @callback
def calculate_trend(indices: list[float]) -> str:
"""Calculate the "moving average" of a set of indices.""" """Calculate the "moving average" of a set of indices."""
index_range = np.arange(0, len(indices)) index_range = np.arange(0, len(indices))
index_array = np.array(indices) index_array = np.array(indices)
linear_fit = np.polyfit(index_range, index_array, 1) linear_fit = np.polyfit(index_range, index_array, 1) # type: ignore
slope = round(linear_fit[0], 2) slope = round(linear_fit[0], 2)
if slope > 0: if slope > 0:
@ -102,7 +108,7 @@ class ForecastSensor(IQVIAEntity):
"""Define sensor related to forecast data.""" """Define sensor related to forecast data."""
@callback @callback
def update_from_latest_data(self): def update_from_latest_data(self) -> None:
"""Update the sensor.""" """Update the sensor."""
if not self.available: if not self.available:
return return
@ -151,7 +157,7 @@ class IndexSensor(IQVIAEntity):
"""Define sensor related to indices.""" """Define sensor related to indices."""
@callback @callback
def update_from_latest_data(self): def update_from_latest_data(self) -> None:
"""Update the sensor.""" """Update the sensor."""
if not self.coordinator.last_update_success: if not self.coordinator.last_update_success:
return return

View File

@ -605,6 +605,17 @@ no_implicit_optional = true
warn_return_any = true warn_return_any = true
warn_unreachable = true warn_unreachable = true
[mypy-homeassistant.components.iqvia.*]
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.knx.*] [mypy-homeassistant.components.knx.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true