Enable strict typing - wallbox (#59301)

This commit is contained in:
Marc Mueller 2021-11-23 22:30:22 +01:00 committed by GitHub
parent ce369bb336
commit 6089aef072
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 41 deletions

View file

@ -141,6 +141,7 @@ homeassistant.components.vacuum.*
homeassistant.components.vallox.* homeassistant.components.vallox.*
homeassistant.components.velbus.* homeassistant.components.velbus.*
homeassistant.components.vlc_telnet.* homeassistant.components.vlc_telnet.*
homeassistant.components.wallbox.*
homeassistant.components.water_heater.* homeassistant.components.water_heater.*
homeassistant.components.watttime.* homeassistant.components.watttime.*
homeassistant.components.weather.* homeassistant.components.weather.*

View file

@ -1,7 +1,10 @@
"""The Wallbox integration.""" """The Wallbox integration."""
from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from http import HTTPStatus from http import HTTPStatus
import logging import logging
from typing import Any, Dict
import requests import requests
from wallbox import Wallbox from wallbox import Wallbox
@ -20,10 +23,10 @@ PLATFORMS = ["sensor", "number"]
UPDATE_INTERVAL = 30 UPDATE_INTERVAL = 30
class WallboxCoordinator(DataUpdateCoordinator): class WallboxCoordinator(DataUpdateCoordinator[Dict[str, Any]]):
"""Wallbox Coordinator class.""" """Wallbox Coordinator class."""
def __init__(self, station, wallbox, hass): def __init__(self, station: str, wallbox: Wallbox, hass: HomeAssistant) -> None:
"""Initialize.""" """Initialize."""
self._station = station self._station = station
self._wallbox = wallbox self._wallbox = wallbox
@ -35,31 +38,29 @@ class WallboxCoordinator(DataUpdateCoordinator):
update_interval=timedelta(seconds=UPDATE_INTERVAL), update_interval=timedelta(seconds=UPDATE_INTERVAL),
) )
def _authenticate(self): def _authenticate(self) -> None:
"""Authenticate using Wallbox API.""" """Authenticate using Wallbox API."""
try: try:
self._wallbox.authenticate() self._wallbox.authenticate()
return True
except requests.exceptions.HTTPError as wallbox_connection_error: except requests.exceptions.HTTPError as wallbox_connection_error:
if wallbox_connection_error.response.status_code == HTTPStatus.FORBIDDEN: if wallbox_connection_error.response.status_code == HTTPStatus.FORBIDDEN:
raise ConfigEntryAuthFailed from wallbox_connection_error raise ConfigEntryAuthFailed from wallbox_connection_error
raise ConnectionError from wallbox_connection_error raise ConnectionError from wallbox_connection_error
def _validate(self): def _validate(self) -> None:
"""Authenticate using Wallbox API.""" """Authenticate using Wallbox API."""
try: try:
self._wallbox.authenticate() self._wallbox.authenticate()
return True
except requests.exceptions.HTTPError as wallbox_connection_error: except requests.exceptions.HTTPError as wallbox_connection_error:
if wallbox_connection_error.response.status_code == 403: if wallbox_connection_error.response.status_code == 403:
raise InvalidAuth from wallbox_connection_error raise InvalidAuth from wallbox_connection_error
raise ConnectionError from wallbox_connection_error raise ConnectionError from wallbox_connection_error
def _get_data(self): def _get_data(self) -> dict[str, Any]:
"""Get new sensor data for Wallbox component.""" """Get new sensor data for Wallbox component."""
try: try:
self._authenticate() self._authenticate()
data = self._wallbox.getChargerStatus(self._station) data: dict[str, Any] = self._wallbox.getChargerStatus(self._station)
data[CONF_MAX_CHARGING_CURRENT_KEY] = data[CONF_DATA_KEY][ data[CONF_MAX_CHARGING_CURRENT_KEY] = data[CONF_DATA_KEY][
CONF_MAX_CHARGING_CURRENT_KEY CONF_MAX_CHARGING_CURRENT_KEY
] ]
@ -69,7 +70,7 @@ class WallboxCoordinator(DataUpdateCoordinator):
except requests.exceptions.HTTPError as wallbox_connection_error: except requests.exceptions.HTTPError as wallbox_connection_error:
raise ConnectionError from wallbox_connection_error raise ConnectionError from wallbox_connection_error
def _set_charging_current(self, charging_current): def _set_charging_current(self, charging_current: float) -> None:
"""Set maximum charging current for Wallbox.""" """Set maximum charging current for Wallbox."""
try: try:
self._authenticate() self._authenticate()
@ -79,22 +80,21 @@ class WallboxCoordinator(DataUpdateCoordinator):
raise InvalidAuth from wallbox_connection_error raise InvalidAuth from wallbox_connection_error
raise ConnectionError from wallbox_connection_error raise ConnectionError from wallbox_connection_error
async def async_set_charging_current(self, charging_current): async def async_set_charging_current(self, charging_current: float) -> None:
"""Set maximum charging current for Wallbox.""" """Set maximum charging current for Wallbox."""
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(
self._set_charging_current, charging_current self._set_charging_current, charging_current
) )
await self.async_request_refresh() await self.async_request_refresh()
async def _async_update_data(self) -> bool: async def _async_update_data(self) -> dict[str, Any]:
"""Get new sensor data for Wallbox component.""" """Get new sensor data for Wallbox component."""
data = await self.hass.async_add_executor_job(self._get_data) data = await self.hass.async_add_executor_job(self._get_data)
return data return data
async def async_validate_input(self) -> bool: async def async_validate_input(self) -> None:
"""Get new sensor data for Wallbox component.""" """Get new sensor data for Wallbox component."""
data = await self.hass.async_add_executor_job(self._validate) await self.hass.async_add_executor_job(self._validate)
return data
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View file

@ -1,9 +1,14 @@
"""Config flow for Wallbox integration.""" """Config flow for Wallbox integration."""
from __future__ import annotations
from typing import Any
import voluptuous as vol import voluptuous as vol
from wallbox import Wallbox from wallbox import Wallbox
from homeassistant import config_entries, core from homeassistant import config_entries, core
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from . import InvalidAuth, WallboxCoordinator from . import InvalidAuth, WallboxCoordinator
from .const import CONF_STATION, DOMAIN from .const import CONF_STATION, DOMAIN
@ -19,7 +24,9 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
) )
async def validate_input(hass: core.HomeAssistant, data): async def validate_input(
hass: core.HomeAssistant, data: dict[str, Any]
) -> dict[str, str]:
"""Validate the user input allows to connect. """Validate the user input allows to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
@ -36,11 +43,13 @@ async def validate_input(hass: core.HomeAssistant, data):
class ConfigFlow(config_entries.ConfigFlow, domain=COMPONENT_DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=COMPONENT_DOMAIN):
"""Handle a config flow for Wallbox.""" """Handle a config flow for Wallbox."""
def __init__(self): def __init__(self) -> None:
"""Start the Wallbox config flow.""" """Start the Wallbox config flow."""
self._reauth_entry = None self._reauth_entry: config_entries.ConfigEntry | None = None
async def async_step_reauth(self, user_input=None): async def async_step_reauth(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Perform reauth upon an API authentication error.""" """Perform reauth upon an API authentication error."""
self._reauth_entry = self.hass.config_entries.async_get_entry( self._reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"] self.context["entry_id"]
@ -48,7 +57,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=COMPONENT_DOMAIN):
return await self.async_step_user() return await self.async_step_user()
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 initial step.""" """Handle the initial step."""
if user_input is None: if user_input is None:
return self.async_show_form( return self.async_show_form(

View file

@ -2,12 +2,16 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, cast
from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DEVICE_CLASS_CURRENT from homeassistant.const import DEVICE_CLASS_CURRENT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import InvalidAuth from . import InvalidAuth, WallboxCoordinator
from .const import CONF_MAX_AVAILABLE_POWER_KEY, CONF_MAX_CHARGING_CURRENT_KEY, DOMAIN from .const import CONF_MAX_AVAILABLE_POWER_KEY, CONF_MAX_CHARGING_CURRENT_KEY, DOMAIN
@ -28,9 +32,11 @@ NUMBER_TYPES: dict[str, WallboxNumberEntityDescription] = {
} }
async def async_setup_entry(hass, config, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Create wallbox sensor entities in HASS.""" """Create wallbox sensor entities in HASS."""
coordinator = hass.data[DOMAIN][config.entry_id] coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id]
# Check if the user is authorized to change current, if so, add number component: # Check if the user is authorized to change current, if so, add number component:
try: try:
await coordinator.async_set_charging_current( await coordinator.async_set_charging_current(
@ -41,7 +47,7 @@ async def async_setup_entry(hass, config, async_add_entities):
async_add_entities( async_add_entities(
[ [
WallboxNumber(coordinator, config, description) WallboxNumber(coordinator, entry, description)
for ent in coordinator.data for ent in coordinator.data
if (description := NUMBER_TYPES.get(ent)) if (description := NUMBER_TYPES.get(ent))
] ]
@ -52,27 +58,33 @@ class WallboxNumber(CoordinatorEntity, NumberEntity):
"""Representation of the Wallbox portal.""" """Representation of the Wallbox portal."""
entity_description: WallboxNumberEntityDescription entity_description: WallboxNumberEntityDescription
coordinator: WallboxCoordinator
def __init__( def __init__(
self, coordinator, config, description: WallboxNumberEntityDescription self,
): coordinator: WallboxCoordinator,
entry: ConfigEntry,
description: WallboxNumberEntityDescription,
) -> None:
"""Initialize a Wallbox sensor.""" """Initialize a Wallbox sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self.entity_description = description self.entity_description = description
self._coordinator = coordinator self._coordinator = coordinator
self._attr_name = f"{config.title} {description.name}" self._attr_name = f"{entry.title} {description.name}"
self._attr_min_value = description.min_value self._attr_min_value = description.min_value
@property @property
def max_value(self): def max_value(self) -> float:
"""Return the maximum available current.""" """Return the maximum available current."""
return self._coordinator.data[CONF_MAX_AVAILABLE_POWER_KEY] return cast(float, self._coordinator.data[CONF_MAX_AVAILABLE_POWER_KEY])
@property @property
def value(self): def value(self) -> float | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._coordinator.data[CONF_MAX_CHARGING_CURRENT_KEY] return cast(
Optional[float], self._coordinator.data[CONF_MAX_CHARGING_CURRENT_KEY]
)
async def async_set_value(self, value: float): async def async_set_value(self, value: float) -> None:
"""Set the value of the entity.""" """Set the value of the entity."""
await self._coordinator.async_set_charging_current(value) await self._coordinator.async_set_charging_current(value)

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import cast
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
@ -10,6 +11,8 @@ from homeassistant.components.sensor import (
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
) )
from homeassistant.components.wallbox import WallboxCoordinator
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
@ -21,6 +24,9 @@ from homeassistant.const import (
PERCENTAGE, PERCENTAGE,
POWER_KILO_WATT, POWER_KILO_WATT,
) )
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ( from .const import (
@ -130,13 +136,15 @@ SENSOR_TYPES: dict[str, WallboxSensorEntityDescription] = {
} }
async def async_setup_entry(hass, config, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Create wallbox sensor entities in HASS.""" """Create wallbox sensor entities in HASS."""
coordinator = hass.data[DOMAIN][config.entry_id] coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities( async_add_entities(
[ [
WallboxSensor(coordinator, config, description) WallboxSensor(coordinator, entry, description)
for ent in coordinator.data for ent in coordinator.data
if (description := SENSOR_TYPES.get(ent)) if (description := SENSOR_TYPES.get(ent))
] ]
@ -147,24 +155,31 @@ class WallboxSensor(CoordinatorEntity, SensorEntity):
"""Representation of the Wallbox portal.""" """Representation of the Wallbox portal."""
entity_description: WallboxSensorEntityDescription entity_description: WallboxSensorEntityDescription
coordinator: WallboxCoordinator
def __init__( def __init__(
self, coordinator, config, description: WallboxSensorEntityDescription self,
): coordinator: WallboxCoordinator,
entry: ConfigEntry,
description: WallboxSensorEntityDescription,
) -> None:
"""Initialize a Wallbox sensor.""" """Initialize a Wallbox sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self.entity_description = description self.entity_description = description
self._attr_name = f"{config.title} {description.name}" self._attr_name = f"{entry.title} {description.name}"
@property @property
def native_value(self): def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
if (sensor_round := self.entity_description.precision) is not None: if (sensor_round := self.entity_description.precision) is not None:
try: try:
return round( return cast(
StateType,
round(
self.coordinator.data[self.entity_description.key], sensor_round self.coordinator.data[self.entity_description.key], sensor_round
),
) )
except TypeError: except TypeError:
_LOGGER.debug("Cannot format %s", self._attr_name) _LOGGER.debug("Cannot format %s", self._attr_name)
return None return None
return self.coordinator.data[self.entity_description.key] return cast(StateType, self.coordinator.data[self.entity_description.key])

View file

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