diff --git a/kasa/__init__.py b/kasa/__init__.py index e9f64c708..8428154ed 100755 --- a/kasa/__init__.py +++ b/kasa/__init__.py @@ -16,7 +16,6 @@ from typing import TYPE_CHECKING from warnings import warn -from kasa.bulb import Bulb, BulbPreset from kasa.credentials import Credentials from kasa.device import Device from kasa.device_type import DeviceType @@ -36,6 +35,7 @@ UnsupportedDeviceError, ) from kasa.feature import Feature +from kasa.interfaces.light import Light, LightPreset from kasa.iotprotocol import ( IotProtocol, _deprecated_TPLinkSmartHomeProtocol, # noqa: F401 @@ -52,14 +52,14 @@ "BaseProtocol", "IotProtocol", "SmartProtocol", - "BulbPreset", + "LightPreset", "TurnOnBehaviors", "TurnOnBehavior", "DeviceType", "Feature", "EmeterStatus", "Device", - "Bulb", + "Light", "Plug", "Module", "KasaException", @@ -84,7 +84,7 @@ "SmartLightStrip": iot.IotLightStrip, "SmartStrip": iot.IotStrip, "SmartDimmer": iot.IotDimmer, - "SmartBulbPreset": BulbPreset, + "SmartBulbPreset": LightPreset, } deprecated_exceptions = { "SmartDeviceException": KasaException, @@ -124,7 +124,7 @@ def __getattr__(name): SmartLightStrip = iot.IotLightStrip SmartStrip = iot.IotStrip SmartDimmer = iot.IotDimmer - SmartBulbPreset = BulbPreset + SmartBulbPreset = LightPreset SmartDeviceException = KasaException UnsupportedDeviceException = UnsupportedDeviceError diff --git a/kasa/cli.py b/kasa/cli.py index 696dee274..d51679a2f 100755 --- a/kasa/cli.py +++ b/kasa/cli.py @@ -18,7 +18,6 @@ from kasa import ( AuthenticationError, - Bulb, ConnectionType, Credentials, Device, @@ -28,6 +27,7 @@ EncryptType, Feature, KasaException, + Light, UnsupportedDeviceError, ) 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.option("--transition", type=int, required=False) @pass_dev -async def brightness(dev: Bulb, brightness: int, transition: int): +async def brightness(dev: Light, brightness: int, transition: int): """Get or set brightness.""" if not dev.is_dimmable: 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) @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.""" if not dev.is_variable_color_temp: echo("Device does not support color temperature") diff --git a/kasa/interfaces/__init__.py b/kasa/interfaces/__init__.py new file mode 100644 index 000000000..d8d089c5c --- /dev/null +++ b/kasa/interfaces/__init__.py @@ -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", +] diff --git a/kasa/fan.py b/kasa/interfaces/fan.py similarity index 93% rename from kasa/fan.py rename to kasa/interfaces/fan.py index e881136e8..767fe89f1 100644 --- a/kasa/fan.py +++ b/kasa/interfaces/fan.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod -from .device import Device +from ..device import Device class Fan(Device, ABC): diff --git a/kasa/bulb.py b/kasa/interfaces/light.py similarity index 94% rename from kasa/bulb.py rename to kasa/interfaces/light.py index 52a722d92..141be1fdf 100644 --- a/kasa/bulb.py +++ b/kasa/interfaces/light.py @@ -7,7 +7,7 @@ from pydantic.v1 import BaseModel -from .device import Device +from ..device import Device class ColorTempRange(NamedTuple): @@ -25,8 +25,8 @@ class HSV(NamedTuple): value: int -class BulbPreset(BaseModel): - """Bulb configuration preset.""" +class LightPreset(BaseModel): + """Light configuration preset.""" index: int brightness: int @@ -42,8 +42,8 @@ class BulbPreset(BaseModel): mode: Optional[int] # noqa: UP007 -class Bulb(Device, ABC): - """Base class for TP-Link Bulb.""" +class Light(Device, ABC): + """Base class for TP-Link Light.""" def _raise_for_invalid_brightness(self, value): if not isinstance(value, int) or not (0 <= value <= 100): @@ -135,5 +135,5 @@ async def set_brightness( @property @abstractmethod - def presets(self) -> list[BulbPreset]: + def presets(self) -> list[LightPreset]: """Return a list of available bulb setting presets.""" diff --git a/kasa/iot/iotbulb.py b/kasa/iot/iotbulb.py index 92bf98147..f6135fd18 100644 --- a/kasa/iot/iotbulb.py +++ b/kasa/iot/iotbulb.py @@ -9,10 +9,10 @@ from pydantic.v1 import BaseModel, Field, root_validator -from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange from ..device_type import DeviceType from ..deviceconfig import DeviceConfig from ..feature import Feature +from ..interfaces.light import HSV, ColorTempRange, Light, LightPreset from ..module import Module from ..protocol import BaseProtocol from .iotdevice import IotDevice, KasaException, requires_update @@ -88,7 +88,7 @@ class TurnOnBehaviors(BaseModel): _LOGGER = logging.getLogger(__name__) -class IotBulb(IotDevice, Bulb): +class IotBulb(IotDevice, Light): r"""Representation of a TP-Link Smart Bulb. 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.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: >>> preset = bulb.presets[0] @@ -523,11 +523,11 @@ async def set_alias(self, alias: str) -> None: @property # type: ignore @requires_update - def presets(self) -> list[BulbPreset]: + def presets(self) -> list[LightPreset]: """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. You can either construct a preset object manually, or pass an existing one diff --git a/kasa/smart/modules/color.py b/kasa/smart/modules/color.py index 979d4fec0..88d029082 100644 --- a/kasa/smart/modules/color.py +++ b/kasa/smart/modules/color.py @@ -4,8 +4,8 @@ from typing import TYPE_CHECKING -from ...bulb import HSV from ...feature import Feature +from ...interfaces.light import HSV from ..smartmodule import SmartModule if TYPE_CHECKING: diff --git a/kasa/smart/modules/colortemperature.py b/kasa/smart/modules/colortemperature.py index 88d5ea211..fa3b74126 100644 --- a/kasa/smart/modules/colortemperature.py +++ b/kasa/smart/modules/colortemperature.py @@ -5,8 +5,8 @@ import logging from typing import TYPE_CHECKING -from ...bulb import ColorTempRange from ...feature import Feature +from ...interfaces.light import ColorTempRange from ..smartmodule import SmartModule if TYPE_CHECKING: diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 122c943b5..e7b45c8e2 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -8,14 +8,14 @@ from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast from ..aestransport import AesTransport -from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange from ..device import Device, WifiNetwork from ..device_type import DeviceType from ..deviceconfig import DeviceConfig from ..emeterstatus import EmeterStatus from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode -from ..fan import Fan from ..feature import Feature +from ..interfaces.fan import Fan +from ..interfaces.light import HSV, ColorTempRange, Light, LightPreset from ..module import Module from ..modulemapping import ModuleMapping, ModuleName from ..smartprotocol import SmartProtocol @@ -39,7 +39,7 @@ # Device must go last as the other interfaces also inherit Device # 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.""" def __init__( @@ -766,7 +766,7 @@ async def set_brightness( return await self.modules[Module.Brightness].set_brightness(brightness) @property - def presets(self) -> list[BulbPreset]: + def presets(self) -> list[LightPreset]: """Return a list of available bulb setting presets.""" return [] diff --git a/kasa/tests/test_bulb.py b/kasa/tests/test_bulb.py index acee8f74c..19400c836 100644 --- a/kasa/tests/test_bulb.py +++ b/kasa/tests/test_bulb.py @@ -7,7 +7,7 @@ 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.smart import SmartDevice @@ -65,7 +65,7 @@ async def test_get_light_state(dev: IotBulb): @color_bulb @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) assert dev.is_color @@ -96,7 +96,7 @@ async def test_set_hsv_transition(dev: IotBulb, mocker): @color_bulb @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) assert dev.is_color @@ -116,13 +116,13 @@ async def test_invalid_hsv(dev: Bulb, turn_on): @color_bulb @pytest.mark.skip("requires color feature") async def test_color_state_information(dev: Device): - assert isinstance(dev, Bulb) + assert isinstance(dev, Light) assert "HSV" in dev.state_information assert dev.state_information["HSV"] == dev.hsv @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 with pytest.raises(KasaException): @@ -134,7 +134,7 @@ async def test_hsv_on_non_color(dev: Bulb): @variable_temp @pytest.mark.skip("requires colortemp module") 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 dev.state_information["Color temperature"] == dev.color_temp @@ -142,7 +142,7 @@ async def test_variable_temp_state_information(dev: Device): @variable_temp @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 dev.set_color_temp(2700) await dev.update() @@ -171,7 +171,7 @@ async def test_smart_temp_range(dev: SmartDevice): @variable_temp -async def test_out_of_range_temperature(dev: Bulb): +async def test_out_of_range_temperature(dev: Light): with pytest.raises(ValueError): await dev.set_color_temp(1000) with pytest.raises(ValueError): @@ -179,7 +179,7 @@ async def test_out_of_range_temperature(dev: Bulb): @non_variable_temp -async def test_non_variable_temp(dev: Bulb): +async def test_non_variable_temp(dev: Light): with pytest.raises(KasaException): await dev.set_color_temp(2700) @@ -193,7 +193,7 @@ async def test_non_variable_temp(dev: Bulb): @dimmable @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) assert dev.is_dimmable @@ -230,7 +230,7 @@ async def test_dimmable_brightness_transition(dev: IotBulb, mocker): @dimmable -async def test_invalid_brightness(dev: Bulb): +async def test_invalid_brightness(dev: Light): assert dev.is_dimmable with pytest.raises(ValueError): @@ -241,7 +241,7 @@ async def test_invalid_brightness(dev: Bulb): @non_dimmable -async def test_non_dimmable(dev: Bulb): +async def test_non_dimmable(dev: Light): assert not dev.is_dimmable with pytest.raises(KasaException): @@ -291,7 +291,7 @@ async def test_modify_preset(dev: IotBulb, mocker): "saturation": 0, "color_temp": 0, } - preset = BulbPreset(**data) + preset = LightPreset(**data) assert preset.index == 0 assert preset.brightness == 10 @@ -305,7 +305,7 @@ async def test_modify_preset(dev: IotBulb, mocker): with pytest.raises(KasaException): 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"), [ ( - 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}, ), ( - 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}, ), ], diff --git a/kasa/tests/test_device.py b/kasa/tests/test_device.py index d0ed0c71e..76ea1acf6 100644 --- a/kasa/tests/test_device.py +++ b/kasa/tests/test_device.py @@ -25,6 +25,7 @@ def _get_subclasses(of_class): inspect.isclass(obj) and issubclass(obj, of_class) and module.__package__ != "kasa" + and module.__package__ != "kasa.interfaces" ): subclasses.add((module.__package__ + "." + name, obj)) return subclasses