mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-04-26 16:46:23 +00:00
Require explicit feature type (#1006)
Explicit > implicit. Having this previously would have avoided using a wrong type for water_alert in the first place.
This commit is contained in:
parent
5846bbdbbb
commit
0f5bafaa43
@ -129,6 +129,8 @@ class Feature:
|
|||||||
id: str
|
id: str
|
||||||
#: User-friendly short description
|
#: User-friendly short description
|
||||||
name: str
|
name: str
|
||||||
|
#: Type of the feature
|
||||||
|
type: Feature.Type
|
||||||
#: Name of the property that allows accessing the value
|
#: Name of the property that allows accessing the value
|
||||||
attribute_getter: str | Callable | None = None
|
attribute_getter: str | Callable | None = None
|
||||||
#: Name of the method that allows changing the value
|
#: Name of the method that allows changing the value
|
||||||
@ -144,8 +146,6 @@ class Feature:
|
|||||||
unit_getter: str | None = None
|
unit_getter: str | None = None
|
||||||
#: Category hint for downstreams
|
#: Category hint for downstreams
|
||||||
category: Feature.Category = Category.Unset
|
category: Feature.Category = Category.Unset
|
||||||
#: Type of the feature
|
|
||||||
type: Feature.Type = Type.Sensor
|
|
||||||
|
|
||||||
# Display hints offer a way suggest how the value should be shown to users
|
# Display hints offer a way suggest how the value should be shown to users
|
||||||
#: Hint to help rounding the sensor values to given after-comma digits
|
#: Hint to help rounding the sensor values to given after-comma digits
|
||||||
@ -191,14 +191,19 @@ class Feature:
|
|||||||
else:
|
else:
|
||||||
self.category = Feature.Category.Info
|
self.category = Feature.Category.Info
|
||||||
|
|
||||||
if self.category == Feature.Category.Config and self.type in [
|
if self.type in (
|
||||||
Feature.Type.Sensor,
|
Feature.Type.Sensor,
|
||||||
Feature.Type.BinarySensor,
|
Feature.Type.BinarySensor,
|
||||||
]:
|
):
|
||||||
|
if self.category == Feature.Category.Config:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid type for configurable feature: {self.name} ({self.id}):"
|
f"Invalid type for configurable feature: {self.name} ({self.id}):"
|
||||||
f" {self.type}"
|
f" {self.type}"
|
||||||
)
|
)
|
||||||
|
elif self.attribute_setter is not None:
|
||||||
|
raise ValueError(
|
||||||
|
f"Read-only feat defines attribute_setter: {self.name} ({self.id}):"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
@ -44,6 +44,7 @@ class Energy(Module, ABC):
|
|||||||
id="current_consumption",
|
id="current_consumption",
|
||||||
precision_hint=1,
|
precision_hint=1,
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -56,6 +57,7 @@ class Energy(Module, ABC):
|
|||||||
id="consumption_today",
|
id="consumption_today",
|
||||||
precision_hint=3,
|
precision_hint=3,
|
||||||
category=Feature.Category.Info,
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -68,6 +70,7 @@ class Energy(Module, ABC):
|
|||||||
unit="kWh",
|
unit="kWh",
|
||||||
precision_hint=3,
|
precision_hint=3,
|
||||||
category=Feature.Category.Info,
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.supports(self.ModuleFeature.CONSUMPTION_TOTAL):
|
if self.supports(self.ModuleFeature.CONSUMPTION_TOTAL):
|
||||||
@ -81,6 +84,7 @@ class Energy(Module, ABC):
|
|||||||
id="consumption_total",
|
id="consumption_total",
|
||||||
precision_hint=3,
|
precision_hint=3,
|
||||||
category=Feature.Category.Info,
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.supports(self.ModuleFeature.VOLTAGE_CURRENT):
|
if self.supports(self.ModuleFeature.VOLTAGE_CURRENT):
|
||||||
@ -94,6 +98,7 @@ class Energy(Module, ABC):
|
|||||||
id="voltage",
|
id="voltage",
|
||||||
precision_hint=1,
|
precision_hint=1,
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -106,6 +111,7 @@ class Energy(Module, ABC):
|
|||||||
id="current",
|
id="current",
|
||||||
precision_hint=2,
|
precision_hint=2,
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -340,7 +340,9 @@ class IotDevice(Device):
|
|||||||
name="RSSI",
|
name="RSSI",
|
||||||
attribute_getter="rssi",
|
attribute_getter="rssi",
|
||||||
icon="mdi:signal",
|
icon="mdi:signal",
|
||||||
|
unit="dBm",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# iot strips calculate on_since from the children
|
# iot strips calculate on_since from the children
|
||||||
@ -353,6 +355,7 @@ class IotDevice(Device):
|
|||||||
attribute_getter="on_since",
|
attribute_getter="on_since",
|
||||||
icon="mdi:clock",
|
icon="mdi:clock",
|
||||||
category=Feature.Category.Info,
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -347,6 +347,7 @@ class IotStripPlug(IotPlug):
|
|||||||
attribute_getter="on_since",
|
attribute_getter="on_since",
|
||||||
icon="mdi:clock",
|
icon="mdi:clock",
|
||||||
category=Feature.Category.Info,
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for module in self._supported_modules.values():
|
for module in self._supported_modules.values():
|
||||||
|
@ -43,6 +43,7 @@ class Alarm(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="source",
|
attribute_getter="source",
|
||||||
icon="mdi:bell",
|
icon="mdi:bell",
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
|
@ -56,6 +56,7 @@ class AutoOff(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="auto_off_at",
|
attribute_getter="auto_off_at",
|
||||||
category=Feature.Category.Info,
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,6 +101,7 @@ class Firmware(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="current_firmware",
|
attribute_getter="current_firmware",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -111,6 +112,7 @@ class Firmware(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="latest_firmware",
|
attribute_getter="latest_firmware",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ class HumiditySensor(SmartModule):
|
|||||||
icon="mdi:water-percent",
|
icon="mdi:water-percent",
|
||||||
unit="%",
|
unit="%",
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
|
@ -28,6 +28,7 @@ class ReportMode(SmartModule):
|
|||||||
attribute_getter="report_interval",
|
attribute_getter="report_interval",
|
||||||
unit="s",
|
unit="s",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ class TemperatureControl(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="mode",
|
attribute_getter="mode",
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ class TemperatureSensor(SmartModule):
|
|||||||
icon="mdi:thermometer",
|
icon="mdi:thermometer",
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
unit_getter="temperature_unit",
|
unit_getter="temperature_unit",
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if "current_temp_exception" in device.sys_info:
|
if "current_temp_exception" in device.sys_info:
|
||||||
|
@ -30,6 +30,7 @@ class Time(SmartModule):
|
|||||||
attribute_getter="time",
|
attribute_getter="time",
|
||||||
container=self,
|
container=self,
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ class WaterleakSensor(SmartModule):
|
|||||||
attribute_getter="status",
|
attribute_getter="status",
|
||||||
icon="mdi:water",
|
icon="mdi:water",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -47,6 +48,7 @@ class WaterleakSensor(SmartModule):
|
|||||||
attribute_getter="alert",
|
attribute_getter="alert",
|
||||||
icon="mdi:water-alert",
|
icon="mdi:water-alert",
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
|
type=Feature.Type.BinarySensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -242,6 +242,7 @@ class SmartDevice(Device):
|
|||||||
name="Device ID",
|
name="Device ID",
|
||||||
attribute_getter="device_id",
|
attribute_getter="device_id",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if "device_on" in self._info:
|
if "device_on" in self._info:
|
||||||
@ -266,6 +267,7 @@ class SmartDevice(Device):
|
|||||||
attribute_getter=lambda x: x._info["signal_level"],
|
attribute_getter=lambda x: x._info["signal_level"],
|
||||||
icon="mdi:signal",
|
icon="mdi:signal",
|
||||||
category=Feature.Category.Info,
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -279,6 +281,7 @@ class SmartDevice(Device):
|
|||||||
icon="mdi:signal",
|
icon="mdi:signal",
|
||||||
unit="dBm",
|
unit="dBm",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -291,6 +294,7 @@ class SmartDevice(Device):
|
|||||||
attribute_getter="ssid",
|
attribute_getter="ssid",
|
||||||
icon="mdi:wifi",
|
icon="mdi:wifi",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -318,6 +322,7 @@ class SmartDevice(Device):
|
|||||||
attribute_getter="on_since",
|
attribute_getter="on_since",
|
||||||
icon="mdi:clock",
|
icon="mdi:clock",
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,8 +44,11 @@ def test_feature_api(dummy_feature: Feature):
|
|||||||
assert dummy_feature.unit == "dummyunit"
|
assert dummy_feature.unit == "dummyunit"
|
||||||
|
|
||||||
|
|
||||||
def test_feature_missing_type():
|
@pytest.mark.parametrize(
|
||||||
"""Test that creating a feature with a setter but without type causes an error."""
|
"read_only_type", [Feature.Type.Sensor, Feature.Type.BinarySensor]
|
||||||
|
)
|
||||||
|
def test_feature_setter_on_sensor(read_only_type):
|
||||||
|
"""Test that creating a sensor feature with a setter causes an error."""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Feature(
|
Feature(
|
||||||
device=DummyDevice(), # type: ignore[arg-type]
|
device=DummyDevice(), # type: ignore[arg-type]
|
||||||
@ -53,6 +56,7 @@ def test_feature_missing_type():
|
|||||||
name="dummy error",
|
name="dummy error",
|
||||||
attribute_getter="dummygetter",
|
attribute_getter="dummygetter",
|
||||||
attribute_setter="dummysetter",
|
attribute_setter="dummysetter",
|
||||||
|
type=read_only_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user