Skip to content

Update docs for the new module attributes has/get feature #1301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions kasa/interfaces/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import NamedTuple
from typing import Annotated, NamedTuple

from ..module import Module
from ..module import FeatureAttribute, Module


@dataclass
Expand Down Expand Up @@ -129,20 +129,20 @@ def has_effects(self) -> bool:

@property
@abstractmethod
def hsv(self) -> HSV:
def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
"""Return the current HSV state of the bulb.

:return: hue, saturation and value (degrees, %, %)
"""

@property
@abstractmethod
def color_temp(self) -> int:
def color_temp(self) -> Annotated[int, FeatureAttribute()]:
"""Whether the bulb supports color temperature changes."""

@property
@abstractmethod
def brightness(self) -> int:
def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Return the current brightness in percentage."""

@abstractmethod
Expand All @@ -153,7 +153,7 @@ async def set_hsv(
value: int | None = None,
*,
transition: int | None = None,
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV.

Note, transition is not supported and will be ignored.
Expand All @@ -167,7 +167,7 @@ async def set_hsv(
@abstractmethod
async def set_color_temp(
self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set the color temperature of the device in kelvin.

Note, transition is not supported and will be ignored.
Expand All @@ -179,7 +179,7 @@ async def set_color_temp(
@abstractmethod
async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage.

Note, transition is not supported and will be ignored.
Expand Down
20 changes: 18 additions & 2 deletions kasa/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@
>>> print(dev.alias)
Living Room Bulb

To see whether a device supports functionality check for the existence of the module:
To see whether a device supports a group of functionality
check for the existence of the module:

>>> if light := dev.modules.get("Light"):
>>> print(light.brightness)
100

To see whether a device supports specific functionality, you can check whether the
module has that feature:

>>> if light.has_feature("hsv"):
>>> print(light.hsv)
HSV(hue=0, saturation=100, value=100)

Expand Down Expand Up @@ -70,6 +78,9 @@
class FeatureAttribute:
"""Class for annotating attributes bound to feature."""

def __repr__(self) -> str:
return "FeatureAttribute"


class Module(ABC):
"""Base class implemention for all modules.
Expand Down Expand Up @@ -147,6 +158,11 @@ def __init__(self, device: Device, module: str) -> None:
self._module = module
self._module_features: dict[str, Feature] = {}

@property
def _all_features(self) -> dict[str, Feature]:
"""Get the features for this module and any sub modules."""
return self._module_features

def has_feature(self, attribute: str | property | Callable) -> bool:
"""Return True if the module attribute feature is supported."""
return bool(self.get_feature(attribute))
Expand Down Expand Up @@ -247,7 +263,7 @@ def _get_bound_feature(
)

check = {attribute_name, attribute_callable}
for feature in module._module_features.values():
for feature in module._all_features.values():
if (getter := feature.attribute_getter) and getter in check:
return feature

Expand Down
28 changes: 21 additions & 7 deletions kasa/smart/modules/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from __future__ import annotations

from dataclasses import asdict
from typing import Annotated

from ...exceptions import KasaException
from ...feature import Feature
from ...interfaces.light import HSV, ColorTempRange, LightState
from ...interfaces.light import Light as LightInterface
from ...module import Module
from ...module import FeatureAttribute, Module
from ..smartmodule import SmartModule


Expand All @@ -16,6 +18,18 @@ class Light(SmartModule, LightInterface):

_light_state: LightState

@property
def _all_features(self) -> dict[str, Feature]:
"""Get the features for this module and any sub modules."""
ret: dict[str, Feature] = {}
if brightness := self._device.modules.get(Module.Brightness):
ret.update(**brightness._module_features)
if color := self._device.modules.get(Module.Color):
ret.update(**color._module_features)
if temp := self._device.modules.get(Module.ColorTemperature):
ret.update(**temp._module_features)
return ret

def query(self) -> dict:
"""Query to execute during the update cycle."""
return {}
Expand Down Expand Up @@ -47,7 +61,7 @@ def valid_temperature_range(self) -> ColorTempRange:
return self._device.modules[Module.ColorTemperature].valid_temperature_range

@property
def hsv(self) -> HSV:
def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
"""Return the current HSV state of the bulb.

:return: hue, saturation and value (degrees, %, %)
Expand All @@ -58,15 +72,15 @@ def hsv(self) -> HSV:
return self._device.modules[Module.Color].hsv

@property
def color_temp(self) -> int:
def color_temp(self) -> Annotated[int, FeatureAttribute()]:
"""Whether the bulb supports color temperature changes."""
if not self.is_variable_color_temp:
raise KasaException("Bulb does not support colortemp.")

return self._device.modules[Module.ColorTemperature].color_temp

@property
def brightness(self) -> int:
def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Return the current brightness in percentage."""
if not self.is_dimmable: # pragma: no cover
raise KasaException("Bulb is not dimmable.")
Expand All @@ -80,7 +94,7 @@ async def set_hsv(
value: int | None = None,
*,
transition: int | None = None,
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV.

Note, transition is not supported and will be ignored.
Expand All @@ -97,7 +111,7 @@ async def set_hsv(

async def set_color_temp(
self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set the color temperature of the device in kelvin.

Note, transition is not supported and will be ignored.
Expand All @@ -113,7 +127,7 @@ async def set_color_temp(

async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage.

Note, transition is not supported and will be ignored.
Expand Down
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ classifiers = [

[project.optional-dependencies]
speedups = ["orjson>=3.9.1", "kasa-crypt>=0.2.0"]
docs = ["sphinx~=6.2", "sphinx_rtd_theme~=2.0", "sphinxcontrib-programoutput~=0.0", "myst-parser", "docutils>=0.17"]
docs = [
"sphinx_rtd_theme~=2.0",
"sphinxcontrib-programoutput~=0.0",
"myst-parser",
"docutils>=0.17",
"sphinx>=7.4.7",
]
shell = ["ptpython", "rich"]

[project.urls]
Expand Down
32 changes: 16 additions & 16 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading