22
33from __future__ import annotations
44
5+ import json
6+ import logging
57from dataclasses import dataclass , field
68from datetime import datetime , timezone
79from typing import Any
2426 WidgetType ,
2527)
2628
29+ _LOGGER = logging .getLogger (__name__ )
30+
2731from ._general import (
2832 BaseWidget ,
2933 BaseWidgetOutput ,
3034 CommandResponse ,
3135 Thing ,
3236 Widget ,
3337)
34-
3538from ._update import FirmwareSettings
3639
3740
41+ def _filter_valid_widgets (
42+ items : list [dict [str , Any ] | str ], field_name : str
43+ ) -> list [dict [str , Any ] | str ]:
44+ """Filter items with valid WidgetType codes, logging warnings for invalid ones.
45+
46+ Args:
47+ items: List of widget dicts or code strings
48+ field_name: Name of the field being filtered (for logging)
49+
50+ Returns:
51+ List of items with valid widget codes
52+ """
53+ valid_items = []
54+ for item in items :
55+ # Extract code - either directly if string, or from 'code' key if dict
56+ if code := (item if isinstance (item , str ) else item .get ("code" )):
57+ try :
58+ WidgetType (code )
59+ except ValueError :
60+ # Log entire JSON if it's a dict, otherwise just the code
61+ if isinstance (item , dict ):
62+ _LOGGER .warning (
63+ "Unknown widget in field '%s' will be discarded: %s" ,
64+ field_name ,
65+ json .dumps (item ),
66+ )
67+ else :
68+ _LOGGER .warning (
69+ "Unknown widget code '%s' in field '%s' will be discarded" ,
70+ code ,
71+ field_name ,
72+ )
73+ else :
74+ valid_items .append (item )
75+ return valid_items
76+
77+
3878@dataclass (kw_only = True )
3979class ThingConfig (DataClassJSONMixin ):
4080 """Dashboard config with widgets."""
@@ -44,11 +84,16 @@ class ThingConfig(DataClassJSONMixin):
4484
4585 @classmethod
4686 def __pre_deserialize__ (cls , d : dict [str , Any ]) -> dict [str , Any ]:
47- # move code to widget_type for mashumaro annotated serialization
48- widgets = d ["widgets" ]
49- for widget in widgets :
50- widget ["output" ]["widget_type" ] = widget ["code" ]
51- d ["widgets" ] = widgets
87+ # Filter out widgets with unknown codes and log warnings
88+ widgets = d .get ("widgets" , [])
89+ valid_widgets = _filter_valid_widgets (widgets , "widgets" )
90+
91+ # Set widget_type for valid widgets
92+ for item in valid_widgets :
93+ assert isinstance (item , dict )
94+ item ["output" ]["widget_type" ] = item ["code" ]
95+
96+ d ["widgets" ] = valid_widgets
5297 return d
5398
5499 @classmethod
@@ -75,6 +120,17 @@ class ThingDashboardWebsocketConfig(ThingConfig):
75120 uuid : str
76121 commands : list [CommandResponse ]
77122
123+ @classmethod
124+ def __pre_deserialize__ (cls , d : dict [str , Any ]) -> dict [str , Any ]:
125+ # First call parent's __pre_deserialize__ to handle widgets
126+ d = super ().__pre_deserialize__ (d )
127+
128+ # Filter out removed_widgets with unknown codes and log warnings
129+ d ["removedWidgets" ] = _filter_valid_widgets (
130+ d .get ("removedWidgets" , []), "removedWidgets"
131+ )
132+ return d
133+
78134
79135@dataclass (kw_only = True )
80136class MachineStatus (BaseWidgetOutput ):
0 commit comments