diff --git a/.strict-typing b/.strict-typing index e64a9a04931..45d11f089dd 100644 --- a/.strict-typing +++ b/.strict-typing @@ -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.* diff --git a/homeassistant/components/litterrobot/__init__.py b/homeassistant/components/litterrobot/__init__.py index a2612966a98..f3b150f2c1d 100644 --- a/homeassistant/components/litterrobot/__init__.py +++ b/homeassistant/components/litterrobot/__init__.py @@ -1,4 +1,5 @@ """The Litter-Robot integration.""" +from __future__ import annotations from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index c49d18c5257..fbe32fa9749 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -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 = {} diff --git a/homeassistant/components/litterrobot/entity.py b/homeassistant/components/litterrobot/entity.py index 51b88bb4f79..501b71fbd06 100644 --- a/homeassistant/components/litterrobot/entity.py +++ b/homeassistant/components/litterrobot/entity.py @@ -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): diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 43d60e534ea..bde4c780482 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -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 diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 01deaa302cf..b6dd2a976c3 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -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", diff --git a/homeassistant/components/litterrobot/switch.py b/homeassistant/components/litterrobot/switch.py index 4d302a0d4ae..5374add1e34 100644 --- a/homeassistant/components/litterrobot/switch.py +++ b/homeassistant/components/litterrobot/switch.py @@ -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)) diff --git a/mypy.ini b/mypy.ini index ee953f13d74..af6abe6658f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -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