3232__PRIMITIVE_TYPES = {int , float , complex , str , bytes , bytearray , bool }
3333
3434
35- def is_sequence (obj : Any ) -> bool :
35+ def _is_sequence (obj : Any ) -> bool :
3636 """Check if object implements `__iter__` method"""
3737 return hasattr (obj , "__iter__" )
3838
3939
40- def is_subscriptable (obj : Any ) -> bool :
40+ def _is_subscriptable (obj : Any ) -> bool :
4141 """Check if object implements `__get_item__` method"""
4242 return hasattr (obj , "__get_item__" )
4343
4444
45- def is_primitive (obj : Any ) -> bool :
45+ def _is_primitive (obj : Any ) -> bool :
4646 """Check if object type is primitive"""
4747 return type (obj ) in __PRIMITIVE_TYPES
4848
@@ -65,19 +65,31 @@ def map(
6565 * ,
6666 skip_none_values : bool = False ,
6767 fields_mapping : FieldsMap = None ,
68+ use_deepcopy : bool = True ,
6869 ) -> T :
69- """Produces output object mapped from source object and custom arguments
70+ """Produces output object mapped from source object and custom arguments.
71+
72+ Args:
73+ obj (S): _description_
74+ skip_none_values (bool, optional): Skip None values when creating `target class` obj. Defaults to False.
75+ fields_mapping (FieldsMap, optional): Custom mapping.
76+ Specify dictionary in format {"field_name": value_object}. Defaults to None.
77+ use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
78+ Defaults to True.
7079
71- Parameters:
72- skip_none_values - do not map fields that has None value
73- fields_mapping - mapping for fields with different names
80+ Raises:
81+ CircularReferenceError: Circular references in `source class` object are not allowed yet.
82+
83+ Returns:
84+ T: instance of `target class` with mapped values from `source class` or custom `fields_mapping` dictionary.
7485 """
7586 return self .__mapper ._map_common (
7687 obj ,
7788 self .__target_cls ,
7889 set (),
7990 skip_none_values = skip_none_values ,
8091 fields_mapping = fields_mapping ,
92+ use_deepcopy = use_deepcopy ,
8193 )
8294
8395
@@ -94,10 +106,9 @@ def __init__(self) -> None:
94106 def add_spec (self , classifier : Type [T ], spec_func : SpecFunction [T ]) -> None :
95107 """Add a spec function for all classes in inherited from base class.
96108
97- Parameters:
98- * classifier - base class to identify all descendant classes
99- * spec_func - returns a list of fields (List[str]) for target class
100- that are accepted in constructor
109+ Args:
110+ classifier (ClassifierFunction[T]): base class to identify all descendant classes.
111+ spec_func (SpecFunction[T]): get list of fields (List[str]) for `target class` to be passed in constructor.
101112 """
102113 ...
103114
@@ -107,11 +118,10 @@ def add_spec(
107118 ) -> None :
108119 """Add a spec function for all classes identified by classifier function.
109120
110- Parameters:
111- * classifier - boolean predicate that identifies a group of classes
112- by certain characteristics: if class has a specific method or a field, etc.
113- * spec_func - returns a list of fields (List[str]) for target class
114- that are accepted in constructor
121+ Args:
122+ classifier (ClassifierFunction[T]): boolean predicate that identifies a group of classes
123+ by certain characteristics: if class has a specific method or a field, etc.
124+ spec_func (SpecFunction[T]): get list of fields (List[str]) for `target class` to be passed in constructor.
115125 """
116126 ...
117127
@@ -144,14 +154,19 @@ def add(
144154 ) -> None :
145155 """Adds mapping between object of `source class` to an object of `target class`.
146156
147- Parameters
148- ----------
149- source_cls : Type
150- Source class to map from
151- target_cls : Type
152- Target class to map to
153- override : bool, optional
154- Override existing `source class` mapping to use new `target class`
157+ Args:
158+ source_cls (Type[S]): Source class to map from
159+ target_cls (Type[T]): Target class to map to
160+ override (bool, optional): Override existing `source class` mapping to use new `target class`.
161+ Defaults to False.
162+ fields_mapping (FieldsMap, optional): Custom mapping.
163+ Specify dictionary in format {"field_name": value_object}. Defaults to None.
164+
165+ Raises:
166+ DuplicatedRegistrationError: Same mapping for `source class` was added.
167+ Only one mapping per source class can exist at a time for now.
168+ You can specify target class manually using `mapper.to(target_cls)` method
169+ or use `override` argument to replace existing mapping.
155170 """
156171 if source_cls in self ._mappings and not override :
157172 raise DuplicatedRegistrationError (
@@ -165,8 +180,26 @@ def map(
165180 * ,
166181 skip_none_values : bool = False ,
167182 fields_mapping : FieldsMap = None ,
183+ use_deepcopy : bool = True ,
168184 ) -> T : # type: ignore [type-var]
169- """Produces output object mapped from source object and custom arguments"""
185+ """Produces output object mapped from source object and custom arguments
186+
187+ Args:
188+ obj (object): Source object to map to `target class`.
189+ skip_none_values (bool, optional): Skip None values when creating `target class` obj. Defaults to False.
190+ fields_mapping (FieldsMap, optional): Custom mapping.
191+ Specify dictionary in format {"field_name": value_object}. Defaults to None.
192+ use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
193+ Defaults to True.
194+
195+ Raises:
196+ MappingError: No `target class` specified to be mapped into.
197+ Register mappings using `mapped.add(...)` or specify `target class` using `mapper.to(target_cls).map()`.
198+ CircularReferenceError: Circular references in `source class` object are not allowed yet.
199+
200+ Returns:
201+ T: instance of `target class` with mapped values from `source class` or custom `fields_mapping` dictionary.
202+ """
170203 obj_type = type (obj )
171204 if obj_type not in self ._mappings :
172205 raise MappingError (f"Missing mapping type for input type { obj_type } " )
@@ -196,6 +229,7 @@ def map(
196229 set (),
197230 skip_none_values = skip_none_values ,
198231 fields_mapping = common_fields_mapping ,
232+ use_deepcopy = use_deepcopy ,
199233 )
200234
201235 def _get_fields (self , target_cls : Type [T ]) -> Iterable [str ]:
@@ -208,15 +242,16 @@ def _get_fields(self, target_cls: Type[T]) -> Iterable[str]:
208242 if classifier (target_cls ):
209243 return self ._classifier_specs [classifier ](target_cls )
210244
245+ target_cls_name = getattr (target_cls , "__name__" , type (target_cls ))
211246 raise MappingError (
212- f"No spec function is added for base class of { type ( target_cls ) } "
247+ f"No spec function is added for base class of { target_cls_name !r } "
213248 )
214249
215250 def _map_subobject (
216251 self , obj : S , _visited_stack : Set [int ], skip_none_values : bool = False
217252 ) -> Any :
218253 """Maps subobjects recursively"""
219- if is_primitive (obj ):
254+ if _is_primitive (obj ):
220255 return obj
221256
222257 obj_id = id (obj )
@@ -231,7 +266,7 @@ def _map_subobject(
231266 else :
232267 _visited_stack .add (obj_id )
233268
234- if is_sequence (obj ):
269+ if _is_sequence (obj ):
235270 if isinstance (obj , dict ):
236271 result = {
237272 k : self ._map_subobject (
@@ -262,12 +297,25 @@ def _map_common(
262297 _visited_stack : Set [int ],
263298 skip_none_values : bool = False ,
264299 fields_mapping : FieldsMap = None ,
300+ use_deepcopy : bool = True ,
265301 ) -> T :
266- """Produces output object mapped from source object and custom arguments
267-
268- Parameters:
269- skip_none_values - do not map fields that has None value
270- fields_mapping - fields mappings for fields with different names
302+ """Produces output object mapped from source object and custom arguments.
303+
304+ Args:
305+ obj (S): Source object to map to `target class`.
306+ target_cls (Type[T]): Target class to map to.
307+ _visited_stack (Set[int]): Visited child objects. To avoid infinite recursive calls.
308+ skip_none_values (bool, optional): Skip None values when creating `target class` obj. Defaults to False.
309+ fields_mapping (FieldsMap, optional): Custom mapping.
310+ Specify dictionary in format {"field_name": value_object}. Defaults to None.
311+ use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
312+ Defaults to True.
313+
314+ Raises:
315+ CircularReferenceError: Circular references in `source class` object are not allowed yet.
316+
317+ Returns:
318+ T: Instance of `target class` with mapped fields.
271319 """
272320 obj_id = id (obj )
273321
@@ -278,7 +326,7 @@ def _map_common(
278326 target_cls_fields = self ._get_fields (target_cls )
279327
280328 mapped_values : Dict [str , Any ] = {}
281- is_obj_subscriptable = is_subscriptable (obj )
329+ is_obj_subscriptable = _is_subscriptable (obj )
282330 for field_name in target_cls_fields :
283331 if (
284332 (fields_mapping and field_name in fields_mapping )
@@ -293,9 +341,12 @@ def _map_common(
293341 value = obj [field_name ] # type: ignore [index]
294342
295343 if value is not None :
296- mapped_values [field_name ] = self ._map_subobject (
297- value , _visited_stack , skip_none_values
298- )
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
299350 elif not skip_none_values :
300351 mapped_values [field_name ] = None
301352
@@ -304,5 +355,12 @@ def _map_common(
304355 return cast (target_cls , target_cls (** mapped_values )) # type: ignore [valid-type]
305356
306357 def to (self , target_cls : Type [T ]) -> MappingWrapper [T ]:
307- """Specify target class to map source object to"""
358+ """Specify `target class` to which map `source class` object.
359+
360+ Args:
361+ target_cls (Type[T]): Target class.
362+
363+ Returns:
364+ MappingWrapper[T]: Mapping wrapper. Use `map` method to perform mapping now.
365+ """
308366 return MappingWrapper [T ](self , target_cls )
0 commit comments