From b5bfe7017848de66477bc7422a968794ab77e901 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 17:55:52 +0000 Subject: [PATCH 1/8] Implement pydantic for sockets --- pytradfri/device/__init__.py | 3 ++- pytradfri/device/socket.py | 28 +++++++++++++++++++++------- pytradfri/device/socket_control.py | 14 ++++++-------- tests/test_device.py | 12 ++++++++---- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/pytradfri/device/__init__.py b/pytradfri/device/__init__.py index 6f749a0d..04862e8f 100644 --- a/pytradfri/device/__init__.py +++ b/pytradfri/device/__init__.py @@ -29,6 +29,7 @@ from .blind_control import BlindControl, BlindResponse from .light_control import LightControl from .signal_repeater_control import SignalRepeaterControl +from .socket import SocketResponse from .socket_control import SocketControl @@ -60,7 +61,7 @@ class DeviceResponse(ApiResourceResponse): signal_repeater_control: Optional[List[Dict[str, Any]]] = Field( alias=ROOT_SIGNAL_REPEATER ) - socket_control: Optional[List[Dict[str, Any]]] = Field(alias=ATTR_SWITCH_PLUG) + socket_control: Optional[List[SocketResponse]] = Field(alias=ATTR_SWITCH_PLUG) class Device(ApiResource): diff --git a/pytradfri/device/socket.py b/pytradfri/device/socket.py index 90cef6dd..e2ead704 100644 --- a/pytradfri/device/socket.py +++ b/pytradfri/device/socket.py @@ -1,32 +1,46 @@ """Represent a socket.""" from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING -from ..const import ATTR_DEVICE_STATE +from pydantic import BaseModel, Field + +from ..const import ATTR_DEVICE_STATE, ATTR_ID + + +class SocketResponse(BaseModel): + """Represent API response for a blind.""" + + id: int = Field(alias=ATTR_ID) + state: int = Field(alias=ATTR_DEVICE_STATE) + + +if TYPE_CHECKING: + # avoid cyclic import at runtime. + from . import Device class Socket: """Represent a socket.""" - def __init__(self, device, index): + def __init__(self, device: Device, index: int) -> None: """Create object of class.""" self.device = device self.index = index @property - def state(self): + def state(self) -> bool: """State.""" - return self.raw.get(ATTR_DEVICE_STATE) == 1 + return self.raw.state == 1 @property - def raw(self) -> dict[str, Any]: + def raw(self) -> SocketResponse: """Return raw data that it represents.""" socket_control_response = self.device.raw.socket_control assert socket_control_response is not None return socket_control_response[self.index] - def __repr__(self): + def __repr__(self) -> str: """Return representation of class object.""" state = "on" if self.state else "off" return f"" diff --git a/pytradfri/device/socket_control.py b/pytradfri/device/socket_control.py index 8ef59d85..f210a968 100644 --- a/pytradfri/device/socket_control.py +++ b/pytradfri/device/socket_control.py @@ -1,34 +1,32 @@ """Class to control the sockets.""" from __future__ import annotations -from typing import Any - from ..command import Command from ..const import ATTR_DEVICE_STATE, ATTR_SWITCH_PLUG from .base_controller import BaseController -from .socket import Socket +from .socket import Socket, SocketResponse class SocketControl(BaseController): """Class to control the sockets.""" @property - def raw(self) -> list[dict[str, Any]]: + def raw(self) -> list[SocketResponse]: """Return raw data that it represents.""" socket_control_response = self._device.raw.socket_control assert socket_control_response is not None return socket_control_response @property - def sockets(self): + def sockets(self) -> list[Socket]: """Return socket objects of the socket control.""" return [Socket(self._device, i) for i in range(len(self.raw))] - def set_state(self, state, *, index=0): + def set_state(self, state: int, *, index=0) -> Command: """Set state of a socket.""" - return self.set_values({ATTR_DEVICE_STATE: int(state)}, index=index) + return self.set_values({ATTR_DEVICE_STATE: state}, index=index) - def set_values(self, values, *, index=0): + def set_values(self, values, *, index=0) -> Command: """Set values on socket control. Returns a Command. diff --git a/tests/test_device.py b/tests/test_device.py index f42c1b85..f0d887ba 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -336,15 +336,19 @@ def test_socket_value_setting(function_name, comment, test_input, expected_resul def test_socket_state_off(): """Test socket off.""" - socket = Device(deepcopy(OUTLET)).socket_control.sockets[0] - socket.raw[ATTR_DEVICE_STATE] = 0 + socket_response = deepcopy(OUTLET) + socket_response["3312"][0][ATTR_DEVICE_STATE] = 0 + + socket = Device(socket_response).socket_control.sockets[0] assert socket.state is False def test_socket_state_on(): """Test socket on.""" - socket = Device(deepcopy(OUTLET)).socket_control.sockets[0] - socket.raw[ATTR_DEVICE_STATE] = 1 + socket_response = deepcopy(OUTLET) + socket_response["3312"][0][ATTR_DEVICE_STATE] = 1 + + socket = Device(socket_response).socket_control.sockets[0] assert socket.state is True From dbb92c812ce164d57c831aca82a75fd59dbf4824 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 17:58:17 +0000 Subject: [PATCH 2/8] Add type hint --- pytradfri/device/socket_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytradfri/device/socket_control.py b/pytradfri/device/socket_control.py index f210a968..0cf8b285 100644 --- a/pytradfri/device/socket_control.py +++ b/pytradfri/device/socket_control.py @@ -26,7 +26,7 @@ def set_state(self, state: int, *, index=0) -> Command: """Set state of a socket.""" return self.set_values({ATTR_DEVICE_STATE: state}, index=index) - def set_values(self, values, *, index=0) -> Command: + def set_values(self, values: dict[str, int], *, index) -> Command: """Set values on socket control. Returns a Command. From 8b9fae95c0927c12a0e3791e556a3bde46497c83 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 17:58:55 +0000 Subject: [PATCH 3/8] Use constant --- tests/test_device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_device.py b/tests/test_device.py index f0d887ba..5af3d413 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -337,7 +337,7 @@ def test_socket_value_setting(function_name, comment, test_input, expected_resul def test_socket_state_off(): """Test socket off.""" socket_response = deepcopy(OUTLET) - socket_response["3312"][0][ATTR_DEVICE_STATE] = 0 + socket_response[ATTR_LIGHT_CONTROL][0][ATTR_DEVICE_STATE] = 0 socket = Device(socket_response).socket_control.sockets[0] assert socket.state is False @@ -346,7 +346,7 @@ def test_socket_state_off(): def test_socket_state_on(): """Test socket on.""" socket_response = deepcopy(OUTLET) - socket_response["3312"][0][ATTR_DEVICE_STATE] = 1 + socket_response[ATTR_LIGHT_CONTROL][0][ATTR_DEVICE_STATE] = 1 socket = Device(socket_response).socket_control.sockets[0] assert socket.state is True From 42ea041dde759ab413e58a73db9e46a4cbcad8f8 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 18:03:09 +0000 Subject: [PATCH 4/8] Incorrect constant --- tests/test_device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_device.py b/tests/test_device.py index 5af3d413..5693994c 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -337,7 +337,7 @@ def test_socket_value_setting(function_name, comment, test_input, expected_resul def test_socket_state_off(): """Test socket off.""" socket_response = deepcopy(OUTLET) - socket_response[ATTR_LIGHT_CONTROL][0][ATTR_DEVICE_STATE] = 0 + socket_response[ATTR_SWITCH_PLUG][0][ATTR_DEVICE_STATE] = 0 socket = Device(socket_response).socket_control.sockets[0] assert socket.state is False @@ -346,7 +346,7 @@ def test_socket_state_off(): def test_socket_state_on(): """Test socket on.""" socket_response = deepcopy(OUTLET) - socket_response[ATTR_LIGHT_CONTROL][0][ATTR_DEVICE_STATE] = 1 + socket_response[ATTR_SWITCH_PLUG][0][ATTR_DEVICE_STATE] = 1 socket = Device(socket_response).socket_control.sockets[0] assert socket.state is True From ca9fc07ea0c3d9875bb1a79ecc181f1aeebd3d56 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 19:36:02 +0100 Subject: [PATCH 5/8] Update pytradfri/device/socket_control.py Co-authored-by: Martin Hjelmare --- pytradfri/device/socket_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytradfri/device/socket_control.py b/pytradfri/device/socket_control.py index 0cf8b285..22a8ac1a 100644 --- a/pytradfri/device/socket_control.py +++ b/pytradfri/device/socket_control.py @@ -22,7 +22,7 @@ def sockets(self) -> list[Socket]: """Return socket objects of the socket control.""" return [Socket(self._device, i) for i in range(len(self.raw))] - def set_state(self, state: int, *, index=0) -> Command: + def set_state(self, state: int, *, index: int = 0) -> Command: """Set state of a socket.""" return self.set_values({ATTR_DEVICE_STATE: state}, index=index) From 57c5c4306add170ad8f028255a4e8843a6783a70 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 18:37:40 +0000 Subject: [PATCH 6/8] Put back unintended delete --- pytradfri/device/socket_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytradfri/device/socket_control.py b/pytradfri/device/socket_control.py index 22a8ac1a..6296b5ee 100644 --- a/pytradfri/device/socket_control.py +++ b/pytradfri/device/socket_control.py @@ -26,7 +26,7 @@ def set_state(self, state: int, *, index: int = 0) -> Command: """Set state of a socket.""" return self.set_values({ATTR_DEVICE_STATE: state}, index=index) - def set_values(self, values: dict[str, int], *, index) -> Command: + def set_values(self, values: dict[str, int], *, index: int = 0) -> Command: """Set values on socket control. Returns a Command. From 9946848c3f2bffb366c7061b84b97a8e2f931dd5 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 18:40:23 +0000 Subject: [PATCH 7/8] Add files to strict typing --- mypy.ini | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/mypy.ini b/mypy.ini index 1888ba64..8d40bd99 100644 --- a/mypy.ini +++ b/mypy.ini @@ -146,3 +146,47 @@ disallow_untyped_defs = true no_implicit_optional = true warn_return_any = true warn_unreachable = true + +[mypy-pytradfri.device.socket] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-pytradfri.device.socket_control] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-pytradfri.device.air_purifier] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + +[mypy-pytradfri.device.air_purifier_control] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true From 23ccaa288c71e4e4c550ae70d79ddd7993e9d7e7 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 26 Feb 2022 18:43:24 +0000 Subject: [PATCH 8/8] Add missing type hints --- pytradfri/device/air_purifier_control.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pytradfri/device/air_purifier_control.py b/pytradfri/device/air_purifier_control.py index a0b2037d..cc7f95ad 100644 --- a/pytradfri/device/air_purifier_control.py +++ b/pytradfri/device/air_purifier_control.py @@ -29,36 +29,36 @@ def air_purifiers(self) -> list[AirPurifier]: """Return air purifier objects of the air purifier control.""" return [AirPurifier(self._device, i) for i in range(len(self.raw))] - def turn_off(self, *, index=0) -> Command: + def turn_off(self, *, index: int = 0) -> Command: """Turn the device off.""" return self.set_value({ATTR_AIR_PURIFIER_MODE: 0}, index=index) - def turn_on_auto_mode(self, *, index=0) -> Command: + def turn_on_auto_mode(self, *, index: int = 0) -> Command: """Turn on auto mode.""" return self.set_value( {ATTR_AIR_PURIFIER_MODE: ATTR_AIR_PURIFIER_MODE_AUTO}, index=index ) - def set_fan_speed(self, mode: int, *, index=0) -> Command: + def set_fan_speed(self, mode: int, *, index: int = 0) -> Command: """Set the fan speed of the purifier.""" self._value_validate(mode, RANGE_AIR_PURIFIER, "Air Purifier mode") return self.set_value({ATTR_AIR_PURIFIER_MODE: mode}, index=index) - def set_controls_locked(self, locked: bool, *, index=0) -> Command: + def set_controls_locked(self, locked: bool, *, index: int = 0) -> Command: """Set physical controls locked of the air purifier.""" return self.set_value( {ATTR_AIR_PURIFIER_CONTROLS_LOCKED: 1 if locked else 0}, index=index ) - def set_leds_off(self, leds_off: bool, *, index=0) -> Command: + def set_leds_off(self, leds_off: bool, *, index: int = 0) -> Command: """Set led's off/on of the air purifier.""" return self.set_value( {ATTR_AIR_PURIFIER_LEDS_OFF: 1 if leds_off else 0}, index=index ) - def set_value(self, value: dict[str, bool | int], *, index=0) -> Command: + def set_value(self, value: dict[str, bool | int], *, index: int = 0) -> Command: """Set values on air purifier control. Returns a Command.