Make device initialisation easier by reducing required imports (#936)

Adds username and password arguments to discovery to remove the need to import Credentials.
Creates TypeAliases in Device for connection configuration classes and DeviceType.
Using the API with these changes will only require importing either Discover or Device
depending on whether using Discover.discover() or Device.connect() to 
initialize and interact with the API.
This commit is contained in:
Steven B
2024-06-03 21:06:54 +03:00
committed by GitHub
parent bfba7a347f
commit be5202ccb7
12 changed files with 263 additions and 90 deletions

View File

@@ -116,13 +116,11 @@ def test_deprecated_devices(device_class, use_class):
getattr(module, use_class.__name__)
@pytest.mark.parametrize(
"exceptions_class, use_class", kasa.deprecated_exceptions.items()
)
def test_deprecated_exceptions(exceptions_class, use_class):
msg = f"{exceptions_class} is deprecated, use {use_class.__name__} instead"
@pytest.mark.parametrize("deprecated_class, use_class", kasa.deprecated_classes.items())
def test_deprecated_classes(deprecated_class, use_class):
msg = f"{deprecated_class} is deprecated, use {use_class.__name__} instead"
with pytest.deprecated_call(match=msg):
getattr(kasa, exceptions_class)
getattr(kasa, deprecated_class)
getattr(kasa, use_class.__name__)
@@ -266,3 +264,27 @@ async def test_deprecated_light_preset_attributes(dev: Device):
IotLightPreset(index=0, hue=100, brightness=100, saturation=0, color_temp=0), # type: ignore[call-arg]
will_raise=exc,
)
async def test_device_type_aliases():
"""Test that the device type aliases in Device work."""
def _mock_connect(config, *args, **kwargs):
mock = Mock()
mock.config = config
return mock
with patch("kasa.device_factory.connect", side_effect=_mock_connect):
dev = await Device.connect(
config=Device.Config(
host="127.0.0.1",
credentials=Device.Credentials(username="user", password="foobar"), # noqa: S106
connection_type=Device.ConnectionParameters(
device_family=Device.Family.SmartKasaPlug,
encryption_type=Device.EncryptionType.Klap,
login_version=2,
),
)
)
assert isinstance(dev.config, DeviceConfig)
assert DeviceType.Dimmer == Device.Type.Dimmer

View File

@@ -17,10 +17,10 @@ from kasa.device_factory import (
get_protocol,
)
from kasa.deviceconfig import (
ConnectionType,
DeviceConfig,
DeviceFamilyType,
EncryptType,
DeviceConnectionParameters,
DeviceEncryptionType,
DeviceFamily,
)
from kasa.discover import DiscoveryResult
from kasa.smart.smartdevice import SmartDevice
@@ -31,12 +31,12 @@ def _get_connection_type_device_class(discovery_info):
device_class = Discover._get_device_class(discovery_info)
dr = DiscoveryResult(**discovery_info["result"])
connection_type = ConnectionType.from_values(
connection_type = DeviceConnectionParameters.from_values(
dr.device_type, dr.mgt_encrypt_schm.encrypt_type
)
else:
connection_type = ConnectionType.from_values(
DeviceFamilyType.IotSmartPlugSwitch.value, EncryptType.Xor.value
connection_type = DeviceConnectionParameters.from_values(
DeviceFamily.IotSmartPlugSwitch.value, DeviceEncryptionType.Xor.value
)
device_class = Discover._get_device_class(discovery_info)
@@ -137,7 +137,7 @@ async def test_connect_http_client(discovery_data, mocker):
host=host, credentials=Credentials("foor", "bar"), connection_type=ctype
)
dev = await connect(config=config)
if ctype.encryption_type != EncryptType.Xor:
if ctype.encryption_type != DeviceEncryptionType.Xor:
assert dev.protocol._transport._http_client.client != http_client
await dev.disconnect()
@@ -148,7 +148,7 @@ async def test_connect_http_client(discovery_data, mocker):
http_client=http_client,
)
dev = await connect(config=config)
if ctype.encryption_type != EncryptType.Xor:
if ctype.encryption_type != DeviceEncryptionType.Xor:
assert dev.protocol._transport._http_client.client == http_client
await dev.disconnect()
await http_client.close()

View File

@@ -1,4 +1,6 @@
# type: ignore
# ruff: noqa: S106
import asyncio
import re
import socket
@@ -16,8 +18,8 @@ from kasa import (
KasaException,
)
from kasa.deviceconfig import (
ConnectionType,
DeviceConfig,
DeviceConnectionParameters,
)
from kasa.discover import DiscoveryResult, _DiscoverProtocol, json_dumps
from kasa.exceptions import AuthenticationError, UnsupportedDeviceError
@@ -128,7 +130,7 @@ async def test_discover_single(discovery_mock, custom_port, mocker):
if discovery_mock.default_port == 80:
assert x.alias is None
ct = ConnectionType.from_values(
ct = DeviceConnectionParameters.from_values(
discovery_mock.device_type,
discovery_mock.encrypt_type,
discovery_mock.login_version,
@@ -164,6 +166,60 @@ async def test_discover_single_hostname(discovery_mock, mocker):
x = await Discover.discover_single(host, credentials=Credentials())
async def test_discover_credentials(mocker):
"""Make sure that discover gives credentials precedence over un and pw."""
host = "127.0.0.1"
mocker.patch("kasa.discover._DiscoverProtocol.wait_for_discovery_to_complete")
def mock_discover(self, *_, **__):
self.discovered_devices = {host: MagicMock()}
mocker.patch.object(_DiscoverProtocol, "do_discover", mock_discover)
dp = mocker.spy(_DiscoverProtocol, "__init__")
# Only credentials passed
await Discover.discover(credentials=Credentials(), timeout=0)
assert dp.mock_calls[0].kwargs["credentials"] == Credentials()
# Credentials and un/pw passed
await Discover.discover(
credentials=Credentials(), username="Foo", password="Bar", timeout=0
)
assert dp.mock_calls[1].kwargs["credentials"] == Credentials()
# Only un/pw passed
await Discover.discover(username="Foo", password="Bar", timeout=0)
assert dp.mock_calls[2].kwargs["credentials"] == Credentials("Foo", "Bar")
# Only un passed, credentials should be None
await Discover.discover(username="Foo", timeout=0)
assert dp.mock_calls[3].kwargs["credentials"] is None
async def test_discover_single_credentials(mocker):
"""Make sure that discover_single gives credentials precedence over un and pw."""
host = "127.0.0.1"
mocker.patch("kasa.discover._DiscoverProtocol.wait_for_discovery_to_complete")
def mock_discover(self, *_, **__):
self.discovered_devices = {host: MagicMock()}
mocker.patch.object(_DiscoverProtocol, "do_discover", mock_discover)
dp = mocker.spy(_DiscoverProtocol, "__init__")
# Only credentials passed
await Discover.discover_single(host, credentials=Credentials(), timeout=0)
assert dp.mock_calls[0].kwargs["credentials"] == Credentials()
# Credentials and un/pw passed
await Discover.discover_single(
host, credentials=Credentials(), username="Foo", password="Bar", timeout=0
)
assert dp.mock_calls[1].kwargs["credentials"] == Credentials()
# Only un/pw passed
await Discover.discover_single(host, username="Foo", password="Bar", timeout=0)
assert dp.mock_calls[2].kwargs["credentials"] == Credentials("Foo", "Bar")
# Only un passed, credentials should be None
await Discover.discover_single(host, username="Foo", timeout=0)
assert dp.mock_calls[3].kwargs["credentials"] is None
async def test_discover_single_unsupported(unsupported_device_info, mocker):
"""Make sure that discover_single handles unsupported devices correctly."""
host = "127.0.0.1"