From b521e743ea4d8db5ef9a16514ff4231947398aa2 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:52:47 +0000 Subject: [PATCH 1/4] Update docs for the new module attributes has/get feature --- kasa/module.py | 13 +++++- pyproject.toml | 4 +- uv.lock | 121 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 119 insertions(+), 19 deletions(-) diff --git a/kasa/module.py b/kasa/module.py index ccd22d4e0..483429490 100644 --- a/kasa/module.py +++ b/kasa/module.py @@ -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 check whether the +module has that feature: + +>>> if light.has_feature(light.set_hsv): >>> print(light.hsv) HSV(hue=0, saturation=100, value=100) @@ -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. diff --git a/pyproject.toml b/pyproject.toml index 9fdc888d1..e4e91d455 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ dependencies = [ "aiohttp>=3", "tzdata>=2024.2 ; platform_system == 'Windows'", "mashumaro>=3.14", + "sphinx-autodoc-typehints[docs]>=1.23.0", + "sphinx[docs]==7.4.7", ] classifiers = [ @@ -25,7 +27,7 @@ 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"] shell = ["ptpython", "rich"] [project.urls] diff --git a/uv.lock b/uv.lock index e84a5c356..8df70587e 100644 --- a/uv.lock +++ b/uv.lock @@ -149,6 +149,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, ] +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, +] + [[package]] name = "certifi" version = "2024.8.30" @@ -381,11 +393,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]] @@ -472,6 +484,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] +[[package]] +name = "furo" +version = "2024.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "sphinx-basic-ng" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/e2/d351d69a9a9e4badb4a5be062c2d0e87bd9e6c23b5e57337fef14bef34c8/furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01", size = 1661506 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/48/e791a7ed487dbb9729ef32bb5d1af16693d8925f4366befef54119b2e576/furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c", size = 341333 }, +] + [[package]] name = "identify" version = "2.6.1" @@ -556,14 +583,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 +655,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 +767,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 +777,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]] @@ -1090,6 +1117,8 @@ dependencies = [ { name = "asyncclick" }, { name = "cryptography" }, { name = "mashumaro" }, + { name = "sphinx", extra = ["docs"] }, + { name = "sphinx-autodoc-typehints", extra = ["docs"] }, { name = "tzdata", marker = "platform_system == 'Windows'" }, ] @@ -1097,7 +1126,6 @@ dependencies = [ docs = [ { name = "docutils" }, { name = "myst-parser" }, - { name = "sphinx" }, { name = "sphinx-rtd-theme" }, { name = "sphinxcontrib-programoutput" }, ] @@ -1143,7 +1171,8 @@ 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", extras = ["docs"], specifier = "==7.4.7" }, + { name = "sphinx-autodoc-typehints", extras = ["docs"], specifier = ">=1.23.0" }, { 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" }, @@ -1285,9 +1314,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, ] +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + [[package]] name = "sphinx" -version = "6.2.1" +version = "7.4.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, @@ -1307,9 +1345,44 @@ 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/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624 }, +] + +[package.optional-dependencies] +docs = [ + { name = "sphinxcontrib-websupport" }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/30/9764a2c735c655c3065f32072fb3d8c6fd5dda8df294d4e9f05670d60e31/sphinx_autodoc_typehints-1.23.0.tar.gz", hash = "sha256:5d44e2996633cdada499b6d27a496ddf9dbc95dd1f0f09f7b37940249e61f6e9", size = 35945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/be/792b64ddacfcff362062077689ce37eb9750b9924fc0a14f623fa71ffaf6/sphinx_autodoc_typehints-1.23.0-py3-none-any.whl", hash = "sha256:ac099057e66b09e51b698058ba7dd76e57e1fe696cd91b54e121d3dad188f91d", size = 17896 }, +] + +[package.optional-dependencies] +docs = [ + { name = "furo" }, + { name = "sphinx" }, +] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736 } 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/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496 }, ] [[package]] @@ -1404,6 +1477,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] +[[package]] +name = "sphinxcontrib-websupport" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "sphinx" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/ed/11167ae7c3eb80e7c67823bceea24207bb7b886aefde6c367b4a571c05de/sphinxcontrib_websupport-2.0.0.tar.gz", hash = "sha256:0b7367d3bac6454b1f97e42aa8c4d4d4a1b756d525fc726ebbe5571e033e79cd", size = 600125 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/c9/b9ce2eaacaf75d94a5ee5b809bb2be351f1c9cda5c14316cc53b1aea0650/sphinxcontrib_websupport-2.0.0-py3-none-any.whl", hash = "sha256:365b4da67e03cc163dc4752ed44b3c4bc334172c8198102135a7eb7945111229", size = 37069 }, +] + [[package]] name = "termcolor" version = "2.5.0" From 822db46856c3827e9fa02f601816f1a89a22b308 Mon Sep 17 00:00:00 2001 From: "Steven B." <51370195+sdb9696@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:34:45 +0000 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Teemu R. --- kasa/module.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kasa/module.py b/kasa/module.py index 8f9b4cdc7..0feb22b87 100644 --- a/kasa/module.py +++ b/kasa/module.py @@ -21,7 +21,7 @@ >>> print(light.brightness) 100 -To see whether a device supports specific functionality check whether the +To see whether a device supports specific functionality, you can check whether the module has that feature: >>> if light.has_feature(light.set_hsv): diff --git a/pyproject.toml b/pyproject.toml index ab1cb7d41..616438106 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "tzdata>=2024.2 ; platform_system == 'Windows'", "mashumaro>=3.14", "sphinx-autodoc-typehints[docs]>=1.23.0", - "sphinx[docs]==7.4.7", + "sphinx[docs]>=7.4.7", ] classifiers = [ From 62af913174d3a8f3d7ed3668e53fc217636a3554 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:00:01 +0000 Subject: [PATCH 3/4] Add light bound features --- kasa/interfaces/light.py | 16 ++++++++-------- kasa/module.py | 9 +++++++-- kasa/smart/modules/light.py | 28 +++++++++++++++++++++------- uv.lock | 2 +- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/kasa/interfaces/light.py b/kasa/interfaces/light.py index 298ad1f8e..1d99f846c 100644 --- a/kasa/interfaces/light.py +++ b/kasa/interfaces/light.py @@ -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 @@ -129,7 +129,7 @@ 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, %, %) @@ -137,12 +137,12 @@ def hsv(self) -> HSV: @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 @@ 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. @@ -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. @@ -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. diff --git a/kasa/module.py b/kasa/module.py index 0feb22b87..ba6791b0f 100644 --- a/kasa/module.py +++ b/kasa/module.py @@ -24,7 +24,7 @@ To see whether a device supports specific functionality, you can check whether the module has that feature: ->>> if light.has_feature(light.set_hsv): +>>> if light.has_feature("hsv"): >>> print(light.hsv) HSV(hue=0, saturation=100, value=100) @@ -158,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)) @@ -258,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 diff --git a/kasa/smart/modules/light.py b/kasa/smart/modules/light.py index e637b6075..730988750 100644 --- a/kasa/smart/modules/light.py +++ b/kasa/smart/modules/light.py @@ -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 @@ 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, %, %) @@ -58,7 +72,7 @@ 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.") @@ -66,7 +80,7 @@ def color_temp(self) -> int: 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 @@ 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. @@ -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. @@ -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. diff --git a/uv.lock b/uv.lock index 8df70587e..03b224190 100644 --- a/uv.lock +++ b/uv.lock @@ -1171,7 +1171,7 @@ requires-dist = [ { name = "orjson", marker = "extra == 'speedups'", specifier = ">=3.9.1" }, { name = "ptpython", marker = "extra == 'shell'" }, { name = "rich", marker = "extra == 'shell'" }, - { name = "sphinx", extras = ["docs"], specifier = "==7.4.7" }, + { name = "sphinx", extras = ["docs"], specifier = ">=7.4.7" }, { name = "sphinx-autodoc-typehints", extras = ["docs"], specifier = ">=1.23.0" }, { name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = "~=2.0" }, { name = "sphinxcontrib-programoutput", marker = "extra == 'docs'", specifier = "~=0.0" }, From d69bb12c9138697ca5fad1892f219404521c56aa Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Tue, 26 Nov 2024 08:21:35 +0000 Subject: [PATCH 4/4] Fix sphinx dependency and remove unused extension --- pyproject.toml | 10 ++++-- uv.lock | 91 ++------------------------------------------------ 2 files changed, 9 insertions(+), 92 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 616438106..bce90cb0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,8 +12,6 @@ dependencies = [ "aiohttp>=3", "tzdata>=2024.2 ; platform_system == 'Windows'", "mashumaro>=3.14", - "sphinx-autodoc-typehints[docs]>=1.23.0", - "sphinx[docs]>=7.4.7", ] classifiers = [ @@ -27,7 +25,13 @@ classifiers = [ [project.optional-dependencies] speedups = ["orjson>=3.9.1", "kasa-crypt>=0.2.0"] -docs = ["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] diff --git a/uv.lock b/uv.lock index 03b224190..0ae238c23 100644 --- a/uv.lock +++ b/uv.lock @@ -149,18 +149,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, ] -[[package]] -name = "beautifulsoup4" -version = "4.12.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, -] - [[package]] name = "certifi" version = "2024.8.30" @@ -484,21 +472,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] -[[package]] -name = "furo" -version = "2024.8.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "sphinx-basic-ng" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a0/e2/d351d69a9a9e4badb4a5be062c2d0e87bd9e6c23b5e57337fef14bef34c8/furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01", size = 1661506 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/48/e791a7ed487dbb9729ef32bb5d1af16693d8925f4366befef54119b2e576/furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c", size = 341333 }, -] - [[package]] name = "identify" version = "2.6.1" @@ -1117,8 +1090,6 @@ dependencies = [ { name = "asyncclick" }, { name = "cryptography" }, { name = "mashumaro" }, - { name = "sphinx", extra = ["docs"] }, - { name = "sphinx-autodoc-typehints", extra = ["docs"] }, { name = "tzdata", marker = "platform_system == 'Windows'" }, ] @@ -1126,6 +1097,7 @@ dependencies = [ docs = [ { name = "docutils" }, { name = "myst-parser" }, + { name = "sphinx" }, { name = "sphinx-rtd-theme" }, { name = "sphinxcontrib-programoutput" }, ] @@ -1171,8 +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", extras = ["docs"], specifier = ">=7.4.7" }, - { name = "sphinx-autodoc-typehints", extras = ["docs"], specifier = ">=1.23.0" }, + { 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" }, @@ -1314,15 +1285,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, ] -[[package]] -name = "soupsieve" -version = "2.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, -] - [[package]] name = "sphinx" version = "7.4.7" @@ -1350,41 +1312,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624 }, ] -[package.optional-dependencies] -docs = [ - { name = "sphinxcontrib-websupport" }, -] - -[[package]] -name = "sphinx-autodoc-typehints" -version = "1.23.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/30/9764a2c735c655c3065f32072fb3d8c6fd5dda8df294d4e9f05670d60e31/sphinx_autodoc_typehints-1.23.0.tar.gz", hash = "sha256:5d44e2996633cdada499b6d27a496ddf9dbc95dd1f0f09f7b37940249e61f6e9", size = 35945 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/be/792b64ddacfcff362062077689ce37eb9750b9924fc0a14f623fa71ffaf6/sphinx_autodoc_typehints-1.23.0-py3-none-any.whl", hash = "sha256:ac099057e66b09e51b698058ba7dd76e57e1fe696cd91b54e121d3dad188f91d", size = 17896 }, -] - -[package.optional-dependencies] -docs = [ - { name = "furo" }, - { name = "sphinx" }, -] - -[[package]] -name = "sphinx-basic-ng" -version = "1.0.0b2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496 }, -] - [[package]] name = "sphinx-rtd-theme" version = "2.0.0" @@ -1477,20 +1404,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] -[[package]] -name = "sphinxcontrib-websupport" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "sphinx" }, - { name = "sphinxcontrib-serializinghtml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/ed/11167ae7c3eb80e7c67823bceea24207bb7b886aefde6c367b4a571c05de/sphinxcontrib_websupport-2.0.0.tar.gz", hash = "sha256:0b7367d3bac6454b1f97e42aa8c4d4d4a1b756d525fc726ebbe5571e033e79cd", size = 600125 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/c9/b9ce2eaacaf75d94a5ee5b809bb2be351f1c9cda5c14316cc53b1aea0650/sphinxcontrib_websupport-2.0.0-py3-none-any.whl", hash = "sha256:365b4da67e03cc163dc4752ed44b3c4bc334172c8198102135a7eb7945111229", size = 37069 }, -] - [[package]] name = "termcolor" version = "2.5.0"