Skip to content

Commit bd47a56

Browse files
committed
Simplify Device to use _flexible_init and resolve ui_class/widget at init
Replace manual __init__ with declarative attrs fields and __attrs_post_init__. The ui_class and widget fields are now public, resolved at construction time from either the API kwargs or the definition fallback.
1 parent 86910f0 commit bd47a56

1 file changed

Lines changed: 21 additions & 74 deletions

File tree

pyoverkiz/models.py

Lines changed: 21 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -220,26 +220,30 @@ def _to_states(value: list[dict[str, Any]] | States | None) -> States:
220220
return States(value)
221221

222222

223-
def _to_definition(value: dict[str, Any] | Definition) -> Definition:
224-
"""Converter: raw dict or Definition -> Definition instance."""
225-
if isinstance(value, Definition):
226-
return value
227-
return Definition(**value)
223+
def _to_command_definitions(value: Any) -> Any:
224+
"""Converter: raw list -> CommandDefinitions, or passthrough."""
225+
if isinstance(value, list):
226+
return _resolve("CommandDefinitions")(value)
227+
return value
228228

229229

230-
@define(init=False, kw_only=True)
230+
@_flexible_init
231+
@define(kw_only=True)
231232
class Device:
232233
"""Representation of a device in the setup including parsed fields and states."""
233234

234-
attributes: States
235+
attributes: States = field(factory=lambda: _to_states(None), converter=_to_states)
235236
available: bool
236237
enabled: bool
237238
label: str = field(repr=obfuscate_string)
238239
device_url: str = field(repr=obfuscate_id)
239240
controllable_name: str
240-
definition: Definition
241-
states: States
242-
type: ProductType
241+
definition: Definition = field(converter=_to_optional("Definition"))
242+
states: States = field(factory=lambda: _to_states(None), converter=_to_states)
243+
type: ProductType = field(converter=ProductType)
244+
ui_class: UIClass | None = field(default=None, converter=_to_optional_enum(UIClass))
245+
widget: UIWidget | None = field(default=None, converter=_to_optional_enum(UIWidget))
246+
identifier: DeviceIdentifier = field(init=False, repr=False)
243247
oid: str | None = field(repr=obfuscate_id, default=None)
244248
place_oid: str | None = None
245249
creation_time: int | None = None
@@ -248,73 +252,16 @@ class Device:
248252
metadata: str | None = None
249253
synced: bool | None = None
250254
subsystem_id: int | None = None
251-
identifier: DeviceIdentifier = field(init=False, repr=False)
252-
_ui_class: UIClass | None = field(init=False, repr=False)
253-
_widget: UIWidget | None = field(init=False, repr=False)
254255

255-
def __init__(
256-
self,
257-
*,
258-
attributes: list[dict[str, Any]] | States | None = None,
259-
available: bool,
260-
enabled: bool,
261-
label: str,
262-
device_url: str,
263-
controllable_name: str,
264-
definition: dict[str, Any] | Definition,
265-
widget: str | None = None,
266-
ui_class: str | None = None,
267-
states: list[dict[str, Any]] | States | None = None,
268-
type: int | ProductType,
269-
oid: str | None = None,
270-
place_oid: str | None = None,
271-
creation_time: int | None = None,
272-
last_update_time: int | None = None,
273-
shortcut: bool | None = None,
274-
metadata: str | None = None,
275-
synced: bool | None = None,
276-
subsystem_id: int | None = None,
277-
**_: Any,
278-
) -> None:
279-
"""Initialize Device and parse URL, protocol and nested definitions."""
280-
self.attributes = _to_states(attributes)
281-
self.available = available
282-
self.definition = _to_definition(definition)
283-
self.device_url = device_url
284-
self.enabled = enabled
285-
self.label = label
286-
self.controllable_name = controllable_name
287-
self.states = _to_states(states)
288-
self.type = ProductType(type) if not isinstance(type, ProductType) else type
289-
self.oid = oid
290-
self.place_oid = place_oid
291-
self.creation_time = creation_time
292-
self.last_update_time = last_update_time
293-
self.shortcut = shortcut
294-
self.metadata = metadata
295-
self.synced = synced
296-
self.subsystem_id = subsystem_id
297-
self.identifier = DeviceIdentifier.from_device_url(device_url)
298-
self._ui_class = UIClass(ui_class) if ui_class else None
299-
self._widget = UIWidget(widget) if widget else None
256+
def __attrs_post_init__(self) -> None:
257+
"""Resolve computed fields from device URL and definition fallbacks."""
258+
self.identifier = DeviceIdentifier.from_device_url(self.device_url)
300259

301-
@property
302-
def ui_class(self) -> UIClass:
303-
"""Return the UI class, falling back to the definition if available."""
304-
if self._ui_class is not None:
305-
return self._ui_class
306-
if self.definition.ui_class:
307-
return UIClass(self.definition.ui_class)
308-
raise ValueError(f"Device {self.device_url} has no UI class defined")
260+
if self.ui_class is None and self.definition.ui_class:
261+
self.ui_class = UIClass(self.definition.ui_class)
309262

310-
@property
311-
def widget(self) -> UIWidget:
312-
"""Return the widget, falling back to the definition if available."""
313-
if self._widget is not None:
314-
return self._widget
315-
if self.definition.widget_name:
316-
return UIWidget(self.definition.widget_name)
317-
raise ValueError(f"Device {self.device_url} has no widget defined")
263+
if self.widget is None and self.definition.widget_name:
264+
self.widget = UIWidget(self.definition.widget_name)
318265

319266
def supports_command(self, command: str | OverkizCommand) -> bool:
320267
"""Check if device supports a command."""

0 commit comments

Comments
 (0)