Add strict typing for litterrobot (#75540)

This commit is contained in:
Marc Mueller 2022-07-25 22:52:13 +02:00 committed by GitHub
parent 3aa75f3fcc
commit 274584f2a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 53 additions and 22 deletions

View File

@ -152,6 +152,7 @@ homeassistant.components.laundrify.*
homeassistant.components.lcn.* homeassistant.components.lcn.*
homeassistant.components.light.* homeassistant.components.light.*
homeassistant.components.lifx.* homeassistant.components.lifx.*
homeassistant.components.litterrobot.*
homeassistant.components.local_ip.* homeassistant.components.local_ip.*
homeassistant.components.lock.* homeassistant.components.lock.*
homeassistant.components.logbook.* homeassistant.components.logbook.*

View File

@ -1,4 +1,5 @@
"""The Litter-Robot integration.""" """The Litter-Robot integration."""
from __future__ import annotations
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException

View File

@ -1,11 +1,16 @@
"""Config flow for Litter-Robot integration.""" """Config flow for Litter-Robot integration."""
from __future__ import annotations
from collections.abc import Mapping
import logging import logging
from typing import Any
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from .const import DOMAIN from .const import DOMAIN
from .hub import LitterRobotHub from .hub import LitterRobotHub
@ -22,7 +27,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
async def async_step_user(self, user_input=None): async def async_step_user(
self, user_input: Mapping[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step.""" """Handle the initial step."""
errors = {} errors = {}

View File

@ -1,29 +1,35 @@
"""Litter-Robot entities for common data and methods.""" """Litter-Robot entities for common data and methods."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Coroutine
from datetime import time from datetime import time
import logging import logging
from types import MethodType
from typing import Any from typing import Any
from pylitterbot import Robot from pylitterbot import Robot
from pylitterbot.exceptions import InvalidCommandException from pylitterbot.exceptions import InvalidCommandException
from typing_extensions import ParamSpec
from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.core import CALLBACK_TYPE, callback
from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from .const import DOMAIN from .const import DOMAIN
from .hub import LitterRobotHub from .hub import LitterRobotHub
_P = ParamSpec("_P")
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REFRESH_WAIT_TIME_SECONDS = 8 REFRESH_WAIT_TIME_SECONDS = 8
class LitterRobotEntity(CoordinatorEntity): class LitterRobotEntity(CoordinatorEntity[DataUpdateCoordinator[bool]]):
"""Generic Litter-Robot entity representing common data and methods.""" """Generic Litter-Robot entity representing common data and methods."""
def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None: def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None:
@ -63,7 +69,10 @@ class LitterRobotControlEntity(LitterRobotEntity):
self._refresh_callback: CALLBACK_TYPE | None = None self._refresh_callback: CALLBACK_TYPE | None = None
async def perform_action_and_refresh( async def perform_action_and_refresh(
self, action: MethodType, *args: Any, **kwargs: Any self,
action: Callable[_P, Coroutine[Any, Any, bool]],
*args: _P.args,
**kwargs: _P.kwargs,
) -> bool: ) -> bool:
"""Perform an action and initiates a refresh of the robot data after a few seconds.""" """Perform an action and initiates a refresh of the robot data after a few seconds."""
success = False success = False
@ -82,7 +91,7 @@ class LitterRobotControlEntity(LitterRobotEntity):
) )
return success return success
async def async_call_later_callback(self, *_) -> None: async def async_call_later_callback(self, *_: Any) -> None:
"""Perform refresh request on callback.""" """Perform refresh request on callback."""
self._refresh_callback = None self._refresh_callback = None
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
@ -92,7 +101,7 @@ class LitterRobotControlEntity(LitterRobotEntity):
self.async_cancel_refresh_callback() self.async_cancel_refresh_callback()
@callback @callback
def async_cancel_refresh_callback(self): def async_cancel_refresh_callback(self) -> None:
"""Clear the refresh callback if it has not already fired.""" """Clear the refresh callback if it has not already fired."""
if self._refresh_callback is not None: if self._refresh_callback is not None:
self._refresh_callback() self._refresh_callback()
@ -126,10 +135,10 @@ class LitterRobotConfigEntity(LitterRobotControlEntity):
def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None: def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None:
"""Init a Litter-Robot control entity.""" """Init a Litter-Robot control entity."""
super().__init__(robot=robot, entity_type=entity_type, hub=hub) super().__init__(robot=robot, entity_type=entity_type, hub=hub)
self._assumed_state: Any = None self._assumed_state: bool | None = None
async def perform_action_and_assume_state( async def perform_action_and_assume_state(
self, action: MethodType, assumed_state: Any self, action: Callable[[bool], Coroutine[Any, Any, bool]], assumed_state: bool
) -> None: ) -> None:
"""Perform an action and assume the state passed in if call is successful.""" """Perform an action and assume the state passed in if call is successful."""
if await self.perform_action_and_refresh(action, assumed_state): if await self.perform_action_and_refresh(action, assumed_state):

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any
from pylitterbot import Account from pylitterbot import Account
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException
@ -24,7 +25,7 @@ class LitterRobotHub:
account: Account account: Account
def __init__(self, hass: HomeAssistant, data: Mapping) -> None: def __init__(self, hass: HomeAssistant, data: Mapping[str, Any]) -> None:
"""Initialize the Litter-Robot hub.""" """Initialize the Litter-Robot hub."""
self._data = data self._data = data

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Any from typing import Any, Union, cast
from pylitterbot.robot import Robot from pylitterbot.robot import Robot
@ -12,7 +12,6 @@ from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
StateType,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE from homeassistant.const import PERCENTAGE
@ -61,12 +60,12 @@ class LitterRobotSensorEntity(LitterRobotEntity, SensorEntity):
self.entity_description = description self.entity_description = description
@property @property
def native_value(self) -> StateType | datetime: def native_value(self) -> float | datetime | str | None:
"""Return the state.""" """Return the state."""
if self.entity_description.should_report(self.robot): if self.entity_description.should_report(self.robot):
if isinstance(val := getattr(self.robot, self.entity_description.key), str): if isinstance(val := getattr(self.robot, self.entity_description.key), str):
return val.lower() return val.lower()
return val return cast(Union[float, datetime, None], val)
return None return None
@property @property
@ -88,13 +87,13 @@ ROBOT_SENSORS = [
name="Sleep Mode Start Time", name="Sleep Mode Start Time",
key="sleep_mode_start_time", key="sleep_mode_start_time",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
should_report=lambda robot: robot.sleep_mode_enabled, should_report=lambda robot: robot.sleep_mode_enabled, # type: ignore[no-any-return]
), ),
LitterRobotSensorEntityDescription( LitterRobotSensorEntityDescription(
name="Sleep Mode End Time", name="Sleep Mode End Time",
key="sleep_mode_end_time", key="sleep_mode_end_time",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
should_report=lambda robot: robot.sleep_mode_enabled, should_report=lambda robot: robot.sleep_mode_enabled, # type: ignore[no-any-return]
), ),
LitterRobotSensorEntityDescription( LitterRobotSensorEntityDescription(
name="Last Seen", name="Last Seen",

View File

@ -17,11 +17,11 @@ class LitterRobotNightLightModeSwitch(LitterRobotConfigEntity, SwitchEntity):
"""Litter-Robot Night Light Mode Switch.""" """Litter-Robot Night Light Mode Switch."""
@property @property
def is_on(self) -> bool: def is_on(self) -> bool | None:
"""Return true if switch is on.""" """Return true if switch is on."""
if self._refresh_callback is not None: if self._refresh_callback is not None:
return self._assumed_state return self._assumed_state
return self.robot.night_light_mode_enabled return self.robot.night_light_mode_enabled # type: ignore[no-any-return]
@property @property
def icon(self) -> str: def icon(self) -> str:
@ -41,11 +41,11 @@ class LitterRobotPanelLockoutSwitch(LitterRobotConfigEntity, SwitchEntity):
"""Litter-Robot Panel Lockout Switch.""" """Litter-Robot Panel Lockout Switch."""
@property @property
def is_on(self) -> bool: def is_on(self) -> bool | None:
"""Return true if switch is on.""" """Return true if switch is on."""
if self._refresh_callback is not None: if self._refresh_callback is not None:
return self._assumed_state return self._assumed_state
return self.robot.panel_lock_enabled return self.robot.panel_lock_enabled # type: ignore[no-any-return]
@property @property
def icon(self) -> str: def icon(self) -> str:
@ -61,7 +61,9 @@ class LitterRobotPanelLockoutSwitch(LitterRobotConfigEntity, SwitchEntity):
await self.perform_action_and_assume_state(self.robot.set_panel_lockout, False) await self.perform_action_and_assume_state(self.robot.set_panel_lockout, False)
ROBOT_SWITCHES: list[tuple[type[LitterRobotConfigEntity], str]] = [ ROBOT_SWITCHES: list[
tuple[type[LitterRobotNightLightModeSwitch | LitterRobotPanelLockoutSwitch], str]
] = [
(LitterRobotNightLightModeSwitch, "Night Light Mode"), (LitterRobotNightLightModeSwitch, "Night Light Mode"),
(LitterRobotPanelLockoutSwitch, "Panel Lockout"), (LitterRobotPanelLockoutSwitch, "Panel Lockout"),
] ]
@ -75,7 +77,7 @@ async def async_setup_entry(
"""Set up Litter-Robot switches using config entry.""" """Set up Litter-Robot switches using config entry."""
hub: LitterRobotHub = hass.data[DOMAIN][entry.entry_id] hub: LitterRobotHub = hass.data[DOMAIN][entry.entry_id]
entities = [] entities: list[SwitchEntity] = []
for robot in hub.account.robots: for robot in hub.account.robots:
for switch_class, switch_type in ROBOT_SWITCHES: for switch_class, switch_type in ROBOT_SWITCHES:
entities.append(switch_class(robot=robot, entity_type=switch_type, hub=hub)) entities.append(switch_class(robot=robot, entity_type=switch_type, hub=hub))

View File

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