2
2
from __future__ import annotations
3
3
4
4
from base64 import b64decode , b64encode
5
+ import binascii
5
6
from dataclasses import MISSING , asdict , fields , is_dataclass
6
7
from datetime import datetime
7
8
from enum import Enum
@@ -78,7 +79,7 @@ def _convert_value(value: Any) -> Any:
78
79
if isinstance (value , Enum ):
79
80
return value .value
80
81
if isinstance (value , bytes ):
81
- return b64encode (value ).decode ()
82
+ return b64encode (value ).decode ("utf-8" )
82
83
if isinstance (value , float32 ):
83
84
return float (value )
84
85
if type (value ) == type :
@@ -115,11 +116,8 @@ def parse_value(name: str, value: Any, value_type: Any, default: Any = MISSING)
115
116
if hasattr (value_type , "from_dict" ):
116
117
return value_type .from_dict (value )
117
118
# handle a parse error in the sdk which is returned as:
118
- # {'TLVValue': None, 'Reason': None}
119
- if (
120
- value .get ("TLVValue" , MISSING ) is None
121
- and value .get ("Reason" , MISSING ) is None
122
- ):
119
+ # {'TLVValue': None, 'Reason': None} or {'TLVValue': None}
120
+ if value .get ("TLVValue" , MISSING ) is None :
123
121
if value_type in (None , Nullable , Any ):
124
122
return None
125
123
value = None
@@ -132,13 +130,14 @@ def parse_value(name: str, value: Any, value_type: Any, default: Any = MISSING)
132
130
return None
133
131
if is_dataclass (value_type ) and isinstance (value , dict ):
134
132
return dataclass_from_dict (value_type , value )
135
- origin = get_origin (value_type )
136
- if origin is list and isinstance (value , list ):
137
- return [
133
+ # get origin value type and inspect one-by-one
134
+ origin : Any = get_origin (value_type )
135
+ if origin in (list , tuple ) and isinstance (value , list | tuple ):
136
+ return origin (
138
137
parse_value (name , subvalue , get_args (value_type )[0 ])
139
138
for subvalue in value
140
139
if subvalue is not None
141
- ]
140
+ )
142
141
# handle dictionary where we should inspect all values
143
142
elif origin is dict :
144
143
subkey_type = get_args (value_type )[0 ]
@@ -175,13 +174,19 @@ def parse_value(name: str, value: Any, value_type: Any, default: Any = MISSING)
175
174
return None
176
175
elif origin is type :
177
176
return get_type_hints (value , globals (), locals ())
177
+ # handle Any as value type (which is basically unprocessable)
178
178
if value_type is Any :
179
179
return value
180
+ # raise if value is None and the value is required according to annotations
180
181
if value is None and value_type is not NoneType :
181
182
raise KeyError (f"`{ name } ` of type `{ value_type } ` is required." )
182
183
183
184
try :
184
185
if issubclass (value_type , Enum ):
186
+ # handle enums from the SDK that have a value that does not exist in the enum (sigh)
187
+ if value not in value_type ._value2member_map_ :
188
+ # we do not want to crash so we return the raw value
189
+ return value
185
190
return value_type (value )
186
191
if issubclass (value_type , datetime ):
187
192
return parse_utc_timestamp (value )
@@ -194,8 +199,14 @@ def parse_value(name: str, value: Any, value_type: Any, default: Any = MISSING)
194
199
return float (value )
195
200
if value_type is int and isinstance (value , str ) and value .isnumeric ():
196
201
return int (value )
202
+ # handle bytes values (sent over the wire as base64 encoded strings)
197
203
if value_type is bytes and isinstance (value , str ):
198
- return b64decode (value .encode ())
204
+ try :
205
+ return b64decode (value .encode ("utf-8" ))
206
+ except binascii .Error :
207
+ # unfortunately sometimes the data is malformed
208
+ # as it is not super important we ignore it (for now)
209
+ return b""
199
210
200
211
# Matter SDK specific types
201
212
if value_type is uint and (
0 commit comments