Add generic interface for accessing device features (#741)

This adds a generic interface for all device classes to introspect available device features,
that is necessary to make it easier to support a wide variety of supported devices with different set of features.
This will allow constructing generic interfaces (e.g., in homeassistant) that fetch and change these features without hard-coding the API calls.

`Device.features()` now returns a mapping of `<identifier, Feature>` where the `Feature` contains all necessary information (like the name, the icon, a way to get and change the setting) to present and change the defined feature through its interface.
This commit is contained in:
Teemu R
2024-02-15 16:25:08 +01:00
committed by GitHub
parent 57835276e3
commit 64da736717
12 changed files with 345 additions and 28 deletions

View File

@@ -37,6 +37,11 @@ async def test_update_called_by_cli(dev, mocker):
"""Test that device update is called on main."""
runner = CliRunner()
update = mocker.patch.object(dev, "update")
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
mocker.patch("kasa.discover.Discover.discover_single", return_value=dev)
res = await runner.invoke(
@@ -49,6 +54,7 @@ async def test_update_called_by_cli(dev, mocker):
"--password",
"bar",
],
catch_exceptions=False,
)
assert res.exit_code == 0
update.assert_called()
@@ -292,6 +298,10 @@ async def test_brightness(dev):
async def test_json_output(dev: Device, mocker):
"""Test that the json output produces correct output."""
mocker.patch("kasa.Discover.discover", return_value={"127.0.0.1": dev})
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
runner = CliRunner()
res = await runner.invoke(cli, ["--json", "state"], obj=dev)
assert res.exit_code == 0
@@ -345,6 +355,10 @@ async def test_without_device_type(dev, mocker):
discovery_mock = mocker.patch(
"kasa.discover.Discover.discover_single", return_value=dev
)
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
res = await runner.invoke(
cli,
[
@@ -410,6 +424,10 @@ async def test_duplicate_target_device():
async def test_discover(discovery_mock, mocker):
"""Test discovery output."""
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
runner = CliRunner()
res = await runner.invoke(
cli,
@@ -429,6 +447,10 @@ async def test_discover(discovery_mock, mocker):
async def test_discover_host(discovery_mock, mocker):
"""Test discovery output."""
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
runner = CliRunner()
res = await runner.invoke(
cli,