Enable ruff check for ANN (#1139)

This commit is contained in:
Teemu R.
2024-11-10 19:55:13 +01:00
committed by GitHub
parent 6b44fe6242
commit 66eb17057e
89 changed files with 596 additions and 452 deletions

View File

@@ -15,7 +15,9 @@ class SmartLightEffect(LightEffectInterface, ABC):
"""
@abstractmethod
async def set_brightness(self, brightness: int, *, transition: int | None = None):
async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> dict:
"""Set effect brightness."""
@property

View File

@@ -20,7 +20,7 @@ class Alarm(SmartModule):
"get_support_alarm_type_list": None, # This should be needed only once
}
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features.
This is implemented as some features depend on device responses.
@@ -100,7 +100,7 @@ class Alarm(SmartModule):
"""Return current alarm sound."""
return self.data["get_alarm_configure"]["type"]
async def set_alarm_sound(self, sound: str):
async def set_alarm_sound(self, sound: str) -> dict:
"""Set alarm sound.
See *alarm_sounds* for list of available sounds.
@@ -119,7 +119,7 @@ class Alarm(SmartModule):
"""Return alarm volume."""
return self.data["get_alarm_configure"]["volume"]
async def set_alarm_volume(self, volume: Literal["low", "normal", "high"]):
async def set_alarm_volume(self, volume: Literal["low", "normal", "high"]) -> dict:
"""Set alarm volume."""
payload = self.data["get_alarm_configure"].copy()
payload["volume"] = volume

View File

@@ -17,7 +17,7 @@ class AutoOff(SmartModule):
REQUIRED_COMPONENT = "auto_off"
QUERY_GETTER_NAME = "get_auto_off_config"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -63,7 +63,7 @@ class AutoOff(SmartModule):
"""Return True if enabled."""
return self.data["enable"]
async def set_enabled(self, enable: bool):
async def set_enabled(self, enable: bool) -> dict:
"""Enable/disable auto off."""
return await self.call(
"set_auto_off_config",
@@ -75,7 +75,7 @@ class AutoOff(SmartModule):
"""Return time until auto off."""
return self.data["delay_min"]
async def set_delay(self, delay: int):
async def set_delay(self, delay: int) -> dict:
"""Set time until auto off."""
return await self.call(
"set_auto_off_config", {"delay_min": delay, "enable": self.data["enable"]}
@@ -96,7 +96,7 @@ class AutoOff(SmartModule):
return self._device.time + timedelta(seconds=sysinfo["auto_off_remain_time"])
async def _check_supported(self):
async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device.
Parent devices that report components of children such as P300 will not have

View File

@@ -12,7 +12,7 @@ class BatterySensor(SmartModule):
REQUIRED_COMPONENT = "battery_detect"
QUERY_GETTER_NAME = "get_battery_detect_info"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features."""
self._add_feature(
Feature(
@@ -48,11 +48,11 @@ class BatterySensor(SmartModule):
return {}
@property
def battery(self):
def battery(self) -> int:
"""Return battery level."""
return self._device.sys_info["battery_percentage"]
@property
def battery_low(self):
def battery_low(self) -> bool:
"""Return True if battery is low."""
return self._device.sys_info["at_low_battery"]

View File

@@ -14,7 +14,7 @@ class Brightness(SmartModule):
REQUIRED_COMPONENT = "brightness"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features."""
super()._initialize_features()
@@ -39,7 +39,7 @@ class Brightness(SmartModule):
return {}
@property
def brightness(self):
def brightness(self) -> int:
"""Return current brightness."""
# If the device supports effects and one is active, use its brightness
if (
@@ -49,7 +49,9 @@ class Brightness(SmartModule):
return self.data["brightness"]
async def set_brightness(self, brightness: int, *, transition: int | None = None):
async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> dict:
"""Set the brightness. A brightness value of 0 will turn off the light.
Note, transition is not supported and will be ignored.
@@ -73,6 +75,6 @@ class Brightness(SmartModule):
return await self.call("set_device_info", {"brightness": brightness})
async def _check_supported(self):
async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device."""
return "brightness" in self.data

View File

@@ -12,7 +12,7 @@ class ChildProtection(SmartModule):
REQUIRED_COMPONENT = "child_protection"
QUERY_GETTER_NAME = "get_child_protection"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(

View File

@@ -13,7 +13,7 @@ class Cloud(SmartModule):
REQUIRED_COMPONENT = "cloud_connect"
MINIMUM_UPDATE_INTERVAL_SECS = 60
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -29,7 +29,7 @@ class Cloud(SmartModule):
)
@property
def is_connected(self):
def is_connected(self) -> bool:
"""Return True if device is connected to the cloud."""
if self._has_data_error():
return False

View File

@@ -12,7 +12,7 @@ class Color(SmartModule):
REQUIRED_COMPONENT = "color"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -48,7 +48,7 @@ class Color(SmartModule):
# due to the cpython implementation.
return tuple.__new__(HSV, (h, s, v))
def _raise_for_invalid_brightness(self, value):
def _raise_for_invalid_brightness(self, value: int) -> None:
"""Raise error on invalid brightness value."""
if not isinstance(value, int):
raise TypeError("Brightness must be an integer")

View File

@@ -18,7 +18,7 @@ class ColorTemperature(SmartModule):
REQUIRED_COMPONENT = "color_temperature"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features."""
self._add_feature(
Feature(
@@ -52,11 +52,11 @@ class ColorTemperature(SmartModule):
return ColorTempRange(*ct_range)
@property
def color_temp(self):
def color_temp(self) -> int:
"""Return current color temperature."""
return self.data["color_temp"]
async def set_color_temp(self, temp: int, *, brightness=None):
async def set_color_temp(self, temp: int, *, brightness: int | None = None) -> dict:
"""Set the color temperature."""
valid_temperature_range = self.valid_temperature_range
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:

View File

@@ -12,7 +12,7 @@ class ContactSensor(SmartModule):
REQUIRED_COMPONENT = None # we depend on availability of key
REQUIRED_KEY_ON_PARENT = "open"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -32,6 +32,6 @@ class ContactSensor(SmartModule):
return {}
@property
def is_open(self):
def is_open(self) -> bool:
"""Return True if the contact sensor is open."""
return self._device.sys_info["open"]

View File

@@ -10,7 +10,7 @@ class DeviceModule(SmartModule):
REQUIRED_COMPONENT = "device"
async def _post_update_hook(self):
async def _post_update_hook(self) -> None:
"""Perform actions after a device update.
Overrides the default behaviour to disable a module if the query returns

View File

@@ -2,6 +2,8 @@
from __future__ import annotations
from typing import NoReturn
from ...emeterstatus import EmeterStatus
from ...exceptions import KasaException
from ...interfaces.energy import Energy as EnergyInterface
@@ -31,34 +33,34 @@ class Energy(SmartModule, EnergyInterface):
# Fallback if get_energy_usage does not provide current_power,
# which can happen on some newer devices (e.g. P304M).
elif (
power := self.data.get("get_current_power").get("current_power")
power := self.data.get("get_current_power", {}).get("current_power")
) is not None:
return power
return None
@property
@raise_if_update_error
def energy(self):
def energy(self) -> dict:
"""Return get_energy_usage results."""
if en := self.data.get("get_energy_usage"):
return en
return self.data
def _get_status_from_energy(self, energy) -> EmeterStatus:
def _get_status_from_energy(self, energy: dict) -> EmeterStatus:
return EmeterStatus(
{
"power_mw": energy.get("current_power"),
"total": energy.get("today_energy") / 1_000,
"power_mw": energy.get("current_power", 0),
"total": energy.get("today_energy", 0) / 1_000,
}
)
@property
@raise_if_update_error
def status(self):
def status(self) -> EmeterStatus:
"""Get the emeter status."""
return self._get_status_from_energy(self.energy)
async def get_status(self):
async def get_status(self) -> EmeterStatus:
"""Return real-time statistics."""
res = await self.call("get_energy_usage")
return self._get_status_from_energy(res["get_energy_usage"])
@@ -67,13 +69,13 @@ class Energy(SmartModule, EnergyInterface):
@raise_if_update_error
def consumption_this_month(self) -> float | None:
"""Get the emeter value for this month in kWh."""
return self.energy.get("month_energy") / 1_000
return self.energy.get("month_energy", 0) / 1_000
@property
@raise_if_update_error
def consumption_today(self) -> float | None:
"""Get the emeter value for today in kWh."""
return self.energy.get("today_energy") / 1_000
return self.energy.get("today_energy", 0) / 1_000
@property
@raise_if_update_error
@@ -97,22 +99,26 @@ class Energy(SmartModule, EnergyInterface):
"""Retrieve current energy readings."""
return self.status
async def erase_stats(self):
async def erase_stats(self) -> NoReturn:
"""Erase all stats."""
raise KasaException("Device does not support periodic statistics")
async def get_daily_stats(self, *, year=None, month=None, kwh=True) -> dict:
async def get_daily_stats(
self, *, year: int | None = None, month: int | None = None, kwh: bool = True
) -> dict:
"""Return daily stats for the given year & month.
The return value is a dictionary of {day: energy, ...}.
"""
raise KasaException("Device does not support periodic statistics")
async def get_monthly_stats(self, *, year=None, kwh=True) -> dict:
async def get_monthly_stats(
self, *, year: int | None = None, kwh: bool = True
) -> dict:
"""Return monthly stats for the given year."""
raise KasaException("Device does not support periodic statistics")
async def _check_supported(self):
async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device."""
# Energy module is not supported on P304M parent device
return "device_on" in self._device.sys_info

View File

@@ -12,7 +12,7 @@ class Fan(SmartModule, FanInterface):
REQUIRED_COMPONENT = "fan_control"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -50,7 +50,7 @@ class Fan(SmartModule, FanInterface):
"""Return fan speed level."""
return 0 if self.data["device_on"] is False else self.data["fan_speed_level"]
async def set_fan_speed_level(self, level: int):
async def set_fan_speed_level(self, level: int) -> dict:
"""Set fan speed level, 0 for off, 1-4 for on."""
if level < 0 or level > 4:
raise ValueError("Invalid level, should be in range 0-4.")
@@ -65,10 +65,10 @@ class Fan(SmartModule, FanInterface):
"""Return sleep mode status."""
return self.data["fan_sleep_mode_on"]
async def set_sleep_mode(self, on: bool):
async def set_sleep_mode(self, on: bool) -> dict:
"""Set sleep mode."""
return await self.call("set_device_info", {"fan_sleep_mode_on": on})
async def _check_supported(self):
async def _check_supported(self) -> bool:
"""Is the module available on this device."""
return "fan_speed_level" in self.data

View File

@@ -49,14 +49,14 @@ class UpdateInfo(BaseModel):
needs_upgrade: bool = Field(alias="need_to_upgrade")
@validator("release_date", pre=True)
def _release_date_optional(cls, v):
def _release_date_optional(cls, v: str) -> str | None:
if not v:
return None
return v
@property
def update_available(self):
def update_available(self) -> bool:
"""Return True if update available."""
if self.status != 0:
return True
@@ -69,11 +69,11 @@ class Firmware(SmartModule):
REQUIRED_COMPONENT = "firmware"
MINIMUM_UPDATE_INTERVAL_SECS = 60 * 60 * 24
def __init__(self, device: SmartDevice, module: str):
def __init__(self, device: SmartDevice, module: str) -> None:
super().__init__(device, module)
self._firmware_update_info: UpdateInfo | None = None
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features."""
device = self._device
if self.supported_version > 1:
@@ -183,7 +183,7 @@ class Firmware(SmartModule):
@allow_update_after
async def update(
self, progress_cb: Callable[[DownloadState], Coroutine] | None = None
):
) -> dict:
"""Update the device firmware."""
if not self._firmware_update_info:
raise KasaException(
@@ -236,13 +236,15 @@ class Firmware(SmartModule):
else:
_LOGGER.warning("Unhandled state code: %s", state)
return state.dict()
@property
def auto_update_enabled(self) -> bool:
"""Return True if autoupdate is enabled."""
return "enable" in self.data and self.data["enable"]
@allow_update_after
async def set_auto_update_enabled(self, enabled: bool):
async def set_auto_update_enabled(self, enabled: bool) -> dict:
"""Change autoupdate setting."""
data = {**self.data, "enable": enabled}
await self.call("set_auto_update_info", data)
return await self.call("set_auto_update_info", data)

View File

@@ -23,7 +23,7 @@ class FrostProtection(SmartModule):
"""Return True if frost protection is on."""
return self._device.sys_info["frost_protection_on"]
async def set_enabled(self, enable: bool):
async def set_enabled(self, enable: bool) -> dict:
"""Enable/disable frost protection."""
return await self.call(
"set_device_info",

View File

@@ -12,7 +12,7 @@ class HumiditySensor(SmartModule):
REQUIRED_COMPONENT = "humidity"
QUERY_GETTER_NAME = "get_comfort_humidity_config"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -45,7 +45,7 @@ class HumiditySensor(SmartModule):
return {}
@property
def humidity(self):
def humidity(self) -> int:
"""Return current humidity in percentage."""
return self._device.sys_info["current_humidity"]

View File

@@ -19,7 +19,7 @@ class Led(SmartModule, LedInterface):
return {self.QUERY_GETTER_NAME: None}
@property
def mode(self):
def mode(self) -> str:
"""LED mode setting.
"always", "never", "night_mode"
@@ -27,12 +27,12 @@ class Led(SmartModule, LedInterface):
return self.data["led_rule"]
@property
def led(self):
def led(self) -> bool:
"""Return current led status."""
return self.data["led_rule"] != "never"
@allow_update_after
async def set_led(self, enable: bool):
async def set_led(self, enable: bool) -> dict:
"""Set led.
This should probably be a select with always/never/nightmode.
@@ -41,7 +41,7 @@ class Led(SmartModule, LedInterface):
return await self.call("set_led_info", dict(self.data, **{"led_rule": rule}))
@property
def night_mode_settings(self):
def night_mode_settings(self) -> dict:
"""Night mode settings."""
return {
"start": self.data["start_time"],

View File

@@ -96,7 +96,7 @@ class Light(SmartModule, LightInterface):
return await self._device.modules[Module.Color].set_hsv(hue, saturation, value)
async def set_color_temp(
self, temp: int, *, brightness=None, transition: int | None = None
self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> dict:
"""Set the color temperature of the device in kelvin.

View File

@@ -81,7 +81,7 @@ class LightEffect(SmartModule, SmartLightEffect):
*,
brightness: int | None = None,
transition: int | None = None,
) -> None:
) -> dict:
"""Set an effect for the device.
Calling this will modify the brightness of the effect on the device.
@@ -107,7 +107,7 @@ class LightEffect(SmartModule, SmartLightEffect):
)
await self.set_brightness(brightness, effect_id=effect_id)
await self.call("set_dynamic_light_effect_rule_enable", params)
return await self.call("set_dynamic_light_effect_rule_enable", params)
@property
def is_active(self) -> bool:
@@ -139,11 +139,11 @@ class LightEffect(SmartModule, SmartLightEffect):
*,
transition: int | None = None,
effect_id: str | None = None,
):
) -> dict:
"""Set effect brightness."""
new_effect = self._get_effect_data(effect_id=effect_id).copy()
def _replace_brightness(data, new_brightness):
def _replace_brightness(data: list[int], new_brightness: int) -> list[int]:
"""Replace brightness.
The first element is the brightness, the rest are unknown.
@@ -163,7 +163,7 @@ class LightEffect(SmartModule, SmartLightEffect):
async def set_custom_effect(
self,
effect_dict: dict,
) -> None:
) -> dict:
"""Set a custom effect on the device.
:param str effect_dict: The custom effect dict to set

View File

@@ -29,12 +29,12 @@ class LightPreset(SmartModule, LightPresetInterface):
_presets: dict[str, LightState]
_preset_list: list[str]
def __init__(self, device: SmartDevice, module: str):
def __init__(self, device: SmartDevice, module: str) -> None:
super().__init__(device, module)
self._state_in_sysinfo = self.SYS_INFO_STATE_KEY in device.sys_info
self._brightness_only: bool = False
async def _post_update_hook(self):
async def _post_update_hook(self) -> None:
"""Update the internal presets."""
index = 0
self._presets = {}
@@ -113,7 +113,7 @@ class LightPreset(SmartModule, LightPresetInterface):
async def set_preset(
self,
preset_name: str,
) -> None:
) -> dict:
"""Set a light preset for the device."""
light = self._device.modules[SmartModule.Light]
if preset_name == self.PRESET_NOT_SET:
@@ -123,14 +123,14 @@ class LightPreset(SmartModule, LightPresetInterface):
preset = LightState(brightness=100)
elif (preset := self._presets.get(preset_name)) is None: # type: ignore[assignment]
raise ValueError(f"{preset_name} is not a valid preset: {self.preset_list}")
await self._device.modules[SmartModule.Light].set_state(preset)
return await self._device.modules[SmartModule.Light].set_state(preset)
@allow_update_after
async def save_preset(
self,
preset_name: str,
preset_state: LightState,
) -> None:
) -> dict:
"""Update the preset with preset_name with the new preset_info."""
if preset_name not in self._presets:
raise ValueError(f"{preset_name} is not a valid preset: {self.preset_list}")
@@ -138,11 +138,13 @@ class LightPreset(SmartModule, LightPresetInterface):
if self._brightness_only:
bright_list = [state.brightness for state in self._presets.values()]
bright_list[index] = preset_state.brightness
await self.call("set_preset_rules", {"brightness": bright_list})
return await self.call("set_preset_rules", {"brightness": bright_list})
else:
state_params = asdict(preset_state)
new_info = {k: v for k, v in state_params.items() if v is not None}
await self.call("edit_preset_rules", {"index": index, "state": new_info})
return await self.call(
"edit_preset_rules", {"index": index, "state": new_info}
)
@property
def has_save_preset(self) -> bool:
@@ -158,7 +160,7 @@ class LightPreset(SmartModule, LightPresetInterface):
return {self.QUERY_GETTER_NAME: {"start_index": 0}}
async def _check_supported(self):
async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device.
Parent devices that report components of children such as ks240 will not have

View File

@@ -16,7 +16,7 @@ class LightStripEffect(SmartModule, SmartLightEffect):
REQUIRED_COMPONENT = "light_strip_lighting_effect"
def __init__(self, device: SmartDevice, module: str):
def __init__(self, device: SmartDevice, module: str) -> None:
super().__init__(device, module)
effect_list = [self.LIGHT_EFFECTS_OFF]
effect_list.extend(EFFECT_NAMES)
@@ -66,7 +66,9 @@ class LightStripEffect(SmartModule, SmartLightEffect):
eff = self.data["lighting_effect"]
return eff["brightness"]
async def set_brightness(self, brightness: int, *, transition: int | None = None):
async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> dict:
"""Set effect brightness."""
if brightness <= 0:
return await self.set_effect(self.LIGHT_EFFECTS_OFF)
@@ -91,7 +93,7 @@ class LightStripEffect(SmartModule, SmartLightEffect):
*,
brightness: int | None = None,
transition: int | None = None,
) -> None:
) -> dict:
"""Set an effect on the device.
If brightness or transition is defined,
@@ -115,8 +117,7 @@ class LightStripEffect(SmartModule, SmartLightEffect):
effect_dict = self._effect_mapping["Aurora"]
effect_dict = {**effect_dict}
effect_dict["enable"] = 0
await self.set_custom_effect(effect_dict)
return
return await self.set_custom_effect(effect_dict)
if effect not in self._effect_mapping:
raise ValueError(f"The effect {effect} is not a built in effect.")
@@ -134,13 +135,13 @@ class LightStripEffect(SmartModule, SmartLightEffect):
if transition is not None:
effect_dict["transition"] = transition
await self.set_custom_effect(effect_dict)
return await self.set_custom_effect(effect_dict)
@allow_update_after
async def set_custom_effect(
self,
effect_dict: dict,
) -> None:
) -> dict:
"""Set a custom effect on the device.
:param str effect_dict: The custom effect dict to set
@@ -155,7 +156,7 @@ class LightStripEffect(SmartModule, SmartLightEffect):
"""Return True if the device supports setting custom effects."""
return True
def query(self):
def query(self) -> dict:
"""Return the base query."""
return {}

View File

@@ -39,14 +39,14 @@ class LightTransition(SmartModule):
_off_state: _State
_enabled: bool
def __init__(self, device: SmartDevice, module: str):
def __init__(self, device: SmartDevice, module: str) -> None:
super().__init__(device, module)
self._state_in_sysinfo = all(
key in device.sys_info for key in self.SYS_INFO_STATE_KEYS
)
self._supports_on_and_off: bool = self.supported_version > 1
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features."""
icon = "mdi:transition"
if not self._supports_on_and_off:
@@ -138,7 +138,7 @@ class LightTransition(SmartModule):
}
@allow_update_after
async def set_enabled(self, enable: bool):
async def set_enabled(self, enable: bool) -> dict:
"""Enable gradual on/off."""
if not self._supports_on_and_off:
return await self.call("set_on_off_gradually_info", {"enable": enable})
@@ -171,7 +171,7 @@ class LightTransition(SmartModule):
return self._on_state["max_duration"]
@allow_update_after
async def set_turn_on_transition(self, seconds: int):
async def set_turn_on_transition(self, seconds: int) -> dict:
"""Set turn on transition in seconds.
Setting to 0 turns the feature off.
@@ -207,7 +207,7 @@ class LightTransition(SmartModule):
return self._off_state["max_duration"]
@allow_update_after
async def set_turn_off_transition(self, seconds: int):
async def set_turn_off_transition(self, seconds: int) -> dict:
"""Set turn on transition in seconds.
Setting to 0 turns the feature off.
@@ -236,7 +236,7 @@ class LightTransition(SmartModule):
else:
return {self.QUERY_GETTER_NAME: None}
async def _check_supported(self):
async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device."""
# For devices that report child components on the parent that are not
# actually supported by the parent.

View File

@@ -11,7 +11,7 @@ class MotionSensor(SmartModule):
REQUIRED_COMPONENT = "sensitivity"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features."""
self._add_feature(
Feature(
@@ -31,6 +31,6 @@ class MotionSensor(SmartModule):
return {}
@property
def motion_detected(self):
def motion_detected(self) -> bool:
"""Return True if the motion has been detected."""
return self._device.sys_info["detected"]

View File

@@ -12,7 +12,7 @@ class ReportMode(SmartModule):
REQUIRED_COMPONENT = "report_mode"
QUERY_GETTER_NAME = "get_report_mode"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -32,6 +32,6 @@ class ReportMode(SmartModule):
return {}
@property
def report_interval(self):
def report_interval(self) -> int:
"""Reporting interval of a sensor device."""
return self._device.sys_info["report_interval"]

View File

@@ -26,7 +26,7 @@ class TemperatureControl(SmartModule):
REQUIRED_COMPONENT = "temp_control"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -92,7 +92,7 @@ class TemperatureControl(SmartModule):
"""Return thermostat state."""
return self._device.sys_info["frost_protection_on"] is False
async def set_state(self, enabled: bool):
async def set_state(self, enabled: bool) -> dict:
"""Set thermostat state."""
return await self.call("set_device_info", {"frost_protection_on": not enabled})
@@ -147,7 +147,7 @@ class TemperatureControl(SmartModule):
"""Return thermostat states."""
return set(self._device.sys_info["trv_states"])
async def set_target_temperature(self, target: float):
async def set_target_temperature(self, target: float) -> dict:
"""Set target temperature."""
if (
target < self.minimum_target_temperature
@@ -170,7 +170,7 @@ class TemperatureControl(SmartModule):
"""Return temperature offset."""
return self._device.sys_info["temp_offset"]
async def set_temperature_offset(self, offset: int):
async def set_temperature_offset(self, offset: int) -> dict:
"""Set temperature offset."""
if offset < -10 or offset > 10:
raise ValueError("Temperature offset must be [-10, 10]")

View File

@@ -14,7 +14,7 @@ class TemperatureSensor(SmartModule):
REQUIRED_COMPONENT = "temperature"
QUERY_GETTER_NAME = "get_comfort_temp_config"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -60,7 +60,7 @@ class TemperatureSensor(SmartModule):
return {}
@property
def temperature(self):
def temperature(self) -> float:
"""Return current humidity in percentage."""
return self._device.sys_info["current_temp"]
@@ -74,6 +74,8 @@ class TemperatureSensor(SmartModule):
"""Return current temperature unit."""
return self._device.sys_info["temp_unit"]
async def set_temperature_unit(self, unit: Literal["celsius", "fahrenheit"]):
async def set_temperature_unit(
self, unit: Literal["celsius", "fahrenheit"]
) -> dict:
"""Set the device temperature unit."""
return await self.call("set_temperature_unit", {"temp_unit": unit})

View File

@@ -21,7 +21,7 @@ class Time(SmartModule, TimeInterface):
_timezone: tzinfo = timezone.utc
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
@@ -35,7 +35,7 @@ class Time(SmartModule, TimeInterface):
)
)
async def _post_update_hook(self):
async def _post_update_hook(self) -> None:
"""Perform actions after a device update."""
td = timedelta(minutes=cast(float, self.data.get("time_diff")))
if region := self.data.get("region"):
@@ -84,7 +84,7 @@ class Time(SmartModule, TimeInterface):
params["region"] = region
return await self.call("set_device_time", params)
async def _check_supported(self):
async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device.
Hub attached sensors report the time module but do return device time.

View File

@@ -22,7 +22,7 @@ class WaterleakSensor(SmartModule):
REQUIRED_COMPONENT = "sensor_alarm"
def _initialize_features(self):
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(

View File

@@ -49,7 +49,7 @@ class SmartChildDevice(SmartDevice):
self._update_internal_state(info)
self._components = component_info
async def update(self, update_children: bool = True):
async def update(self, update_children: bool = True) -> None:
"""Update child module info.
The parent updates our internal info so just update modules with
@@ -57,7 +57,7 @@ class SmartChildDevice(SmartDevice):
"""
await self._update(update_children)
async def _update(self, update_children: bool = True):
async def _update(self, update_children: bool = True) -> None:
"""Update child module info.
Internal implementation to allow patching of public update in the cli
@@ -118,5 +118,5 @@ class SmartChildDevice(SmartDevice):
dev_type = DeviceType.Unknown
return dev_type
def __repr__(self):
def __repr__(self) -> str:
return f"<{self.device_type} {self.alias} ({self.model}) of {self._parent}>"

View File

@@ -69,7 +69,7 @@ class SmartDevice(Device):
self._on_since: datetime | None = None
self._info: dict[str, Any] = {}
async def _initialize_children(self):
async def _initialize_children(self) -> None:
"""Initialize children for power strips."""
child_info_query = {
"get_child_device_component_list": None,
@@ -108,7 +108,9 @@ class SmartDevice(Device):
"""Return the device modules."""
return cast(ModuleMapping[SmartModule], self._modules)
def _try_get_response(self, responses: dict, request: str, default=None) -> dict:
def _try_get_response(
self, responses: dict, request: str, default: Any | None = None
) -> dict:
response = responses.get(request)
if isinstance(response, SmartErrorCode):
_LOGGER.debug(
@@ -126,7 +128,7 @@ class SmartDevice(Device):
f"{request} not found in {responses} for device {self.host}"
)
async def _negotiate(self):
async def _negotiate(self) -> None:
"""Perform initialization.
We fetch the device info and the available components as early as possible.
@@ -146,7 +148,8 @@ class SmartDevice(Device):
self._info = self._try_get_response(resp, "get_device_info")
# Create our internal presentation of available components
self._components_raw = resp["component_nego"]
self._components_raw = cast(dict, resp["component_nego"])
self._components = {
comp["id"]: int(comp["ver_code"])
for comp in self._components_raw["component_list"]
@@ -167,7 +170,7 @@ class SmartDevice(Device):
"""Update the internal device info."""
self._info = self._try_get_response(info_resp, "get_device_info")
async def update(self, update_children: bool = False):
async def update(self, update_children: bool = False) -> None:
"""Update the device."""
if self.credentials is None and self.credentials_hash is None:
raise AuthenticationError("Tapo plug requires authentication.")
@@ -206,7 +209,7 @@ class SmartDevice(Device):
async def _handle_module_post_update(
self, module: SmartModule, update_time: float, had_query: bool
):
) -> None:
if module.disabled:
return # pragma: no cover
if had_query:
@@ -312,7 +315,7 @@ class SmartDevice(Device):
responses[meth] = SmartErrorCode.INTERNAL_QUERY_ERROR
return responses
async def _initialize_modules(self):
async def _initialize_modules(self) -> None:
"""Initialize modules based on component negotiation response."""
from .smartmodule import SmartModule
@@ -324,7 +327,7 @@ class SmartDevice(Device):
# It also ensures that devices like power strips do not add modules such as
# firmware to the child devices.
skip_parent_only_modules = False
child_modules_to_skip = {}
child_modules_to_skip: dict = {} # TODO: this is never non-empty
if self._parent and self._parent.device_type != DeviceType.Hub:
skip_parent_only_modules = True
@@ -333,17 +336,18 @@ class SmartDevice(Device):
skip_parent_only_modules and mod in NON_HUB_PARENT_ONLY_MODULES
) or mod.__name__ in child_modules_to_skip:
continue
if (
mod.REQUIRED_COMPONENT in self._components
or self.sys_info.get(mod.REQUIRED_KEY_ON_PARENT) is not None
required_component = cast(str, mod.REQUIRED_COMPONENT)
if required_component in self._components or (
mod.REQUIRED_KEY_ON_PARENT
and self.sys_info.get(mod.REQUIRED_KEY_ON_PARENT) is not None
):
_LOGGER.debug(
"Device %s, found required %s, adding %s to modules.",
self.host,
mod.REQUIRED_COMPONENT,
required_component,
mod.__name__,
)
module = mod(self, mod.REQUIRED_COMPONENT)
module = mod(self, required_component)
if await module._check_supported():
self._modules[module.name] = module
@@ -354,7 +358,7 @@ class SmartDevice(Device):
):
self._modules[Light.__name__] = Light(self, "light")
async def _initialize_features(self):
async def _initialize_features(self) -> None:
"""Initialize device features."""
self._add_feature(
Feature(
@@ -575,11 +579,11 @@ class SmartDevice(Device):
return str(self._info.get("device_id"))
@property
def internal_state(self) -> Any:
def internal_state(self) -> dict:
"""Return all the internal state data."""
return self._last_update
def _update_internal_state(self, info: dict) -> None:
def _update_internal_state(self, info: dict[str, Any]) -> None:
"""Update the internal info state.
This is used by the parent to push updates to its children.
@@ -587,8 +591,8 @@ class SmartDevice(Device):
self._info = info
async def _query_helper(
self, method: str, params: dict | None = None, child_ids=None
) -> Any:
self, method: str, params: dict | None = None, child_ids: None = None
) -> dict:
res = await self.protocol.query({method: params})
return res
@@ -610,22 +614,25 @@ class SmartDevice(Device):
"""Return true if the device is on."""
return bool(self._info.get("device_on"))
async def set_state(self, on: bool): # TODO: better name wanted.
async def set_state(self, on: bool) -> dict:
"""Set the device state.
See :meth:`is_on`.
"""
return await self.protocol.query({"set_device_info": {"device_on": on}})
async def turn_on(self, **kwargs):
async def turn_on(self, **kwargs: Any) -> dict:
"""Turn on the device."""
await self.set_state(True)
return await self.set_state(True)
async def turn_off(self, **kwargs):
async def turn_off(self, **kwargs: Any) -> dict:
"""Turn off the device."""
await self.set_state(False)
return await self.set_state(False)
def update_from_discover_info(self, info):
def update_from_discover_info(
self,
info: dict,
) -> None:
"""Update state from info from the discover call."""
self._discovery_info = info
self._info = info
@@ -633,7 +640,7 @@ class SmartDevice(Device):
async def wifi_scan(self) -> list[WifiNetwork]:
"""Scan for available wifi networks."""
def _net_for_scan_info(res):
def _net_for_scan_info(res: dict) -> WifiNetwork:
return WifiNetwork(
ssid=base64.b64decode(res["ssid"]).decode(),
cipher_type=res["cipher_type"],
@@ -651,7 +658,9 @@ class SmartDevice(Device):
]
return networks
async def wifi_join(self, ssid: str, password: str, keytype: str = "wpa2_psk"):
async def wifi_join(
self, ssid: str, password: str, keytype: str = "wpa2_psk"
) -> dict:
"""Join the given wifi network.
This method returns nothing as the device tries to activate the new
@@ -688,9 +697,12 @@ class SmartDevice(Device):
except DeviceError:
raise # Re-raise on device-reported errors
except KasaException:
_LOGGER.debug("Received an expected for wifi join, but this is expected")
_LOGGER.debug(
"Received a kasa exception for wifi join, but this is expected"
)
return {}
async def update_credentials(self, username: str, password: str):
async def update_credentials(self, username: str, password: str) -> dict:
"""Update device credentials.
This will replace the existing authentication credentials on the device.
@@ -705,7 +717,7 @@ class SmartDevice(Device):
}
return await self.protocol.query({"set_qs_info": payload})
async def set_alias(self, alias: str):
async def set_alias(self, alias: str) -> dict:
"""Set the device name (alias)."""
return await self.protocol.query(
{"set_device_info": {"nickname": base64.b64encode(alias.encode()).decode()}}

View File

@@ -22,17 +22,17 @@ _R = TypeVar("_R")
def allow_update_after(
func: Callable[Concatenate[_T, _P], Awaitable[None]],
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]:
func: Callable[Concatenate[_T, _P], Awaitable[dict]],
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, dict]]:
"""Define a wrapper to set _last_update_time to None.
This will ensure that a module is updated in the next update cycle after
a value has been changed.
"""
async def _async_wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
async def _async_wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> dict:
try:
await func(self, *args, **kwargs)
return await func(self, *args, **kwargs)
finally:
self._last_update_time = None
@@ -68,21 +68,21 @@ class SmartModule(Module):
DISABLE_AFTER_ERROR_COUNT = 10
def __init__(self, device: SmartDevice, module: str):
def __init__(self, device: SmartDevice, module: str) -> None:
self._device: SmartDevice
super().__init__(device, module)
self._last_update_time: float | None = None
self._last_update_error: KasaException | None = None
self._error_count = 0
def __init_subclass__(cls, **kwargs):
def __init_subclass__(cls, **kwargs) -> None:
# We only want to register submodules in a modules package so that
# other classes can inherit from smartmodule and not be registered
if cls.__module__.split(".")[-2] == "modules":
_LOGGER.debug("Registering %s", cls)
cls.REGISTERED_MODULES[cls._module_name()] = cls
def _set_error(self, err: Exception | None):
def _set_error(self, err: Exception | None) -> None:
if err is None:
self._error_count = 0
self._last_update_error = None
@@ -119,7 +119,7 @@ class SmartModule(Module):
return self._error_count >= self.DISABLE_AFTER_ERROR_COUNT
@classmethod
def _module_name(cls):
def _module_name(cls) -> str:
return getattr(cls, "NAME", cls.__name__)
@property
@@ -127,7 +127,7 @@ class SmartModule(Module):
"""Name of the module."""
return self._module_name()
async def _post_update_hook(self): # noqa: B027
async def _post_update_hook(self) -> None: # noqa: B027
"""Perform actions after a device update.
Any modules overriding this should ensure that self.data is
@@ -142,7 +142,7 @@ class SmartModule(Module):
"""
return {self.QUERY_GETTER_NAME: None}
async def call(self, method, params=None):
async def call(self, method: str, params: dict | None = None) -> dict:
"""Call a method.
Just a helper method.
@@ -150,7 +150,7 @@ class SmartModule(Module):
return await self._device._query_helper(method, params)
@property
def data(self):
def data(self) -> dict[str, Any]:
"""Return response data for the module.
If the module performs only a single query, the resulting response is unwrapped.