@@ -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 )
231232class 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