-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement pydantic for lights #436
Changes from 14 commits
cba6b82
b158757
661bddc
631b0e1
702b58a
f9d83c6
45dcbc4
7360f38
d79398c
bf33c7e
dc1c8b8
520ce9c
a3c8976
f1c13d9
dff1c37
5d45d1d
7134097
58a16bf
8400c55
fdaa413
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
"""Represent a light.""" | ||
from __future__ import annotations | ||
|
||
from typing import Any | ||
from typing import TYPE_CHECKING, Optional | ||
|
||
from pydantic import BaseModel, Field | ||
|
||
from ..color import supported_features | ||
from ..const import ( | ||
ATTR_DEVICE_STATE, | ||
ATTR_ID, | ||
ATTR_LIGHT_COLOR_HEX, | ||
ATTR_LIGHT_COLOR_HUE, | ||
ATTR_LIGHT_COLOR_SATURATION, | ||
|
@@ -19,6 +22,24 @@ | |
SUPPORT_XY_COLOR, | ||
) | ||
|
||
if TYPE_CHECKING: | ||
# avoid cyclic import at runtime. | ||
from . import Device | ||
|
||
|
||
class LightResponse(BaseModel): | ||
"""Represent API response for a blind.""" | ||
|
||
color_mireds: Optional[int] = Field(alias=ATTR_LIGHT_MIREDS) | ||
color_hex: Optional[str] = Field(alias=ATTR_LIGHT_COLOR_HEX) | ||
color_xy_x: Optional[int] = Field(alias=ATTR_LIGHT_COLOR_X) | ||
color_xy_y: Optional[int] = Field(alias=ATTR_LIGHT_COLOR_Y) | ||
color_hue: Optional[int] = Field(alias=ATTR_LIGHT_COLOR_HUE) | ||
color_saturation: Optional[int] = Field(alias=ATTR_LIGHT_COLOR_SATURATION) | ||
dimmer: int = Field(alias=ATTR_LIGHT_DIMMER) | ||
id: int = Field(alias=ATTR_ID) | ||
state: int = Field(alias=ATTR_DEVICE_STATE) | ||
|
||
|
||
class Light: | ||
"""Represent a light. | ||
|
@@ -27,69 +48,83 @@ class Light: | |
""" | ||
|
||
def __init__(self, device, index): | ||
def __init__(self, device: Device, index: int): | ||
"""Create object of class.""" | ||
self.device = device | ||
self.index = index | ||
|
||
@property | ||
def supported_features(self): | ||
def supported_features(self) -> int: | ||
"""Return supported features.""" | ||
return supported_features(self.raw) | ||
|
||
@property | ||
def state(self): | ||
def state(self) -> bool: | ||
"""Return device state.""" | ||
return self.raw.get(ATTR_DEVICE_STATE) == 1 | ||
return self.raw.state == 1 | ||
|
||
@property | ||
def dimmer(self): | ||
def dimmer(self) -> int | None: | ||
"""Return dimmer if present.""" | ||
if self.supported_features & SUPPORT_BRIGHTNESS: | ||
return self.raw.get(ATTR_LIGHT_DIMMER) | ||
return self.raw.dimmer | ||
return None | ||
|
||
@property | ||
def color_temp(self): | ||
def color_temp(self) -> int | None: | ||
"""Return color temperature.""" | ||
if self.supported_features & SUPPORT_COLOR_TEMP: | ||
if self.raw.get(ATTR_LIGHT_MIREDS) != 0: | ||
return self.raw.get(ATTR_LIGHT_MIREDS) | ||
if self.supported_features & SUPPORT_COLOR_TEMP and self.raw.color_mireds: | ||
return self.raw.color_mireds | ||
return None | ||
|
||
@property | ||
def hex_color(self): | ||
def hex_color(self) -> str | None: | ||
"""Return hex color.""" | ||
if self.supported_features & SUPPORT_HEX_COLOR: | ||
return self.raw.get(ATTR_LIGHT_COLOR_HEX) | ||
return self.raw.color_hex | ||
return None | ||
|
||
@property | ||
def xy_color(self): | ||
def xy_color(self) -> tuple[int, int] | None: | ||
"""Return xy color.""" | ||
if self.supported_features & SUPPORT_XY_COLOR: | ||
return (self.raw.get(ATTR_LIGHT_COLOR_X), self.raw.get(ATTR_LIGHT_COLOR_Y)) | ||
if ( | ||
self.supported_features & SUPPORT_XY_COLOR | ||
and self.raw.color_xy_x is not None | ||
and self.raw.color_xy_y is not None | ||
): | ||
return (self.raw.color_xy_x, self.raw.color_xy_y) | ||
return None | ||
|
||
@property | ||
def hsb_xy_color(self): | ||
def hsb_xy_color( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add checks for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this used? Returning both hsb and xy together is unfamiliar to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Side note for the future: Since we return xy color from another property I would expect this one to only return hsb color. Maybe add a property that does that and deprecate this one? I agree that we should only return a tuple if all items are not None. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Want me to deprecate here in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, just some thoughts for the future. |
||
self, | ||
) -> tuple[int, int, int, int, int] | None: | ||
"""Return hsb xy color.""" | ||
return ( | ||
self.raw.get(ATTR_LIGHT_COLOR_HUE), | ||
self.raw.get(ATTR_LIGHT_COLOR_SATURATION), | ||
self.raw.get(ATTR_LIGHT_DIMMER), | ||
self.raw.get(ATTR_LIGHT_COLOR_X), | ||
self.raw.get(ATTR_LIGHT_COLOR_Y), | ||
) | ||
if ( | ||
self.raw.color_hue is not None | ||
and self.raw.color_saturation is not None | ||
and self.raw.dimmer is not None | ||
and self.raw.color_xy_x is not None | ||
and self.raw.color_xy_y is not None | ||
): | ||
return ( | ||
self.raw.color_hue, | ||
self.raw.color_saturation, | ||
self.raw.dimmer, | ||
self.raw.color_xy_x, | ||
self.raw.color_xy_y, | ||
) | ||
|
||
return None | ||
|
||
@property | ||
def raw(self) -> dict[str, Any]: | ||
def raw(self) -> LightResponse: | ||
"""Return raw data that it represents.""" | ||
light_control_response = self.device.raw.light_control | ||
assert light_control_response is not None | ||
return light_control_response[self.index] | ||
|
||
def __repr__(self): | ||
def __repr__(self) -> str: | ||
"""Return representation of class object.""" | ||
state = "on" if self.state else "off" | ||
return ( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dimmer is not optional in the response model. Can it still be not supported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of any light device that is not dimmable. Perhaps we should drop this supported_features-check and just return the int?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, maybe. Ok to leave for the future though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it for now. I'm expecting to have to update a few tests in this PR so I con't want to mistakenly cause regression errors here.