From 967892dabaa502b37da59b39182b2da5b84077c4 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Tue, 5 Apr 2022 20:12:36 +0200 Subject: [PATCH 1/7] Add a note about socket sharing --- docs/source/smartdevice.rst | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/source/smartdevice.rst b/docs/source/smartdevice.rst index 6b83c1a57..bfddc0d2f 100644 --- a/docs/source/smartdevice.rst +++ b/docs/source/smartdevice.rst @@ -1,12 +1,19 @@ +.. py:module:: kasa + Common API ====================== The basic functionalities of all supported devices are accessible using the common :class:`SmartDevice` base class. -The property accesses use the data obtained before by awaiting :func:`update()`. +The property accesses use the data obtained before by awaiting :func:`SmartDevice.update()`. The values are cached until the next update call. In practice this means that property accesses do no I/O and are dependent, while I/O producing methods need to be awaited. -Methods changing the state of the device do not invalidate the cache (i.e., there is no implicit `update()`). +.. note:: + The device instances share the communication socket in background to optimize I/O accesses. + This means that you need to use the same event loop for subsequent requests. + The library gives a warning ("Detected protocol reuse between different event loop") to hint if you are accessing the device incorrectly. + +Methods changing the state of the device do not invalidate the cache (i.e., there is no implicit :func:`SmartDevice.update()` call made by the library). You can assume that the operation has succeeded if no exception is raised. These methods will return the device response, which can be useful for some use cases. @@ -22,11 +29,28 @@ Simple example script showing some functionality: async def main(): p = SmartPlug("127.0.0.1") - await p.update() - print(p.alias) + await p.update() # Request the update + print(p.alias) # Print out the alias + print(p.emeter_realtime) # Print out current emeter status + + await p.turn_off() # Turn the device off + + if __name__ == "__main__": + asyncio.run(main()) + +If you want to perform updates in a loop, you need to make sure that the device accesses are done in the same event loop: + +.. code-block:: python - await p.turn_off() + import asyncio + from kasa import SmartPlug + async def main(): + dev = SmartPlug("127.0.0.1") # We create the instance inside the main loop + while True: + await dev.update() # Request an update + print(dev.emeter_realtime) + await asyncio.sleep(0.5) # Sleep some time between updates if __name__ == "__main__": asyncio.run(main()) @@ -44,6 +68,6 @@ Refer to device type specific classes for more examples: API documentation ~~~~~~~~~~~~~~~~~ -.. autoclass:: kasa.SmartDevice +.. autoclass:: SmartDevice :members: :undoc-members: From 1fea80b78c874b905e3de21cee6897a52e017f09 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Tue, 5 Apr 2022 20:15:17 +0200 Subject: [PATCH 2/7] Show inherited members for apidocs --- docs/source/smartbulb.rst | 1 + docs/source/smartdimmer.rst | 1 + docs/source/smartlightstrip.rst | 1 + docs/source/smartplug.rst | 1 + docs/source/smartstrip.rst | 1 + 5 files changed, 5 insertions(+) diff --git a/docs/source/smartbulb.rst b/docs/source/smartbulb.rst index bf58ecbf5..3f2baa407 100644 --- a/docs/source/smartbulb.rst +++ b/docs/source/smartbulb.rst @@ -56,4 +56,5 @@ API documentation .. autoclass:: kasa.SmartBulb :members: + :inherited-members: :undoc-members: diff --git a/docs/source/smartdimmer.rst b/docs/source/smartdimmer.rst index fa4e1ece1..b44d8e0c8 100644 --- a/docs/source/smartdimmer.rst +++ b/docs/source/smartdimmer.rst @@ -10,4 +10,5 @@ API documentation .. autoclass:: kasa.SmartDimmer :members: + :inherited-members: :undoc-members: diff --git a/docs/source/smartlightstrip.rst b/docs/source/smartlightstrip.rst index 4d34efbbd..b961be5bb 100644 --- a/docs/source/smartlightstrip.rst +++ b/docs/source/smartlightstrip.rst @@ -10,4 +10,5 @@ API documentation .. autoclass:: kasa.SmartLightStrip :members: + :inherited-members: :undoc-members: diff --git a/docs/source/smartplug.rst b/docs/source/smartplug.rst index e9b8ccdfd..94305ca87 100644 --- a/docs/source/smartplug.rst +++ b/docs/source/smartplug.rst @@ -11,4 +11,5 @@ API documentation .. autoclass:: kasa.SmartPlug :members: + :inherited-members: :undoc-members: diff --git a/docs/source/smartstrip.rst b/docs/source/smartstrip.rst index edd4953bc..fb589a3d7 100644 --- a/docs/source/smartstrip.rst +++ b/docs/source/smartstrip.rst @@ -34,4 +34,5 @@ API documentation .. autoclass:: kasa.SmartStrip :members: + :inherited-members: :undoc-members: From 1eaf5040dac7b83e332ae8ccb2f9b44108753af2 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Tue, 5 Apr 2022 20:17:37 +0200 Subject: [PATCH 3/7] Remove outdated note of emeters not being supported on smartstrips --- docs/source/smartstrip.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/source/smartstrip.rst b/docs/source/smartstrip.rst index fb589a3d7..66d78f9c6 100644 --- a/docs/source/smartstrip.rst +++ b/docs/source/smartstrip.rst @@ -1,11 +1,6 @@ Smart strips ============ - -.. note:: - - The emeter feature is currently not implemented for smart strips. See https://github.com/python-kasa/python-kasa/issues/64 for details. - .. note:: Feel free to open a pull request to improve the documentation! From 1a35af04d6ea00524bb356965c170d0b8e532d9a Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Tue, 5 Apr 2022 20:59:46 +0200 Subject: [PATCH 4/7] Describe emeter and usage modules, add note about NTP for time sync --- docs/source/smartdevice.rst | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/source/smartdevice.rst b/docs/source/smartdevice.rst index bfddc0d2f..669e0bb8b 100644 --- a/docs/source/smartdevice.rst +++ b/docs/source/smartdevice.rst @@ -57,12 +57,37 @@ If you want to perform updates in a loop, you need to make sure that the device Refer to device type specific classes for more examples: +:class:`SmartPlug`, :class:`SmartBulb`, :class:`SmartStrip`, +:class:`SmartDimmer`, :class:`SmartLightStrip`. -* :class:`SmartPlug` -* :class:`SmartBulb` -* :class:`SmartStrip` -* :class:`SmartDimmer` -* :class:`SmartLightStrip` +Energy Consumption and Usage Statistics +======================================= + +.. note:: + In order to use the helper methods to calculate the statistics correctly, your devices need to have correct time set. + The devices use NTP and public servers from `NTP Pool Project `_ to synchronize their time. + +Energy Consumption +~~~~~~~~~~~~~~~~~~ + +The availability of energy consumption sensors depend on the device. +While most of the bulbs support it, only specific switches (e.g., HS110) or strips (e.g., HS300) support it. +You can use :attr:`~SmartDevice.has_emeter` to check for the availability. + + +Usage statistics +~~~~~~~~~~~~~~~~ + +You can use :attr:`~SmartDevice.on_since` to query for the time the device has been turned on. +Some devices also support reporting the usage statistics on daily or monthly basis. +You can access this information using through the usage module (:class:`kasa.modules.Usage`): + +.. code-block:: python + + dev = SmartPlug("127.0.0.1") + usage = dev.modules["usage"] + print(f"Minutes on this month: {usage.usage_this_month}") + print(f"Minutes on today: {usage.usage_today}") API documentation From 64eca2c2686e80f1863cb78e65a924f6b97f1e82 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Wed, 6 Apr 2022 00:55:02 +0200 Subject: [PATCH 5/7] Describe lib design and modules --- docs/source/design.rst | 50 +++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 1 + docs/source/smartdevice.rst | 1 + 3 files changed, 52 insertions(+) create mode 100644 docs/source/design.rst diff --git a/docs/source/design.rst b/docs/source/design.rst new file mode 100644 index 000000000..b3d6b3591 --- /dev/null +++ b/docs/source/design.rst @@ -0,0 +1,50 @@ +.. py:module:: kasa.modules + +.. _library_design: + +Library Design & Modules +======================== + +This page aims to provide some details on the design and internals of this library. +You might be interested in this if you want to improve this library, +or if you are just looking to access some information that is not currently exposed. + +.. _update_cycle: + +Update Cycle +************ + +When :meth:`~kasa.SmartDevice.update()` is called, +the library constructs a query to send to the device based on :ref:`supported modules `. +Internally, each module defines :meth:`~kasa.modules.Module.query()` to describe what they want query during the update. + +The returned data is cached internally to avoid I/O on property accesses. +All properties defined both in the device class and in the module classes follow this principle. + +While the properties are designed to provide a nice API to use for common use cases, +you may sometimes want to access the raw, cached data as returned by the device. +This can be done using the :attr:`~kasa.SmartDevice.internal_state` property. + +.. _modules: + +Modules +******* + +The functionality provided by all :class:`~kasa.SmartDevice` instances is (mostly) done inside separate modules. +While the individual device-type specific classes provide an easy access for the most import features, +you can also access individual modules through :attr:`kasa.SmartDevice.modules`. +You can get the list of supported modules for a given device instance using :attr:`~kasa.SmartDevice.supported_modules`. + +.. note:: + + If you only need some module-specific information, + you can call the wanted method on the module to avoid using :meth:`~kasa.SmartDevice.update`. + + +API documentation for modules +***************************** + +.. automodule:: kasa.modules + :members: + :inherited-members: + :undoc-members: diff --git a/docs/source/index.rst b/docs/source/index.rst index 2804757e2..711d1474a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -8,6 +8,7 @@ cli discover smartdevice + design smartbulb smartplug smartdimmer diff --git a/docs/source/smartdevice.rst b/docs/source/smartdevice.rst index 669e0bb8b..d80456258 100644 --- a/docs/source/smartdevice.rst +++ b/docs/source/smartdevice.rst @@ -7,6 +7,7 @@ The basic functionalities of all supported devices are accessible using the comm The property accesses use the data obtained before by awaiting :func:`SmartDevice.update()`. The values are cached until the next update call. In practice this means that property accesses do no I/O and are dependent, while I/O producing methods need to be awaited. +See :ref:`library_design` for more detailed information. .. note:: The device instances share the communication socket in background to optimize I/O accesses. From 3aabe1409e5b57593444a52f006427a7f74fe25b Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Wed, 6 Apr 2022 01:02:20 +0200 Subject: [PATCH 6/7] Bump sphinx version, ignore d001 (line-length) for doc8 --- .pre-commit-config.yaml | 6 ++++++ poetry.lock | 19 ++++++++++--------- pyproject.toml | 6 +++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c99d152d9..0fe7bdc44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,3 +37,9 @@ repos: hooks: - id: mypy additional_dependencies: [types-click] + +- repo: https://github.com/PyCQA/doc8 + rev: '0.11.1' + hooks: + - id: doc8 + additional_dependencies: [tomli] diff --git a/poetry.lock b/poetry.lock index aa1231d90..4521b423d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -497,18 +497,19 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.4" +version = "4.5.0" description = "Python documentation generator" category = "main" optional = true -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12,<0.17" +docutils = ">=0.14,<0.18" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -516,14 +517,14 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -754,7 +755,7 @@ docs = ["sphinx", "sphinx_rtd_theme", "m2r", "mistune", "sphinxcontrib-programou [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "cbc8eb721e3b498c25eef73c95b2aa309419fa075b878c18cac0b148113c25f9" +content-hash = "6577513a016c329bc825369761eae9971cb6a18a13c96ac0669c1f51ab3de87d" [metadata.files] alabaster = [ @@ -1074,8 +1075,8 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] sphinx = [ - {file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"}, - {file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"}, + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-0.5.2-py2.py3-none-any.whl", hash = "sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f"}, diff --git a/pyproject.toml b/pyproject.toml index 43e9f6ab0..daf648b5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ asyncclick = ">=8" pydantic = "^1" # required only for docs -sphinx = { version = "^3", optional = true } +sphinx = { version = "^4", optional = true } m2r = { version = "^0", optional = true } mistune = { version = "<2.0.0", optional = true } sphinx_rtd_theme = { version = "^0", optional = true } @@ -81,6 +81,10 @@ markers = [ "requires_dummy: test requires dummy data to pass, skipped on real devices", ] +[tool.doc8] +paths = ["docs"] +ignore = ["D001"] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" From 527b541b045e77fbc509eedc833a8229eb00d9e9 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Wed, 6 Apr 2022 01:04:31 +0200 Subject: [PATCH 7/7] demote energy & usage to 3rd level, promote api for 2nd --- docs/source/smartdevice.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/smartdevice.rst b/docs/source/smartdevice.rst index d80456258..42b838568 100644 --- a/docs/source/smartdevice.rst +++ b/docs/source/smartdevice.rst @@ -1,7 +1,7 @@ .. py:module:: kasa Common API -====================== +========== The basic functionalities of all supported devices are accessible using the common :class:`SmartDevice` base class. @@ -62,7 +62,7 @@ Refer to device type specific classes for more examples: :class:`SmartDimmer`, :class:`SmartLightStrip`. Energy Consumption and Usage Statistics -======================================= +*************************************** .. note:: In order to use the helper methods to calculate the statistics correctly, your devices need to have correct time set. @@ -92,7 +92,7 @@ You can access this information using through the usage module (:class:`kasa.mod API documentation -~~~~~~~~~~~~~~~~~ +***************** .. autoclass:: SmartDevice :members: