Rename bulb interface to light and move fan and light interface to interfaces (#910)

Also rename BulbPreset to LightPreset.
This commit is contained in:
Steven B 2024-05-11 19:40:08 +01:00 committed by GitHub
parent f259a8f162
commit d7b00336f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 59 additions and 44 deletions

View File

@ -16,7 +16,6 @@ from importlib.metadata import version
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from warnings import warn from warnings import warn
from kasa.bulb import Bulb, BulbPreset
from kasa.credentials import Credentials from kasa.credentials import Credentials
from kasa.device import Device from kasa.device import Device
from kasa.device_type import DeviceType from kasa.device_type import DeviceType
@ -36,6 +35,7 @@ from kasa.exceptions import (
UnsupportedDeviceError, UnsupportedDeviceError,
) )
from kasa.feature import Feature from kasa.feature import Feature
from kasa.interfaces.light import Light, LightPreset
from kasa.iotprotocol import ( from kasa.iotprotocol import (
IotProtocol, IotProtocol,
_deprecated_TPLinkSmartHomeProtocol, # noqa: F401 _deprecated_TPLinkSmartHomeProtocol, # noqa: F401
@ -52,14 +52,14 @@ __all__ = [
"BaseProtocol", "BaseProtocol",
"IotProtocol", "IotProtocol",
"SmartProtocol", "SmartProtocol",
"BulbPreset", "LightPreset",
"TurnOnBehaviors", "TurnOnBehaviors",
"TurnOnBehavior", "TurnOnBehavior",
"DeviceType", "DeviceType",
"Feature", "Feature",
"EmeterStatus", "EmeterStatus",
"Device", "Device",
"Bulb", "Light",
"Plug", "Plug",
"Module", "Module",
"KasaException", "KasaException",
@ -84,7 +84,7 @@ deprecated_smart_devices = {
"SmartLightStrip": iot.IotLightStrip, "SmartLightStrip": iot.IotLightStrip,
"SmartStrip": iot.IotStrip, "SmartStrip": iot.IotStrip,
"SmartDimmer": iot.IotDimmer, "SmartDimmer": iot.IotDimmer,
"SmartBulbPreset": BulbPreset, "SmartBulbPreset": LightPreset,
} }
deprecated_exceptions = { deprecated_exceptions = {
"SmartDeviceException": KasaException, "SmartDeviceException": KasaException,
@ -124,7 +124,7 @@ if TYPE_CHECKING:
SmartLightStrip = iot.IotLightStrip SmartLightStrip = iot.IotLightStrip
SmartStrip = iot.IotStrip SmartStrip = iot.IotStrip
SmartDimmer = iot.IotDimmer SmartDimmer = iot.IotDimmer
SmartBulbPreset = BulbPreset SmartBulbPreset = LightPreset
SmartDeviceException = KasaException SmartDeviceException = KasaException
UnsupportedDeviceException = UnsupportedDeviceError UnsupportedDeviceException = UnsupportedDeviceError

View File

@ -18,7 +18,6 @@ from pydantic.v1 import ValidationError
from kasa import ( from kasa import (
AuthenticationError, AuthenticationError,
Bulb,
ConnectionType, ConnectionType,
Credentials, Credentials,
Device, Device,
@ -28,6 +27,7 @@ from kasa import (
EncryptType, EncryptType,
Feature, Feature,
KasaException, KasaException,
Light,
UnsupportedDeviceError, UnsupportedDeviceError,
) )
from kasa.discover import DiscoveryResult from kasa.discover import DiscoveryResult
@ -859,7 +859,7 @@ async def usage(dev: Device, year, month, erase):
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False) @click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
@click.option("--transition", type=int, required=False) @click.option("--transition", type=int, required=False)
@pass_dev @pass_dev
async def brightness(dev: Bulb, brightness: int, transition: int): async def brightness(dev: Light, brightness: int, transition: int):
"""Get or set brightness.""" """Get or set brightness."""
if not dev.is_dimmable: if not dev.is_dimmable:
echo("This device does not support brightness.") echo("This device does not support brightness.")
@ -879,7 +879,7 @@ async def brightness(dev: Bulb, brightness: int, transition: int):
) )
@click.option("--transition", type=int, required=False) @click.option("--transition", type=int, required=False)
@pass_dev @pass_dev
async def temperature(dev: Bulb, temperature: int, transition: int): async def temperature(dev: Light, temperature: int, transition: int):
"""Get or set color temperature.""" """Get or set color temperature."""
if not dev.is_variable_color_temp: if not dev.is_variable_color_temp:
echo("Device does not support color temperature") echo("Device does not support color temperature")

View File

@ -0,0 +1,14 @@
"""Package for interfaces."""
from .fan import Fan
from .led import Led
from .light import Light, LightPreset
from .lighteffect import LightEffect
__all__ = [
"Fan",
"Led",
"Light",
"LightEffect",
"LightPreset",
]

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .device import Device from ..device import Device
class Fan(Device, ABC): class Fan(Device, ABC):

View File

@ -7,7 +7,7 @@ from typing import NamedTuple, Optional
from pydantic.v1 import BaseModel from pydantic.v1 import BaseModel
from .device import Device from ..device import Device
class ColorTempRange(NamedTuple): class ColorTempRange(NamedTuple):
@ -25,8 +25,8 @@ class HSV(NamedTuple):
value: int value: int
class BulbPreset(BaseModel): class LightPreset(BaseModel):
"""Bulb configuration preset.""" """Light configuration preset."""
index: int index: int
brightness: int brightness: int
@ -42,8 +42,8 @@ class BulbPreset(BaseModel):
mode: Optional[int] # noqa: UP007 mode: Optional[int] # noqa: UP007
class Bulb(Device, ABC): class Light(Device, ABC):
"""Base class for TP-Link Bulb.""" """Base class for TP-Link Light."""
def _raise_for_invalid_brightness(self, value): def _raise_for_invalid_brightness(self, value):
if not isinstance(value, int) or not (0 <= value <= 100): if not isinstance(value, int) or not (0 <= value <= 100):
@ -135,5 +135,5 @@ class Bulb(Device, ABC):
@property @property
@abstractmethod @abstractmethod
def presets(self) -> list[BulbPreset]: def presets(self) -> list[LightPreset]:
"""Return a list of available bulb setting presets.""" """Return a list of available bulb setting presets."""

View File

@ -9,10 +9,10 @@ from typing import Optional, cast
from pydantic.v1 import BaseModel, Field, root_validator from pydantic.v1 import BaseModel, Field, root_validator
from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
from ..device_type import DeviceType from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
from ..feature import Feature from ..feature import Feature
from ..interfaces.light import HSV, ColorTempRange, Light, LightPreset
from ..module import Module from ..module import Module
from ..protocol import BaseProtocol from ..protocol import BaseProtocol
from .iotdevice import IotDevice, KasaException, requires_update from .iotdevice import IotDevice, KasaException, requires_update
@ -88,7 +88,7 @@ NON_COLOR_MODE_FLAGS = {"transition_period", "on_off"}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class IotBulb(IotDevice, Bulb): class IotBulb(IotDevice, Light):
r"""Representation of a TP-Link Smart Bulb. r"""Representation of a TP-Link Smart Bulb.
To initialize, you have to await :func:`update()` at least once. To initialize, you have to await :func:`update()` at least once.
@ -170,9 +170,9 @@ class IotBulb(IotDevice, Bulb):
Bulb configuration presets can be accessed using the :func:`presets` property: Bulb configuration presets can be accessed using the :func:`presets` property:
>>> bulb.presets >>> bulb.presets
[BulbPreset(index=0, brightness=50, hue=0, saturation=0, color_temp=2700, custom=None, id=None, mode=None), BulbPreset(index=1, brightness=100, hue=0, saturation=75, color_temp=0, custom=None, id=None, mode=None), BulbPreset(index=2, brightness=100, hue=120, saturation=75, color_temp=0, custom=None, id=None, mode=None), BulbPreset(index=3, brightness=100, hue=240, saturation=75, color_temp=0, custom=None, id=None, mode=None)] [LightPreset(index=0, brightness=50, hue=0, saturation=0, color_temp=2700, custom=None, id=None, mode=None), LightPreset(index=1, brightness=100, hue=0, saturation=75, color_temp=0, custom=None, id=None, mode=None), LightPreset(index=2, brightness=100, hue=120, saturation=75, color_temp=0, custom=None, id=None, mode=None), LightPreset(index=3, brightness=100, hue=240, saturation=75, color_temp=0, custom=None, id=None, mode=None)]
To modify an existing preset, pass :class:`~kasa.smartbulb.SmartBulbPreset` To modify an existing preset, pass :class:`~kasa.smartbulb.LightPreset`
instance to :func:`save_preset` method: instance to :func:`save_preset` method:
>>> preset = bulb.presets[0] >>> preset = bulb.presets[0]
@ -523,11 +523,11 @@ class IotBulb(IotDevice, Bulb):
@property # type: ignore @property # type: ignore
@requires_update @requires_update
def presets(self) -> list[BulbPreset]: def presets(self) -> list[LightPreset]:
"""Return a list of available bulb setting presets.""" """Return a list of available bulb setting presets."""
return [BulbPreset(**vals) for vals in self.sys_info["preferred_state"]] return [LightPreset(**vals) for vals in self.sys_info["preferred_state"]]
async def save_preset(self, preset: BulbPreset): async def save_preset(self, preset: LightPreset):
"""Save a setting preset. """Save a setting preset.
You can either construct a preset object manually, or pass an existing one You can either construct a preset object manually, or pass an existing one

View File

@ -4,8 +4,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ...bulb import HSV
from ...feature import Feature from ...feature import Feature
from ...interfaces.light import HSV
from ..smartmodule import SmartModule from ..smartmodule import SmartModule
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@ -5,8 +5,8 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ...bulb import ColorTempRange
from ...feature import Feature from ...feature import Feature
from ...interfaces.light import ColorTempRange
from ..smartmodule import SmartModule from ..smartmodule import SmartModule
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@ -8,14 +8,14 @@ from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast
from ..aestransport import AesTransport from ..aestransport import AesTransport
from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
from ..device import Device, WifiNetwork from ..device import Device, WifiNetwork
from ..device_type import DeviceType from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
from ..emeterstatus import EmeterStatus from ..emeterstatus import EmeterStatus
from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode
from ..fan import Fan
from ..feature import Feature from ..feature import Feature
from ..interfaces.fan import Fan
from ..interfaces.light import HSV, ColorTempRange, Light, LightPreset
from ..module import Module from ..module import Module
from ..modulemapping import ModuleMapping, ModuleName from ..modulemapping import ModuleMapping, ModuleName
from ..smartprotocol import SmartProtocol from ..smartprotocol import SmartProtocol
@ -39,7 +39,7 @@ WALL_SWITCH_PARENT_ONLY_MODULES = [DeviceModule, Time, Firmware, Cloud]
# Device must go last as the other interfaces also inherit Device # Device must go last as the other interfaces also inherit Device
# and python needs a consistent method resolution order. # and python needs a consistent method resolution order.
class SmartDevice(Bulb, Fan, Device): class SmartDevice(Light, Fan, Device):
"""Base class to represent a SMART protocol based device.""" """Base class to represent a SMART protocol based device."""
def __init__( def __init__(
@ -766,7 +766,7 @@ class SmartDevice(Bulb, Fan, Device):
return await self.modules[Module.Brightness].set_brightness(brightness) return await self.modules[Module.Brightness].set_brightness(brightness)
@property @property
def presets(self) -> list[BulbPreset]: def presets(self) -> list[LightPreset]:
"""Return a list of available bulb setting presets.""" """Return a list of available bulb setting presets."""
return [] return []

View File

@ -7,7 +7,7 @@ from voluptuous import (
Schema, Schema,
) )
from kasa import Bulb, BulbPreset, Device, DeviceType, KasaException from kasa import Device, DeviceType, KasaException, Light, LightPreset
from kasa.iot import IotBulb, IotDimmer from kasa.iot import IotBulb, IotDimmer
from kasa.smart import SmartDevice from kasa.smart import SmartDevice
@ -65,7 +65,7 @@ async def test_get_light_state(dev: IotBulb):
@color_bulb @color_bulb
@turn_on @turn_on
async def test_hsv(dev: Device, turn_on): async def test_hsv(dev: Device, turn_on):
assert isinstance(dev, Bulb) assert isinstance(dev, Light)
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
assert dev.is_color assert dev.is_color
@ -96,7 +96,7 @@ async def test_set_hsv_transition(dev: IotBulb, mocker):
@color_bulb @color_bulb
@turn_on @turn_on
async def test_invalid_hsv(dev: Bulb, turn_on): async def test_invalid_hsv(dev: Light, turn_on):
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
assert dev.is_color assert dev.is_color
@ -116,13 +116,13 @@ async def test_invalid_hsv(dev: Bulb, turn_on):
@color_bulb @color_bulb
@pytest.mark.skip("requires color feature") @pytest.mark.skip("requires color feature")
async def test_color_state_information(dev: Device): async def test_color_state_information(dev: Device):
assert isinstance(dev, Bulb) assert isinstance(dev, Light)
assert "HSV" in dev.state_information assert "HSV" in dev.state_information
assert dev.state_information["HSV"] == dev.hsv assert dev.state_information["HSV"] == dev.hsv
@non_color_bulb @non_color_bulb
async def test_hsv_on_non_color(dev: Bulb): async def test_hsv_on_non_color(dev: Light):
assert not dev.is_color assert not dev.is_color
with pytest.raises(KasaException): with pytest.raises(KasaException):
@ -134,7 +134,7 @@ async def test_hsv_on_non_color(dev: Bulb):
@variable_temp @variable_temp
@pytest.mark.skip("requires colortemp module") @pytest.mark.skip("requires colortemp module")
async def test_variable_temp_state_information(dev: Device): async def test_variable_temp_state_information(dev: Device):
assert isinstance(dev, Bulb) assert isinstance(dev, Light)
assert "Color temperature" in dev.state_information assert "Color temperature" in dev.state_information
assert dev.state_information["Color temperature"] == dev.color_temp assert dev.state_information["Color temperature"] == dev.color_temp
@ -142,7 +142,7 @@ async def test_variable_temp_state_information(dev: Device):
@variable_temp @variable_temp
@turn_on @turn_on
async def test_try_set_colortemp(dev: Device, turn_on): async def test_try_set_colortemp(dev: Device, turn_on):
assert isinstance(dev, Bulb) assert isinstance(dev, Light)
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
await dev.set_color_temp(2700) await dev.set_color_temp(2700)
await dev.update() await dev.update()
@ -171,7 +171,7 @@ async def test_smart_temp_range(dev: SmartDevice):
@variable_temp @variable_temp
async def test_out_of_range_temperature(dev: Bulb): async def test_out_of_range_temperature(dev: Light):
with pytest.raises(ValueError): with pytest.raises(ValueError):
await dev.set_color_temp(1000) await dev.set_color_temp(1000)
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -179,7 +179,7 @@ async def test_out_of_range_temperature(dev: Bulb):
@non_variable_temp @non_variable_temp
async def test_non_variable_temp(dev: Bulb): async def test_non_variable_temp(dev: Light):
with pytest.raises(KasaException): with pytest.raises(KasaException):
await dev.set_color_temp(2700) await dev.set_color_temp(2700)
@ -193,7 +193,7 @@ async def test_non_variable_temp(dev: Bulb):
@dimmable @dimmable
@turn_on @turn_on
async def test_dimmable_brightness(dev: Device, turn_on): async def test_dimmable_brightness(dev: Device, turn_on):
assert isinstance(dev, (Bulb, IotDimmer)) assert isinstance(dev, (Light, IotDimmer))
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
assert dev.is_dimmable assert dev.is_dimmable
@ -230,7 +230,7 @@ async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
@dimmable @dimmable
async def test_invalid_brightness(dev: Bulb): async def test_invalid_brightness(dev: Light):
assert dev.is_dimmable assert dev.is_dimmable
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -241,7 +241,7 @@ async def test_invalid_brightness(dev: Bulb):
@non_dimmable @non_dimmable
async def test_non_dimmable(dev: Bulb): async def test_non_dimmable(dev: Light):
assert not dev.is_dimmable assert not dev.is_dimmable
with pytest.raises(KasaException): with pytest.raises(KasaException):
@ -291,7 +291,7 @@ async def test_modify_preset(dev: IotBulb, mocker):
"saturation": 0, "saturation": 0,
"color_temp": 0, "color_temp": 0,
} }
preset = BulbPreset(**data) preset = LightPreset(**data)
assert preset.index == 0 assert preset.index == 0
assert preset.brightness == 10 assert preset.brightness == 10
@ -305,7 +305,7 @@ async def test_modify_preset(dev: IotBulb, mocker):
with pytest.raises(KasaException): with pytest.raises(KasaException):
await dev.save_preset( await dev.save_preset(
BulbPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0) LightPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0)
) )
@ -314,11 +314,11 @@ async def test_modify_preset(dev: IotBulb, mocker):
("preset", "payload"), ("preset", "payload"),
[ [
( (
BulbPreset(index=0, hue=0, brightness=1, saturation=0), LightPreset(index=0, hue=0, brightness=1, saturation=0),
{"index": 0, "hue": 0, "brightness": 1, "saturation": 0}, {"index": 0, "hue": 0, "brightness": 1, "saturation": 0},
), ),
( (
BulbPreset(index=0, brightness=1, id="testid", mode=2, custom=0), LightPreset(index=0, brightness=1, id="testid", mode=2, custom=0),
{"index": 0, "brightness": 1, "id": "testid", "mode": 2, "custom": 0}, {"index": 0, "brightness": 1, "id": "testid", "mode": 2, "custom": 0},
), ),
], ],

View File

@ -25,6 +25,7 @@ def _get_subclasses(of_class):
inspect.isclass(obj) inspect.isclass(obj)
and issubclass(obj, of_class) and issubclass(obj, of_class)
and module.__package__ != "kasa" and module.__package__ != "kasa"
and module.__package__ != "kasa.interfaces"
): ):
subclasses.add((module.__package__ + "." + name, obj)) subclasses.add((module.__package__ + "." + name, obj))
return subclasses return subclasses