@@ -38,15 +38,31 @@ def _is_sequence(obj: Any) -> bool:
38
38
39
39
40
40
def _is_subscriptable (obj : Any ) -> bool :
41
- """Check if object implements `__get_item__` method"""
42
- return hasattr (obj , "__get_item__" )
41
+ """Check if object implements `__getitem__` method"""
42
+ return hasattr (obj , "__getitem__" )
43
+
44
+
45
+ def _object_contains (obj : Any , field_name : str ) -> bool :
46
+ return _is_subscriptable (obj ) and field_name in obj
43
47
44
48
45
49
def _is_primitive (obj : Any ) -> bool :
46
50
"""Check if object type is primitive"""
47
51
return type (obj ) in __PRIMITIVE_TYPES
48
52
49
53
54
+ def _try_get_field_value (
55
+ field_name : str , original_obj : Any , custom_mapping : FieldsMap
56
+ ) -> Tuple [bool , Any ]:
57
+ if field_name in (custom_mapping or {}):
58
+ return True , custom_mapping [field_name ] # type: ignore [index]
59
+ if hasattr (original_obj , field_name ):
60
+ return True , getattr (original_obj , field_name )
61
+ if _object_contains (original_obj , field_name ):
62
+ return True , original_obj [field_name ]
63
+ return False , None
64
+
65
+
50
66
class MappingWrapper (Generic [T ]):
51
67
"""Internal wrapper for supporting syntax:
52
68
```
@@ -88,7 +104,7 @@ def map(
88
104
self .__target_cls ,
89
105
set (),
90
106
skip_none_values = skip_none_values ,
91
- fields_mapping = fields_mapping ,
107
+ custom_mapping = fields_mapping ,
92
108
use_deepcopy = use_deepcopy ,
93
109
)
94
110
@@ -203,17 +219,17 @@ def map(
203
219
obj_type = type (obj )
204
220
if obj_type not in self ._mappings :
205
221
raise MappingError (f"Missing mapping type for input type { obj_type } " )
206
- obj_type_preffix = f"{ obj_type .__name__ } ."
222
+ obj_type_prefix = f"{ obj_type .__name__ } ."
207
223
208
224
target_cls , target_cls_field_mappings = self ._mappings [obj_type ]
209
225
210
226
common_fields_mapping = fields_mapping
211
227
if target_cls_field_mappings :
212
228
# transform mapping if it's from source class field
213
229
common_fields_mapping = {
214
- target_obj_field : getattr (obj , source_field [len (obj_type_preffix ) :])
230
+ target_obj_field : getattr (obj , source_field [len (obj_type_prefix ) :])
215
231
if isinstance (source_field , str )
216
- and source_field .startswith (obj_type_preffix )
232
+ and source_field .startswith (obj_type_prefix )
217
233
else source_field
218
234
for target_obj_field , source_field in target_cls_field_mappings .items ()
219
235
}
@@ -228,7 +244,7 @@ def map(
228
244
target_cls ,
229
245
set (),
230
246
skip_none_values = skip_none_values ,
231
- fields_mapping = common_fields_mapping ,
247
+ custom_mapping = common_fields_mapping ,
232
248
use_deepcopy = use_deepcopy ,
233
249
)
234
250
@@ -296,7 +312,7 @@ def _map_common(
296
312
target_cls : Type [T ],
297
313
_visited_stack : Set [int ],
298
314
skip_none_values : bool = False ,
299
- fields_mapping : FieldsMap = None ,
315
+ custom_mapping : FieldsMap = None ,
300
316
use_deepcopy : bool = True ,
301
317
) -> T :
302
318
"""Produces output object mapped from source object and custom arguments.
@@ -306,7 +322,7 @@ def _map_common(
306
322
target_cls (Type[T]): Target class to map to.
307
323
_visited_stack (Set[int]): Visited child objects. To avoid infinite recursive calls.
308
324
skip_none_values (bool, optional): Skip None values when creating `target class` obj. Defaults to False.
309
- fields_mapping (FieldsMap, optional): Custom mapping.
325
+ custom_mapping (FieldsMap, optional): Custom mapping.
310
326
Specify dictionary in format {"field_name": value_object}. Defaults to None.
311
327
use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
312
328
Defaults to True.
@@ -326,29 +342,20 @@ def _map_common(
326
342
target_cls_fields = self ._get_fields (target_cls )
327
343
328
344
mapped_values : Dict [str , Any ] = {}
329
- is_obj_subscriptable = _is_subscriptable (obj )
330
345
for field_name in target_cls_fields :
331
- if (
332
- (fields_mapping and field_name in fields_mapping )
333
- or hasattr (obj , field_name )
334
- or (is_obj_subscriptable and field_name in obj ) # type: ignore [operator]
335
- ):
336
- if fields_mapping and field_name in fields_mapping :
337
- value = fields_mapping [field_name ]
338
- elif hasattr (obj , field_name ):
339
- value = getattr (obj , field_name )
340
- else :
341
- value = obj [field_name ] # type: ignore [index]
342
-
343
- if value is not None :
344
- if use_deepcopy :
345
- mapped_values [field_name ] = self ._map_subobject (
346
- value , _visited_stack , skip_none_values
347
- )
348
- else : # if use_deepcopy is False, simply assign value to target obj.
349
- mapped_values [field_name ] = value
350
- elif not skip_none_values :
351
- mapped_values [field_name ] = None
346
+ value_found , value = _try_get_field_value (field_name , obj , custom_mapping )
347
+ if not value_found :
348
+ continue
349
+
350
+ if value is not None :
351
+ if use_deepcopy :
352
+ mapped_values [field_name ] = self ._map_subobject (
353
+ value , _visited_stack , skip_none_values
354
+ )
355
+ else : # if use_deepcopy is False, simply assign value to target obj.
356
+ mapped_values [field_name ] = value
357
+ elif not skip_none_values :
358
+ mapped_values [field_name ] = None
352
359
353
360
_visited_stack .remove (obj_id )
354
361
0 commit comments