mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-11-25 02:38:30 +00:00
Refactor devices into subpackages and deprecate old names (#716)
* Refactor devices into subpackages and deprecate old names * Tweak and add tests * Fix linting * Remove duplicate implementations affecting project coverage * Update post review * Add device base class attributes and rename subclasses * Rename Module to BaseModule * Remove has_emeter_history * Fix missing _time in init * Update post review * Fix test_readmeexamples * Fix erroneously duped files * Clean up iot and smart imports * Update post latest review * Tweak Device docstring
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import importlib
|
||||
import inspect
|
||||
import pkgutil
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
@@ -17,20 +20,33 @@ from voluptuous import (
|
||||
)
|
||||
|
||||
import kasa
|
||||
from kasa import Credentials, DeviceConfig, SmartDevice, SmartDeviceException
|
||||
from kasa import Credentials, Device, DeviceConfig, SmartDeviceException
|
||||
from kasa.iot import IotDevice
|
||||
from kasa.smart import SmartChildDevice, SmartDevice
|
||||
|
||||
from .conftest import device_iot, handle_turn_on, has_emeter_iot, no_emeter_iot, turn_on
|
||||
from .fakeprotocol_iot import FakeIotProtocol
|
||||
|
||||
# List of all SmartXXX classes including the SmartDevice base class
|
||||
smart_device_classes = [
|
||||
dc
|
||||
for (mn, dc) in inspect.getmembers(
|
||||
kasa,
|
||||
lambda member: inspect.isclass(member)
|
||||
and (member == SmartDevice or issubclass(member, SmartDevice)),
|
||||
)
|
||||
]
|
||||
|
||||
def _get_subclasses(of_class):
|
||||
package = sys.modules["kasa"]
|
||||
subclasses = set()
|
||||
for _, modname, _ in pkgutil.iter_modules(package.__path__):
|
||||
importlib.import_module("." + modname, package="kasa")
|
||||
module = sys.modules["kasa." + modname]
|
||||
for name, obj in inspect.getmembers(module):
|
||||
if (
|
||||
inspect.isclass(obj)
|
||||
and issubclass(obj, of_class)
|
||||
and module.__package__ != "kasa"
|
||||
):
|
||||
subclasses.add((module.__package__ + "." + name, obj))
|
||||
return subclasses
|
||||
|
||||
|
||||
device_classes = pytest.mark.parametrize(
|
||||
"device_class_name_obj", _get_subclasses(Device), ids=lambda t: t[0]
|
||||
)
|
||||
|
||||
|
||||
@device_iot
|
||||
@@ -220,21 +236,26 @@ async def test_estimated_response_sizes(dev):
|
||||
assert mod.estimated_query_response_size > 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_class", smart_device_classes)
|
||||
def test_device_class_ctors(device_class):
|
||||
@device_classes
|
||||
async def test_device_class_ctors(device_class_name_obj):
|
||||
"""Make sure constructor api not broken for new and existing SmartDevices."""
|
||||
host = "127.0.0.2"
|
||||
port = 1234
|
||||
credentials = Credentials("foo", "bar")
|
||||
config = DeviceConfig(host, port_override=port, credentials=credentials)
|
||||
dev = device_class(host, config=config)
|
||||
klass = device_class_name_obj[1]
|
||||
if issubclass(klass, SmartChildDevice):
|
||||
parent = SmartDevice(host, config=config)
|
||||
dev = klass(parent, 1)
|
||||
else:
|
||||
dev = klass(host, config=config)
|
||||
assert dev.host == host
|
||||
assert dev.port == port
|
||||
assert dev.credentials == credentials
|
||||
|
||||
|
||||
@device_iot
|
||||
async def test_modules_preserved(dev: SmartDevice):
|
||||
async def test_modules_preserved(dev: IotDevice):
|
||||
"""Make modules that are not being updated are preserved between updates."""
|
||||
dev._last_update["some_module_not_being_updated"] = "should_be_kept"
|
||||
await dev.update()
|
||||
@@ -244,6 +265,8 @@ async def test_modules_preserved(dev: SmartDevice):
|
||||
async def test_create_smart_device_with_timeout():
|
||||
"""Make sure timeout is passed to the protocol."""
|
||||
host = "127.0.0.1"
|
||||
dev = IotDevice(host, config=DeviceConfig(host, timeout=100))
|
||||
assert dev.protocol._transport._timeout == 100
|
||||
dev = SmartDevice(host, config=DeviceConfig(host, timeout=100))
|
||||
assert dev.protocol._transport._timeout == 100
|
||||
|
||||
@@ -258,7 +281,7 @@ async def test_create_thin_wrapper():
|
||||
credentials=Credentials("username", "password"),
|
||||
)
|
||||
with patch("kasa.device_factory.connect", return_value=mock) as connect:
|
||||
dev = await SmartDevice.connect(config=config)
|
||||
dev = await Device.connect(config=config)
|
||||
assert dev is mock
|
||||
|
||||
connect.assert_called_once_with(
|
||||
@@ -268,7 +291,7 @@ async def test_create_thin_wrapper():
|
||||
|
||||
|
||||
@device_iot
|
||||
async def test_modules_not_supported(dev: SmartDevice):
|
||||
async def test_modules_not_supported(dev: IotDevice):
|
||||
"""Test that unsupported modules do not break the device."""
|
||||
for module in dev.modules.values():
|
||||
assert module.is_supported is not None
|
||||
@@ -277,6 +300,21 @@ async def test_modules_not_supported(dev: SmartDevice):
|
||||
assert module.is_supported is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_class, use_class", kasa.deprecated_smart_devices.items()
|
||||
)
|
||||
def test_deprecated_devices(device_class, use_class):
|
||||
package_name = ".".join(use_class.__module__.split(".")[:-1])
|
||||
msg = f"{device_class} is deprecated, use {use_class.__name__} from package {package_name} instead"
|
||||
with pytest.deprecated_call(match=msg):
|
||||
getattr(kasa, device_class)
|
||||
packages = package_name.split(".")
|
||||
module = __import__(packages[0])
|
||||
for _ in packages[1:]:
|
||||
module = importlib.import_module(package_name, package=module.__name__)
|
||||
getattr(module, use_class.__name__)
|
||||
|
||||
|
||||
def check_mac(x):
|
||||
if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", x.lower()):
|
||||
return x
|
||||
|
||||
Reference in New Issue
Block a user