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

This commit is contained in:
Steven B. 2024-11-26 08:38:20 +00:00 committed by GitHub
parent fe53cd7d9c
commit cb4e28394d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 70 additions and 34 deletions

View File

@ -64,9 +64,9 @@ from __future__ import annotations
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
@ -129,7 +129,7 @@ class Light(Module, ABC):
@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, %, %)
@ -137,12 +137,12 @@ class Light(Module, ABC):
@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
@ -153,7 +153,7 @@ class Light(Module, ABC):
value: int | None = None,
*,
transition: int | None = None,
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV.
Note, transition is not supported and will be ignored.
@ -167,7 +167,7 @@ class Light(Module, ABC):
@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.
@ -179,7 +179,7 @@ class Light(Module, ABC):
@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.

View File

@ -14,9 +14,17 @@ Light, AutoOff, Firmware etc.
>>> 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)
@ -70,6 +78,9 @@ ModuleT = TypeVar("ModuleT", bound="Module")
class FeatureAttribute:
"""Class for annotating attributes bound to feature."""
def __repr__(self) -> str:
return "FeatureAttribute"
class Module(ABC):
"""Base class implemention for all modules.
@ -147,6 +158,11 @@ class Module(ABC):
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))
@ -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

View File

@ -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
@ -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 {}
@ -47,7 +61,7 @@ class Light(SmartModule, LightInterface):
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, %, %)
@ -58,7 +72,7 @@ class Light(SmartModule, LightInterface):
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.")
@ -66,7 +80,7 @@ class Light(SmartModule, LightInterface):
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.")
@ -80,7 +94,7 @@ class Light(SmartModule, LightInterface):
value: int | None = None,
*,
transition: int | None = None,
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV.
Note, transition is not supported and will be ignored.
@ -97,7 +111,7 @@ class Light(SmartModule, LightInterface):
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.
@ -113,7 +127,7 @@ class Light(SmartModule, LightInterface):
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.

View File

@ -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]

32
uv.lock
View File

@ -381,11 +381,11 @@ wheels = [
[[package]]
name = "docutils"
version = "0.19"
version = "0.20.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/330ea8d383eb2ce973df34d1239b3b21e91cd8c865d21ff82902d952f91f/docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", size = 2056383 }
sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472 },
{ url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 },
]
[[package]]
@ -556,14 +556,14 @@ wheels = [
[[package]]
name = "markdown-it-py"
version = "2.2.0"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/c0/59bd6d0571986f72899288a95d9d6178d0eebd70b6650f1bb3f0da90f8f7/markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1", size = 67120 }
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/25/2d88e8feee8e055d015343f9b86e370a1ccbec546f2865c98397aaef24af/markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", size = 84466 },
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
]
[[package]]
@ -628,14 +628,14 @@ wheels = [
[[package]]
name = "mdit-py-plugins"
version = "0.3.5"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
]
sdist = { url = "https://files.pythonhosted.org/packages/49/e7/cc2720da8a32724b36d04c6dba5644154cdf883a1482b3bbb81959a642ed/mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a", size = 39871 }
sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/4c/a9b222f045f98775034d243198212cbea36d3524c3ee1e8ab8c0346d6953/mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e", size = 52087 },
{ url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 },
]
[[package]]
@ -740,7 +740,7 @@ wheels = [
[[package]]
name = "myst-parser"
version = "1.0.0"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "docutils" },
@ -750,9 +750,9 @@ dependencies = [
{ name = "pyyaml" },
{ name = "sphinx" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5f/69/fbddb50198c6b0901a981e72ae30f1b7769d2dfac88071f7df41c946d133/myst-parser-1.0.0.tar.gz", hash = "sha256:502845659313099542bd38a2ae62f01360e7dd4b1310f025dd014dfc0439cdae", size = 84224 }
sdist = { url = "https://files.pythonhosted.org/packages/85/55/6d1741a1780e5e65038b74bce6689da15f620261c490c3511eb4c12bac4b/myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531", size = 93858 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/1f/1621ef434ac5da26c30d31fcca6d588e3383344902941713640ba717fa87/myst_parser-1.0.0-py3-none-any.whl", hash = "sha256:69fb40a586c6fa68995e6521ac0a525793935db7e724ca9bac1d33be51be9a4c", size = 77312 },
{ url = "https://files.pythonhosted.org/packages/ca/b4/b036f8fdb667587bb37df29dc6644681dd78b7a2a6321a34684b79412b28/myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d", size = 84563 },
]
[[package]]
@ -1143,7 +1143,7 @@ requires-dist = [
{ name = "orjson", marker = "extra == 'speedups'", specifier = ">=3.9.1" },
{ name = "ptpython", marker = "extra == 'shell'" },
{ name = "rich", marker = "extra == 'shell'" },
{ name = "sphinx", marker = "extra == 'docs'", specifier = "~=6.2" },
{ name = "sphinx", marker = "extra == 'docs'", specifier = ">=7.4.7" },
{ name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = "~=2.0" },
{ name = "sphinxcontrib-programoutput", marker = "extra == 'docs'", specifier = "~=0.0" },
{ name = "tzdata", marker = "platform_system == 'Windows'", specifier = ">=2024.2" },
@ -1287,7 +1287,7 @@ wheels = [
[[package]]
name = "sphinx"
version = "6.2.1"
version = "7.4.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "alabaster" },
@ -1307,9 +1307,9 @@ dependencies = [
{ name = "sphinxcontrib-qthelp" },
{ name = "sphinxcontrib-serializinghtml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0f/6d/392defcc95ca48daf62aecb89550143e97a4651275e62a3d7755efe35a3a/Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b", size = 6681092 }
sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/d8/45ba6097c39ba44d9f0e1462fb232e13ca4ddb5aea93a385dcfa964687da/sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912", size = 3024615 },
{ url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624 },
]
[[package]]