mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Add tutorial doctest module and enable top level await (#919)
Add a tutorial module with examples that can be tested with `doctest`. In order to simplify the examples they can be run with doctest allowing top level await statements by adding a fixture to patch the builtins that xdoctest uses to test code. --------- Co-authored-by: Teemu R. <tpr@iki.fi>
This commit is contained in:
parent
a2e8d2c4e8
commit
3490a1ef84
@ -171,7 +171,8 @@ Current state: {'total': 133.105, 'power': 108.223577, 'current': 0.54463, 'volt
|
|||||||
|
|
||||||
# Library usage
|
# Library usage
|
||||||
|
|
||||||
If you want to use this library in your own project, a good starting point is to check [the documentation on discovering devices](https://python-kasa.readthedocs.io/en/latest/discover.html).
|
If you want to use this library in your own project, a good starting point is [the tutorial in the documentation](https://python-kasa.readthedocs.io/en/latest/tutorial.html).
|
||||||
|
|
||||||
You can find several code examples in the API documentation of each of the implementation base classes, check out the [documentation for the base class shared by all supported devices](https://python-kasa.readthedocs.io/en/latest/smartdevice.html).
|
You can find several code examples in the API documentation of each of the implementation base classes, check out the [documentation for the base class shared by all supported devices](https://python-kasa.readthedocs.io/en/latest/smartdevice.html).
|
||||||
|
|
||||||
[The library design and module structure is described in a separate page](https://python-kasa.readthedocs.io/en/latest/design.html).
|
[The library design and module structure is described in a separate page](https://python-kasa.readthedocs.io/en/latest/design.html).
|
||||||
|
@ -10,9 +10,10 @@
|
|||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
#
|
#
|
||||||
# import os
|
import os
|
||||||
# import sys
|
import sys
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
|
||||||
|
sys.path.insert(0, os.path.abspath("..")) # Will find modules in the docs parent
|
||||||
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
@ -22,13 +22,13 @@ Use :func:`~kasa.Discover.discover` to perform udp-based broadcast discovery on
|
|||||||
This will return you a list of device instances based on the discovery replies.
|
This will return you a list of device instances based on the discovery replies.
|
||||||
|
|
||||||
If the device's host is already known, you can use to construct a device instance with
|
If the device's host is already known, you can use to construct a device instance with
|
||||||
:meth:`~kasa.SmartDevice.connect()`.
|
:meth:`~kasa.Device.connect()`.
|
||||||
|
|
||||||
The :meth:`~kasa.SmartDevice.connect()` also enables support for connecting to new
|
The :meth:`~kasa.Device.connect()` also enables support for connecting to new
|
||||||
KASA SMART protocol and TAPO devices directly using the parameter :class:`~kasa.DeviceConfig`.
|
KASA SMART protocol and TAPO devices directly using the parameter :class:`~kasa.DeviceConfig`.
|
||||||
Simply serialize the :attr:`~kasa.SmartDevice.config` property via :meth:`~kasa.DeviceConfig.to_dict()`
|
Simply serialize the :attr:`~kasa.Device.config` property via :meth:`~kasa.DeviceConfig.to_dict()`
|
||||||
and then deserialize it later with :func:`~kasa.DeviceConfig.from_dict()`
|
and then deserialize it later with :func:`~kasa.DeviceConfig.from_dict()`
|
||||||
and then pass it into :meth:`~kasa.SmartDevice.connect()`.
|
and then pass it into :meth:`~kasa.Device.connect()`.
|
||||||
|
|
||||||
|
|
||||||
.. _update_cycle:
|
.. _update_cycle:
|
||||||
@ -36,7 +36,7 @@ and then pass it into :meth:`~kasa.SmartDevice.connect()`.
|
|||||||
Update Cycle
|
Update Cycle
|
||||||
************
|
************
|
||||||
|
|
||||||
When :meth:`~kasa.SmartDevice.update()` is called,
|
When :meth:`~kasa.Device.update()` is called,
|
||||||
the library constructs a query to send to the device based on :ref:`supported modules <modules>`.
|
the library constructs a query to send to the device based on :ref:`supported modules <modules>`.
|
||||||
Internally, each module defines :meth:`~kasa.modules.Module.query()` to describe what they want query during the update.
|
Internally, each module defines :meth:`~kasa.modules.Module.query()` to describe what they want query during the update.
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ All properties defined both in the device class and in the module classes follow
|
|||||||
|
|
||||||
While the properties are designed to provide a nice API to use for common use cases,
|
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.
|
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.
|
This can be done using the :attr:`~kasa.Device.internal_state` property.
|
||||||
|
|
||||||
|
|
||||||
.. _modules:
|
.. _modules:
|
||||||
@ -53,15 +53,15 @@ 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.
|
The functionality provided by all :class:`~kasa.Device` instances is (mostly) done inside separate modules.
|
||||||
While the individual device-type specific classes provide an easy access for the most import features,
|
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 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`.
|
You can get the list of supported modules for a given device instance using :attr:`~kasa.Device.supported_modules`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you only need some module-specific information,
|
If you only need some module-specific information,
|
||||||
you can call the wanted method on the module to avoid using :meth:`~kasa.SmartDevice.update`.
|
you can call the wanted method on the module to avoid using :meth:`~kasa.Device.update`.
|
||||||
|
|
||||||
Protocols and Transports
|
Protocols and Transports
|
||||||
************************
|
************************
|
||||||
@ -112,10 +112,22 @@ The base exception for all library errors is :class:`KasaException <kasa.excepti
|
|||||||
- If the device fails to respond within a timeout the library raises a :class:`TimeoutError <kasa.exceptions.TimeoutError>`.
|
- If the device fails to respond within a timeout the library raises a :class:`TimeoutError <kasa.exceptions.TimeoutError>`.
|
||||||
- All other failures will raise the base :class:`KasaException <kasa.exceptions.KasaException>` class.
|
- All other failures will raise the base :class:`KasaException <kasa.exceptions.KasaException>` class.
|
||||||
|
|
||||||
API documentation for modules
|
API documentation for modules and features
|
||||||
*****************************
|
******************************************
|
||||||
|
|
||||||
.. automodule:: kasa.modules
|
.. autoclass:: kasa.Module
|
||||||
|
:noindex:
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
||||||
|
.. automodule:: kasa.interfaces
|
||||||
|
:noindex:
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
||||||
|
.. autoclass:: kasa.Feature
|
||||||
:noindex:
|
:noindex:
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
@ -6,12 +6,12 @@ Common API
|
|||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
SmartDevice class
|
Device class
|
||||||
*****************
|
************
|
||||||
|
|
||||||
The basic functionalities of all supported devices are accessible using the common :class:`SmartDevice` base class.
|
The basic functionalities of all supported devices are accessible using the common :class:`Device` base class.
|
||||||
|
|
||||||
The property accesses use the data obtained before by awaiting :func:`SmartDevice.update()`.
|
The property accesses use the data obtained before by awaiting :func:`Device.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.
|
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.
|
See :ref:`library_design` for more detailed information.
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ See :ref:`library_design` for more detailed information.
|
|||||||
This means that you need to use the same event loop for subsequent requests.
|
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.
|
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).
|
Methods changing the state of the device do not invalidate the cache (i.e., there is no implicit :func:`Device.update()` call made by the library).
|
||||||
You can assume that the operation has succeeded if no exception is raised.
|
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.
|
These methods will return the device response, which can be useful for some use cases.
|
||||||
|
|
||||||
@ -103,10 +103,10 @@ Currently there are three known types of encryption for TP-Link devices and two
|
|||||||
Devices with automatic firmware updates enabled may update to newer versions of the encryption without separate notice,
|
Devices with automatic firmware updates enabled may update to newer versions of the encryption without separate notice,
|
||||||
so discovery can be helpful to determine the correct config.
|
so discovery can be helpful to determine the correct config.
|
||||||
|
|
||||||
To connect directly pass a :class:`DeviceConfig` object to :meth:`SmartDevice.connect()`.
|
To connect directly pass a :class:`DeviceConfig` object to :meth:`Device.connect()`.
|
||||||
|
|
||||||
A :class:`DeviceConfig` can be constucted manually if you know the :attr:`DeviceConfig.connection_type` values for the device or
|
A :class:`DeviceConfig` can be constucted manually if you know the :attr:`DeviceConfig.connection_type` values for the device or
|
||||||
alternatively the config can be retrieved from :attr:`SmartDevice.config` post discovery and then re-used.
|
alternatively the config can be retrieved from :attr:`Device.config` post discovery and then re-used.
|
||||||
|
|
||||||
Energy Consumption and Usage Statistics
|
Energy Consumption and Usage Statistics
|
||||||
***************************************
|
***************************************
|
||||||
@ -141,7 +141,7 @@ You can access this information using through the usage module (:class:`kasa.mod
|
|||||||
API documentation
|
API documentation
|
||||||
*****************
|
*****************
|
||||||
|
|
||||||
.. autoclass:: SmartDevice
|
.. autoclass:: Device
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
@ -13,7 +13,7 @@ Discovery works by sending broadcast UDP packets to two known TP-link discovery
|
|||||||
Port 9999 is used for legacy devices that do not use strong encryption and 20002 is for newer devices that use different
|
Port 9999 is used for legacy devices that do not use strong encryption and 20002 is for newer devices that use different
|
||||||
levels of encryption.
|
levels of encryption.
|
||||||
If a device uses port 20002 for discovery you will obtain some basic information from the device via discovery, but you
|
If a device uses port 20002 for discovery you will obtain some basic information from the device via discovery, but you
|
||||||
will need to await :func:`SmartDevice.update() <kasa.SmartDevice.update()>` to get full device information.
|
will need to await :func:`Device.update() <kasa.SmartDevice.update()>` to get full device information.
|
||||||
Credentials will most likely be required for port 20002 devices although if the device has never been connected to the tplink
|
Credentials will most likely be required for port 20002 devices although if the device has never been connected to the tplink
|
||||||
cloud it may work without credentials.
|
cloud it may work without credentials.
|
||||||
|
|
||||||
|
@ -7,8 +7,9 @@
|
|||||||
|
|
||||||
Home <self>
|
Home <self>
|
||||||
cli
|
cli
|
||||||
|
tutorial
|
||||||
discover
|
discover
|
||||||
smartdevice
|
device
|
||||||
design
|
design
|
||||||
contribute
|
contribute
|
||||||
smartbulb
|
smartbulb
|
||||||
|
@ -67,13 +67,13 @@ API documentation
|
|||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
.. autoclass:: kasa.smartbulb.BehaviorMode
|
.. autoclass:: kasa.iot.iotbulb.BehaviorMode
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: kasa.TurnOnBehaviors
|
.. autoclass:: kasa.iot.iotbulb.TurnOnBehaviors
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
.. autoclass:: kasa.TurnOnBehavior
|
.. autoclass:: kasa.iot.iotbulb.TurnOnBehavior
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:members:
|
:members:
|
||||||
|
8
docs/source/tutorial.md
Normal file
8
docs/source/tutorial.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Tutorial
|
||||||
|
|
||||||
|
```{eval-rst}
|
||||||
|
.. automodule:: tutorial
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:undoc-members:
|
||||||
|
```
|
103
docs/tutorial.py
Normal file
103
docs/tutorial.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# ruff: noqa
|
||||||
|
"""
|
||||||
|
The kasa library is fully async and methods that perform IO need to be run inside an async couroutine.
|
||||||
|
|
||||||
|
These examples assume you are following the tutorial inside `asyncio REPL` (python -m asyncio) or the code
|
||||||
|
is running inside an async function (`async def`).
|
||||||
|
|
||||||
|
|
||||||
|
The main entry point for the API is :meth:`~kasa.Discover.discover` and
|
||||||
|
:meth:`~kasa.Discover.discover_single` which return Device objects.
|
||||||
|
|
||||||
|
Most newer devices require your TP-Link cloud username and password, but this can be omitted for older devices.
|
||||||
|
|
||||||
|
>>> from kasa import Device, Discover, Credentials
|
||||||
|
|
||||||
|
:func:`~kasa.Discover.discover` returns a list of devices on your network:
|
||||||
|
|
||||||
|
>>> devices = await Discover.discover(credentials=Credentials("user@example.com", "great_password"))
|
||||||
|
>>> for dev in devices:
|
||||||
|
>>> await dev.update()
|
||||||
|
>>> print(dev.host)
|
||||||
|
127.0.0.1
|
||||||
|
127.0.0.2
|
||||||
|
|
||||||
|
:meth:`~kasa.Discover.discover_single` returns a single device by hostname:
|
||||||
|
|
||||||
|
>>> dev = await Discover.discover_single("127.0.0.1", credentials=Credentials("user@example.com", "great_password"))
|
||||||
|
>>> await dev.update()
|
||||||
|
>>> dev.alias
|
||||||
|
Living Room
|
||||||
|
>>> dev.model
|
||||||
|
L530
|
||||||
|
>>> dev.rssi
|
||||||
|
-52
|
||||||
|
>>> dev.mac
|
||||||
|
5C:E9:31:00:00:00
|
||||||
|
|
||||||
|
You can update devices by calling different methods (e.g., ``set_``-prefixed ones).
|
||||||
|
Note, that these do not update the internal state, but you need to call :meth:`~kasa.Device.update()` to query the device again.
|
||||||
|
back to the device.
|
||||||
|
|
||||||
|
>>> await dev.set_alias("Dining Room")
|
||||||
|
>>> await dev.update()
|
||||||
|
>>> dev.alias
|
||||||
|
Dining Room
|
||||||
|
|
||||||
|
Different groups of functionality are supported by modules which you can access via :attr:`~kasa.Device.modules` with a typed
|
||||||
|
key from :class:`~kasa.Module`.
|
||||||
|
|
||||||
|
Modules will only be available on the device if they are supported but some individual features of a module may not be available for your device.
|
||||||
|
You can check the availability using ``is_``-prefixed properties like `is_color`.
|
||||||
|
|
||||||
|
>>> from kasa import Module
|
||||||
|
>>> Module.Light in dev.modules
|
||||||
|
True
|
||||||
|
>>> light = dev.modules[Module.Light]
|
||||||
|
>>> light.brightness
|
||||||
|
100
|
||||||
|
>>> await light.set_brightness(50)
|
||||||
|
>>> await dev.update()
|
||||||
|
>>> light.brightness
|
||||||
|
50
|
||||||
|
>>> light.is_color
|
||||||
|
True
|
||||||
|
>>> if light.is_color:
|
||||||
|
>>> print(light.hsv)
|
||||||
|
HSV(hue=0, saturation=100, value=50)
|
||||||
|
|
||||||
|
You can test if a module is supported by using `get` to access it.
|
||||||
|
|
||||||
|
>>> if effect := dev.modules.get(Module.LightEffect):
|
||||||
|
>>> print(effect.effect)
|
||||||
|
>>> print(effect.effect_list)
|
||||||
|
>>> if effect := dev.modules.get(Module.LightEffect):
|
||||||
|
>>> await effect.set_effect("Party")
|
||||||
|
>>> await dev.update()
|
||||||
|
>>> print(effect.effect)
|
||||||
|
Off
|
||||||
|
['Off', 'Party', 'Relax']
|
||||||
|
Party
|
||||||
|
|
||||||
|
Individual pieces of functionality are also exposed via features which you can access via :attr:`~kasa.Device.features` and will only be present if they are supported.
|
||||||
|
|
||||||
|
Features are similar to modules in that they provide functionality that may or may not be present.
|
||||||
|
|
||||||
|
Whereas modules group functionality into a common interface, features expose a single function that may or may not be part of a module.
|
||||||
|
|
||||||
|
The advantage of features is that they have a simple common interface of `id`, `name`, `value` and `set_value` so no need to learn the module API.
|
||||||
|
|
||||||
|
They are useful if you want write code that dynamically adapts as new features are added to the API.
|
||||||
|
|
||||||
|
>>> if auto_update := dev.features.get("auto_update_enabled"):
|
||||||
|
>>> print(auto_update.value)
|
||||||
|
False
|
||||||
|
>>> if auto_update:
|
||||||
|
>>> await auto_update.set_value(True)
|
||||||
|
>>> await dev.update()
|
||||||
|
>>> print(auto_update.value)
|
||||||
|
True
|
||||||
|
>>> for feat in dev.features.values():
|
||||||
|
>>> print(f"{feat.name}: {feat.value}")
|
||||||
|
Device ID: 0000000000000000000000000000000000000000\nState: True\nSignal Level: 2\nRSSI: -52\nSSID: #MASKED_SSID#\nOverheated: False\nBrightness: 50\nCloud connection: True\nHSV: HSV(hue=0, saturation=100, value=50)\nColor temperature: 2700\nAuto update enabled: True\nUpdate available: False\nCurrent firmware version: 1.1.6 Build 240130 Rel.173828\nAvailable firmware version: 1.1.6 Build 240130 Rel.173828\nLight effect: Party\nSmooth transition on: 2\nSmooth transition off: 2\nTime: 2024-02-23 02:40:15+01:00
|
||||||
|
"""
|
@ -1,12 +1,12 @@
|
|||||||
"""Python interface for TP-Link's smart home devices.
|
"""Python interface for TP-Link's smart home devices.
|
||||||
|
|
||||||
All common, shared functionalities are available through `SmartDevice` class::
|
All common, shared functionalities are available through `Device` class::
|
||||||
|
|
||||||
x = SmartDevice("192.168.1.1")
|
>>> from kasa import Discover
|
||||||
print(x.sys_info)
|
>>> x = await Discover.discover_single("192.168.1.1")
|
||||||
|
>>> print(x.model)
|
||||||
|
|
||||||
For device type specific actions `SmartBulb`, `SmartPlug`, or `SmartStrip`
|
For device type specific actions `modules` and `features` should be used instead.
|
||||||
should be used instead.
|
|
||||||
|
|
||||||
Module-specific errors are raised as `KasaException` and are expected
|
Module-specific errors are raised as `KasaException` and are expected
|
||||||
to be handled by the user of the library.
|
to be handled by the user of the library.
|
||||||
|
@ -150,7 +150,7 @@ class DeviceConfig:
|
|||||||
credentials: Optional[Credentials] = None
|
credentials: Optional[Credentials] = None
|
||||||
#: Credentials hash for devices requiring authentication.
|
#: Credentials hash for devices requiring authentication.
|
||||||
#: If credentials are also supplied they take precendence over credentials_hash.
|
#: If credentials are also supplied they take precendence over credentials_hash.
|
||||||
#: Credentials hash can be retrieved from :attr:`SmartDevice.credentials_hash`
|
#: Credentials hash can be retrieved from :attr:`Device.credentials_hash`
|
||||||
credentials_hash: Optional[str] = None
|
credentials_hash: Optional[str] = None
|
||||||
#: The protocol specific type of connection. Defaults to the legacy type.
|
#: The protocol specific type of connection. Defaults to the legacy type.
|
||||||
batch_size: Optional[int] = None
|
batch_size: Optional[int] = None
|
||||||
|
@ -270,10 +270,10 @@ class Discover:
|
|||||||
you can use *target* parameter to specify the network for discovery.
|
you can use *target* parameter to specify the network for discovery.
|
||||||
|
|
||||||
If given, `on_discovered` coroutine will get awaited with
|
If given, `on_discovered` coroutine will get awaited with
|
||||||
a :class:`SmartDevice`-derived object as parameter.
|
a :class:`Device`-derived object as parameter.
|
||||||
|
|
||||||
The results of the discovery are returned as a dict of
|
The results of the discovery are returned as a dict of
|
||||||
:class:`SmartDevice`-derived objects keyed with IP addresses.
|
:class:`Device`-derived objects keyed with IP addresses.
|
||||||
The devices are already initialized and all but emeter-related properties
|
The devices are already initialized and all but emeter-related properties
|
||||||
can be accessed directly.
|
can be accessed directly.
|
||||||
|
|
||||||
@ -332,7 +332,7 @@ class Discover:
|
|||||||
"""Discover a single device by the given IP address.
|
"""Discover a single device by the given IP address.
|
||||||
|
|
||||||
It is generally preferred to avoid :func:`discover_single()` and
|
It is generally preferred to avoid :func:`discover_single()` and
|
||||||
use :meth:`SmartDevice.connect()` instead as it should perform better when
|
use :meth:`Device.connect()` instead as it should perform better when
|
||||||
the WiFi network is congested or the device is not responding
|
the WiFi network is congested or the device is not responding
|
||||||
to discovery requests.
|
to discovery requests.
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ class IotBulb(IotDevice):
|
|||||||
>>> bulb.presets
|
>>> bulb.presets
|
||||||
[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)]
|
[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.LightPreset`
|
To modify an existing preset, pass :class:`~kasa.interfaces.light.LightPreset`
|
||||||
instance to :func:`save_preset` method:
|
instance to :func:`save_preset` method:
|
||||||
|
|
||||||
>>> preset = bulb.presets[0]
|
>>> preset = bulb.presets[0]
|
||||||
|
@ -41,7 +41,7 @@ class IotPlug(IotDevice):
|
|||||||
>>> plug.led
|
>>> plug.led
|
||||||
True
|
True
|
||||||
|
|
||||||
For more examples, see the :class:`SmartDevice` class.
|
For more examples, see the :class:`Device` class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -83,7 +83,7 @@ class IotStrip(IotDevice):
|
|||||||
>>> strip.is_on
|
>>> strip.is_on
|
||||||
True
|
True
|
||||||
|
|
||||||
For more examples, see the :class:`SmartDevice` class.
|
For more examples, see the :class:`Device` class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -175,7 +175,7 @@
|
|||||||
"longitude": 0,
|
"longitude": 0,
|
||||||
"mac": "5C-E9-31-00-00-00",
|
"mac": "5C-E9-31-00-00-00",
|
||||||
"model": "L530",
|
"model": "L530",
|
||||||
"nickname": "I01BU0tFRF9OQU1FIw==",
|
"nickname": "TGl2aW5nIFJvb20=",
|
||||||
"oem_id": "00000000000000000000000000000000",
|
"oem_id": "00000000000000000000000000000000",
|
||||||
"overheated": false,
|
"overheated": false,
|
||||||
"region": "Europe/Berlin",
|
"region": "Europe/Berlin",
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
import pytest
|
||||||
import xdoctest
|
import xdoctest
|
||||||
|
|
||||||
|
from kasa import Discover
|
||||||
from kasa.tests.conftest import get_device_for_fixture_protocol
|
from kasa.tests.conftest import get_device_for_fixture_protocol
|
||||||
|
|
||||||
|
|
||||||
@ -67,3 +69,61 @@ def test_discovery_examples(mocker):
|
|||||||
mocker.patch("kasa.discover.Discover.discover", return_value=[p])
|
mocker.patch("kasa.discover.Discover.discover", return_value=[p])
|
||||||
res = xdoctest.doctest_module("kasa.discover", "all")
|
res = xdoctest.doctest_module("kasa.discover", "all")
|
||||||
assert not res["failed"]
|
assert not res["failed"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_tutorial_examples(mocker, top_level_await):
|
||||||
|
"""Test discovery examples."""
|
||||||
|
a = asyncio.run(
|
||||||
|
get_device_for_fixture_protocol("L530E(EU)_3.0_1.1.6.json", "SMART")
|
||||||
|
)
|
||||||
|
b = asyncio.run(get_device_for_fixture_protocol("HS110(EU)_1.0_1.2.5.json", "IOT"))
|
||||||
|
a.host = "127.0.0.1"
|
||||||
|
b.host = "127.0.0.2"
|
||||||
|
|
||||||
|
# Note autospec does not work for staticmethods in python < 3.12
|
||||||
|
# https://github.com/python/cpython/issues/102978
|
||||||
|
mocker.patch(
|
||||||
|
"kasa.discover.Discover.discover_single", return_value=a, autospec=True
|
||||||
|
)
|
||||||
|
mocker.patch.object(Discover, "discover", return_value=[a, b], autospec=True)
|
||||||
|
res = xdoctest.doctest_module("docs/tutorial.py", "all")
|
||||||
|
assert not res["failed"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def top_level_await(mocker):
|
||||||
|
"""Fixture to enable top level awaits in doctests.
|
||||||
|
|
||||||
|
Uses the async exec feature of python to patch the builtins xdoctest uses.
|
||||||
|
See https://github.com/python/cpython/issues/78797
|
||||||
|
"""
|
||||||
|
import ast
|
||||||
|
from inspect import CO_COROUTINE
|
||||||
|
|
||||||
|
orig_exec = exec
|
||||||
|
orig_eval = eval
|
||||||
|
orig_compile = compile
|
||||||
|
|
||||||
|
def patch_exec(source, globals=None, locals=None, /, **kwargs):
|
||||||
|
if source.co_flags & CO_COROUTINE == CO_COROUTINE:
|
||||||
|
asyncio.run(orig_eval(source, globals, locals))
|
||||||
|
else:
|
||||||
|
orig_exec(source, globals, locals, **kwargs)
|
||||||
|
|
||||||
|
def patch_eval(source, globals=None, locals=None, /, **kwargs):
|
||||||
|
if source.co_flags & CO_COROUTINE == CO_COROUTINE:
|
||||||
|
return asyncio.run(orig_eval(source, globals, locals, **kwargs))
|
||||||
|
else:
|
||||||
|
return orig_eval(source, globals, locals, **kwargs)
|
||||||
|
|
||||||
|
def patch_compile(
|
||||||
|
source, filename, mode, flags=0, dont_inherit=False, optimize=-1, **kwargs
|
||||||
|
):
|
||||||
|
flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
|
||||||
|
return orig_compile(
|
||||||
|
source, filename, mode, flags, dont_inherit, optimize, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
mocker.patch("builtins.eval", side_effect=patch_eval)
|
||||||
|
mocker.patch("builtins.exec", side_effect=patch_exec)
|
||||||
|
mocker.patch("builtins.compile", side_effect=patch_compile)
|
||||||
|
Loading…
Reference in New Issue
Block a user