diff --git a/pydeconz/models/alarm_system.py b/pydeconz/models/alarm_system.py index 0bef9748..dbeac471 100644 --- a/pydeconz/models/alarm_system.py +++ b/pydeconz/models/alarm_system.py @@ -5,7 +5,7 @@ from typing import Any, Literal, TypedDict from . import ResourceGroup -from .api import APIItem +from .api import ApiData, APIItem LOGGER = logging.getLogger(__name__) @@ -106,7 +106,7 @@ class TypedAlarmSystemDevices(TypedDict): trigger: str -class TypedAlarmSystem(TypedDict): +class TypedAlarmSystem(ApiData): """Alarm system type definition.""" name: str @@ -115,7 +115,7 @@ class TypedAlarmSystem(TypedDict): devices: dict[str, TypedAlarmSystemDevices] -class AlarmSystem(APIItem): +class AlarmSystem(APIItem[TypedAlarmSystem]): """deCONZ alarm system representation. Dresden Elektroniks documentation of alarm systems in deCONZ diff --git a/pydeconz/models/api.py b/pydeconz/models/api.py index 704067c5..7b5383e2 100644 --- a/pydeconz/models/api.py +++ b/pydeconz/models/api.py @@ -4,7 +4,7 @@ from collections.abc import Callable import logging -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar if TYPE_CHECKING: from . import ResourceGroup @@ -15,12 +15,19 @@ UnsubscribeType = Callable[[], None] -class APIItem: +class ApiData(TypedDict): + """API data class.""" + + +ApiDataT = TypeVar("ApiDataT", bound=ApiData) + + +class APIItem(Generic[ApiDataT]): """Base class for a deCONZ API item.""" resource_group: ResourceGroup - def __init__(self, resource_id: str, raw: Any) -> None: + def __init__(self, resource_id: str, raw: ApiDataT) -> None: """Initialize API item.""" self.resource_id = resource_id self.raw = raw @@ -57,7 +64,8 @@ def unsubscribe() -> None: return unsubscribe - def update(self, raw: dict[str, dict[str, Any]]) -> None: + def update(self, raw: ApiDataT) -> None: + # def update(self, raw: dict[str, dict[str, Any]]) -> None: """Update input attr in self. Store a set of keys with changed values. diff --git a/pydeconz/models/deconz_device.py b/pydeconz/models/deconz_device.py index 26f54008..09a0e19a 100644 --- a/pydeconz/models/deconz_device.py +++ b/pydeconz/models/deconz_device.py @@ -1,53 +1,81 @@ """Python library to connect deCONZ and Home Assistant to work together.""" -from .api import APIItem +from typing import NotRequired, TypeVar +from .api import ApiData, APIItem -class DeconzDevice(APIItem): + +class TypedData(ApiData): + """Scene type definition.""" + + etag: NotRequired[str] + manufacturername: NotRequired[str] + modelid: NotRequired[str] + name: NotRequired[str] + swversion: NotRequired[str] + type: NotRequired[str] + uniqueid: NotRequired[str] + + +ApiDataT = TypeVar("ApiDataT", bound=TypedData) + + +class DeconzDevice(APIItem[ApiDataT]): + # class DeconzDevice(APIItem[ApiDataT], Generic[ApiDataT]): """deCONZ resource base representation. Dresden Elektroniks REST API documentation http://dresden-elektronik.github.io/deconz-rest-doc/ """ + raw: ApiDataT + @property def etag(self) -> str: """HTTP etag change on any action to the device.""" - raw: dict[str, str] = self.raw - return raw.get("etag") or "" + # raw: dict[str, str] = self.raw + if "etag" in self.raw: + return self.raw["etag"] + return "" @property def manufacturer(self) -> str: """Device manufacturer.""" - raw: dict[str, str] = self.raw - return raw.get("manufacturername") or "" + if "manufacturername" in self.raw: + return self.raw["manufacturername"] + return "" @property def model_id(self) -> str: """Device model.""" - raw: dict[str, str] = self.raw - return raw.get("modelid") or "" + if "modelid" in self.raw: + return self.raw["modelid"] + return "" @property def name(self) -> str: """Name of the device.""" - raw: dict[str, str] = self.raw - return raw.get("name") or "" + if "name" in self.raw: + return self.raw["name"] + return "" @property def software_version(self) -> str: """Firmware version.""" - raw: dict[str, str] = self.raw - return raw.get("swversion") or "" + if "swversion" in self.raw: + return self.raw["swversion"] + return "" @property def type(self) -> str: """Human readable type of the device.""" - raw: dict[str, str] = self.raw - return raw.get("type") or "" + if "type" in self.raw: + return self.raw["type"] + return "" @property def unique_id(self) -> str: """Id for unique device identification.""" - raw: dict[str, str] = self.raw - return raw.get("uniqueid") or "" + if "uniqueid" in self.raw: + return self.raw["uniqueid"] + return "" diff --git a/pydeconz/models/group.py b/pydeconz/models/group.py index 954a5fb9..20ad16c2 100644 --- a/pydeconz/models/group.py +++ b/pydeconz/models/group.py @@ -3,7 +3,7 @@ from typing import Final, Literal, NotRequired, TypedDict from . import ResourceGroup -from .deconz_device import DeconzDevice +from .deconz_device import DeconzDevice, TypedData from .light.light import LightColorMode, LightEffect from .scene import TypedScene @@ -21,14 +21,25 @@ class TypedGroupAction(TypedDict): """Group action type definition.""" - bri: int - colormode: NotRequired[Literal["ct", "hs", "xy"]] - ct: int - effect: NotRequired[Literal["colorloop", "none"]] - hue: int + bri: NotRequired[int] + colormode: NotRequired[ + Literal[ + LightColorMode.CT, + LightColorMode.HS, + LightColorMode.XY, + ] + ] + ct: NotRequired[int] + effect: NotRequired[ + Literal[ + LightEffect.COLOR_LOOP, + LightEffect.NONE, + ] + ] + hue: NotRequired[int] on: bool - sat: int - xy: tuple[float, float] + sat: NotRequired[int] + xy: tuple[float, float] | tuple[None, None] class TypedGroupState(TypedDict): @@ -38,7 +49,7 @@ class TypedGroupState(TypedDict): any_on: bool -class TypedGroup(TypedDict): +class TypedGroup(TypedData): """Group type definition.""" action: TypedGroupAction @@ -48,13 +59,11 @@ class TypedGroup(TypedDict): lights: list[str] lightsequence: list[str] multideviceids: list[str] - name: str scenes: list[TypedScene] state: TypedGroupState - type: str -class Group(DeconzDevice): +class Group(DeconzDevice[TypedGroup]): """deCONZ light group representation. Dresden Elektroniks documentation of light groups in deCONZ diff --git a/pydeconz/models/scene.py b/pydeconz/models/scene.py index f3e6f941..d230aae7 100644 --- a/pydeconz/models/scene.py +++ b/pydeconz/models/scene.py @@ -1,12 +1,10 @@ """Python library to connect deCONZ and Home Assistant to work together.""" -from typing import TypedDict - from . import ResourceGroup -from .api import APIItem +from .api import ApiData, APIItem -class TypedScene(TypedDict): +class TypedScene(ApiData): """Scene type definition.""" id: str @@ -15,7 +13,7 @@ class TypedScene(TypedDict): name: str -class Scene(APIItem): +class Scene(APIItem[ApiData]): """deCONZ scene representation. Dresden Elektroniks documentation of scenes in deCONZ