From c3329155c8ae98c274da62568357edb5929c8968 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:51:50 +0000 Subject: [PATCH] Raise SmartDeviceException on invalid config dicts (#640) Co-authored-by: J. Nick Koston --- kasa/deviceconfig.py | 19 +++++++++++++------ kasa/tests/test_deviceconfig.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/kasa/deviceconfig.py b/kasa/deviceconfig.py index dd356032..58d33661 100644 --- a/kasa/deviceconfig.py +++ b/kasa/deviceconfig.py @@ -37,11 +37,16 @@ def _dataclass_from_dict(klass, in_val): fieldtypes = {f.name: f.type for f in fields(klass)} val = {} for dict_key in in_val: - if dict_key in fieldtypes and hasattr(fieldtypes[dict_key], "from_dict"): - val[dict_key] = fieldtypes[dict_key].from_dict(in_val[dict_key]) + if dict_key in fieldtypes: + if hasattr(fieldtypes[dict_key], "from_dict"): + val[dict_key] = fieldtypes[dict_key].from_dict(in_val[dict_key]) + else: + val[dict_key] = _dataclass_from_dict( + fieldtypes[dict_key], in_val[dict_key] + ) else: - val[dict_key] = _dataclass_from_dict( - fieldtypes[dict_key], in_val[dict_key] + raise SmartDeviceException( + f"Cannot create dataclass from dict, unknown key: {dict_key}" ) return klass(**val) else: @@ -173,6 +178,8 @@ class DeviceConfig: return _dataclass_to_dict(self) @staticmethod - def from_dict(cparam_dict: Dict[str, Dict[str, str]]) -> "DeviceConfig": + def from_dict(config_dict: Dict[str, Dict[str, str]]) -> "DeviceConfig": """Return device config from dict.""" - return _dataclass_from_dict(DeviceConfig, cparam_dict) + if isinstance(config_dict, dict): + return _dataclass_from_dict(DeviceConfig, config_dict) + raise SmartDeviceException(f"Invalid device config data: {config_dict}") diff --git a/kasa/tests/test_deviceconfig.py b/kasa/tests/test_deviceconfig.py index 342de919..b802d2aa 100644 --- a/kasa/tests/test_deviceconfig.py +++ b/kasa/tests/test_deviceconfig.py @@ -2,6 +2,7 @@ from json import dumps as json_dumps from json import loads as json_loads import aiohttp +import pytest from kasa.credentials import Credentials from kasa.deviceconfig import ( @@ -10,6 +11,7 @@ from kasa.deviceconfig import ( DeviceFamilyType, EncryptType, ) +from kasa.exceptions import SmartDeviceException async def test_serialization(): @@ -21,6 +23,19 @@ async def test_serialization(): assert config == config2 +@pytest.mark.parametrize( + ("input_value", "expected_msg"), + [ + ({"Foo": "Bar"}, "Cannot create dataclass from dict, unknown key: Foo"), + ("foobar", "Invalid device config data: foobar"), + ], + ids=["invalid-dict", "not-dict"], +) +def test_deserialization_errors(input_value, expected_msg): + with pytest.raises(SmartDeviceException, match=expected_msg): + DeviceConfig.from_dict(input_value) + + async def test_credentials_hash(): config = DeviceConfig( host="Foo",