@@ -38,15 +38,31 @@ def _is_sequence(obj: Any) -> bool:
3838
3939
4040def _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
4347
4448
4549def _is_primitive (obj : Any ) -> bool :
4650 """Check if object type is primitive"""
4751 return type (obj ) in __PRIMITIVE_TYPES
4852
4953
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+
5066class MappingWrapper (Generic [T ]):
5167 """Internal wrapper for supporting syntax:
5268 ```
@@ -88,7 +104,7 @@ def map(
88104 self .__target_cls ,
89105 set (),
90106 skip_none_values = skip_none_values ,
91- fields_mapping = fields_mapping ,
107+ custom_mapping = fields_mapping ,
92108 use_deepcopy = use_deepcopy ,
93109 )
94110
@@ -203,17 +219,17 @@ def map(
203219 obj_type = type (obj )
204220 if obj_type not in self ._mappings :
205221 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__ } ."
207223
208224 target_cls , target_cls_field_mappings = self ._mappings [obj_type ]
209225
210226 common_fields_mapping = fields_mapping
211227 if target_cls_field_mappings :
212228 # transform mapping if it's from source class field
213229 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 ) :])
215231 if isinstance (source_field , str )
216- and source_field .startswith (obj_type_preffix )
232+ and source_field .startswith (obj_type_prefix )
217233 else source_field
218234 for target_obj_field , source_field in target_cls_field_mappings .items ()
219235 }
@@ -228,7 +244,7 @@ def map(
228244 target_cls ,
229245 set (),
230246 skip_none_values = skip_none_values ,
231- fields_mapping = common_fields_mapping ,
247+ custom_mapping = common_fields_mapping ,
232248 use_deepcopy = use_deepcopy ,
233249 )
234250
@@ -296,7 +312,7 @@ def _map_common(
296312 target_cls : Type [T ],
297313 _visited_stack : Set [int ],
298314 skip_none_values : bool = False ,
299- fields_mapping : FieldsMap = None ,
315+ custom_mapping : FieldsMap = None ,
300316 use_deepcopy : bool = True ,
301317 ) -> T :
302318 """Produces output object mapped from source object and custom arguments.
@@ -306,7 +322,7 @@ def _map_common(
306322 target_cls (Type[T]): Target class to map to.
307323 _visited_stack (Set[int]): Visited child objects. To avoid infinite recursive calls.
308324 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.
310326 Specify dictionary in format {"field_name": value_object}. Defaults to None.
311327 use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
312328 Defaults to True.
@@ -326,29 +342,20 @@ def _map_common(
326342 target_cls_fields = self ._get_fields (target_cls )
327343
328344 mapped_values : Dict [str , Any ] = {}
329- is_obj_subscriptable = _is_subscriptable (obj )
330345 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
352359
353360 _visited_stack .remove (obj_id )
354361
0 commit comments