From ec20acfc73af3ccf5dfb0ddb8d1cc06a97f0db07 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 15 Jan 2023 21:38:50 +0100 Subject: [PATCH] Fix incorrect super().__getattr__() use on devicestatus --- miio/click_common.py | 1 + miio/devicestatus.py | 13 +++++++------ miio/tests/test_devicestatus.py | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/miio/click_common.py b/miio/click_common.py index 2e612b866..ff5c27f69 100644 --- a/miio/click_common.py +++ b/miio/click_common.py @@ -329,6 +329,7 @@ def wrap(*args, **kwargs): echo(json.dumps(ex.args[0], indent=indent)) return + # TODO: __json__ is not used anywhere and could be removed get_json_data_func = getattr(result, "__json__", None) data_variable = getattr(result, "data", None) if get_json_data_func is not None: diff --git a/miio/devicestatus.py b/miio/devicestatus.py index f5a7def82..ae79dcb53 100644 --- a/miio/devicestatus.py +++ b/miio/devicestatus.py @@ -13,6 +13,8 @@ get_type_hints, ) +import attr + from .descriptors import ( ActionDescriptor, BooleanSettingDescriptor, @@ -115,7 +117,6 @@ def embed(self, other: "DeviceStatus"): for name, sensor in other.sensors().items(): final_name = f"{other_name}__{name}" - import attr self._sensors[final_name] = attr.evolve(sensor, property=final_name) @@ -125,13 +126,13 @@ def embed(self, other: "DeviceStatus"): def __getattr__(self, item): """Overridden to lookup properties from embedded containers.""" - if "__" not in item: - return super().__getattr__(item) + if item.startswith("__") and item.endswith("__"): + return super().__getattribute__(item) - if item == "__json__": # special handling for custom json dunder - return None + embed, prop = item.split("__", maxsplit=1) + if not embed or not prop: + return super().__getattribute__(item) - embed, prop = item.split("__") return getattr(self._embedded[embed], prop) diff --git a/miio/tests/test_devicestatus.py b/miio/tests/test_devicestatus.py index 4f6baaa45..b0b9c0bb0 100644 --- a/miio/tests/test_devicestatus.py +++ b/miio/tests/test_devicestatus.py @@ -76,6 +76,24 @@ def return_none(self): assert repr(NoneStatus()) == "" +def test_get_attribute(mocker): + """Make sure that __get_attribute__ works as expected.""" + + class TestStatus(DeviceStatus): + @property + def existing_attribute(self): + return None + + status = TestStatus() + with pytest.raises(AttributeError): + _ = status.__missing_attribute + + with pytest.raises(AttributeError): + _ = status.__missing_dunder__ + + assert status.existing_attribute is None + + def test_sensor_decorator(): class DecoratedProps(DeviceStatus): @property