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.light.*
homeassistant.components.lifx.*
homeassistant.components.litterrobot.*
homeassistant.components.local_ip.*
homeassistant.components.lock.*
homeassistant.components.logbook.*

View File

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

View File

@ -1,11 +1,16 @@
"""Config flow for Litter-Robot integration."""
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from .const import DOMAIN
from .hub import LitterRobotHub
@ -22,7 +27,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
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."""
errors = {}

View File

@ -1,29 +1,35 @@
"""Litter-Robot entities for common data and methods."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from datetime import time
import logging
from types import MethodType
from typing import Any
from pylitterbot import Robot
from pylitterbot.exceptions import InvalidCommandException
from typing_extensions import ParamSpec
from homeassistant.core import CALLBACK_TYPE, callback
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
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
from .const import DOMAIN
from .hub import LitterRobotHub
_P = ParamSpec("_P")
_LOGGER = logging.getLogger(__name__)
REFRESH_WAIT_TIME_SECONDS = 8
class LitterRobotEntity(CoordinatorEntity):
class LitterRobotEntity(CoordinatorEntity[DataUpdateCoordinator[bool]]):
"""Generic Litter-Robot entity representing common data and methods."""
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
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:
"""Perform an action and initiates a refresh of the robot data after a few seconds."""
success = False
@ -82,7 +91,7 @@ class LitterRobotControlEntity(LitterRobotEntity):
)
return success
async def async_call_later_callback(self, *_) -> None:
async def async_call_later_callback(self, *_: Any) -> None:
"""Perform refresh request on callback."""
self._refresh_callback = None
await self.coordinator.async_request_refresh()
@ -92,7 +101,7 @@ class LitterRobotControlEntity(LitterRobotEntity):
self.async_cancel_refresh_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."""
if self._refresh_callback is not None:
self._refresh_callback()
@ -126,10 +135,10 @@ class LitterRobotConfigEntity(LitterRobotControlEntity):
def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None:
"""Init a Litter-Robot control entity."""
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(
self, action: MethodType, assumed_state: Any
self, action: Callable[[bool], Coroutine[Any, Any, bool]], assumed_state: bool
) -> None:
"""Perform an action and assume the state passed in if call is successful."""
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 datetime import timedelta
import logging
from typing import Any
from pylitterbot import Account
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException
@ -24,7 +25,7 @@ class LitterRobotHub:
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."""
self._data = data

View File

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

View File

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