32
32
__PRIMITIVE_TYPES = {int , float , complex , str , bytes , bytearray , bool }
33
33
34
34
35
- def is_sequence (obj : Any ) -> bool :
35
+ def _is_sequence (obj : Any ) -> bool :
36
36
"""Check if object implements `__iter__` method"""
37
37
return hasattr (obj , "__iter__" )
38
38
39
39
40
- def is_subscriptable (obj : Any ) -> bool :
40
+ def _is_subscriptable (obj : Any ) -> bool :
41
41
"""Check if object implements `__get_item__` method"""
42
42
return hasattr (obj , "__get_item__" )
43
43
44
44
45
- def is_primitive (obj : Any ) -> bool :
45
+ def _is_primitive (obj : Any ) -> bool :
46
46
"""Check if object type is primitive"""
47
47
return type (obj ) in __PRIMITIVE_TYPES
48
48
@@ -65,19 +65,31 @@ def map(
65
65
* ,
66
66
skip_none_values : bool = False ,
67
67
fields_mapping : FieldsMap = None ,
68
+ use_deepcopy : bool = True ,
68
69
) -> 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.
70
79
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.
74
85
"""
75
86
return self .__mapper ._map_common (
76
87
obj ,
77
88
self .__target_cls ,
78
89
set (),
79
90
skip_none_values = skip_none_values ,
80
91
fields_mapping = fields_mapping ,
92
+ use_deepcopy = use_deepcopy ,
81
93
)
82
94
83
95
@@ -94,10 +106,9 @@ def __init__(self) -> None:
94
106
def add_spec (self , classifier : Type [T ], spec_func : SpecFunction [T ]) -> None :
95
107
"""Add a spec function for all classes in inherited from base class.
96
108
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.
101
112
"""
102
113
...
103
114
@@ -107,11 +118,10 @@ def add_spec(
107
118
) -> None :
108
119
"""Add a spec function for all classes identified by classifier function.
109
120
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.
115
125
"""
116
126
...
117
127
@@ -144,14 +154,19 @@ def add(
144
154
) -> None :
145
155
"""Adds mapping between object of `source class` to an object of `target class`.
146
156
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.
155
170
"""
156
171
if source_cls in self ._mappings and not override :
157
172
raise DuplicatedRegistrationError (
@@ -165,8 +180,26 @@ def map(
165
180
* ,
166
181
skip_none_values : bool = False ,
167
182
fields_mapping : FieldsMap = None ,
183
+ use_deepcopy : bool = True ,
168
184
) -> 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
+ """
170
203
obj_type = type (obj )
171
204
if obj_type not in self ._mappings :
172
205
raise MappingError (f"Missing mapping type for input type { obj_type } " )
@@ -196,6 +229,7 @@ def map(
196
229
set (),
197
230
skip_none_values = skip_none_values ,
198
231
fields_mapping = common_fields_mapping ,
232
+ use_deepcopy = use_deepcopy ,
199
233
)
200
234
201
235
def _get_fields (self , target_cls : Type [T ]) -> Iterable [str ]:
@@ -208,15 +242,16 @@ def _get_fields(self, target_cls: Type[T]) -> Iterable[str]:
208
242
if classifier (target_cls ):
209
243
return self ._classifier_specs [classifier ](target_cls )
210
244
245
+ target_cls_name = getattr (target_cls , "__name__" , type (target_cls ))
211
246
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 } "
213
248
)
214
249
215
250
def _map_subobject (
216
251
self , obj : S , _visited_stack : Set [int ], skip_none_values : bool = False
217
252
) -> Any :
218
253
"""Maps subobjects recursively"""
219
- if is_primitive (obj ):
254
+ if _is_primitive (obj ):
220
255
return obj
221
256
222
257
obj_id = id (obj )
@@ -231,7 +266,7 @@ def _map_subobject(
231
266
else :
232
267
_visited_stack .add (obj_id )
233
268
234
- if is_sequence (obj ):
269
+ if _is_sequence (obj ):
235
270
if isinstance (obj , dict ):
236
271
result = {
237
272
k : self ._map_subobject (
@@ -262,12 +297,25 @@ def _map_common(
262
297
_visited_stack : Set [int ],
263
298
skip_none_values : bool = False ,
264
299
fields_mapping : FieldsMap = None ,
300
+ use_deepcopy : bool = True ,
265
301
) -> 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.
271
319
"""
272
320
obj_id = id (obj )
273
321
@@ -278,7 +326,7 @@ def _map_common(
278
326
target_cls_fields = self ._get_fields (target_cls )
279
327
280
328
mapped_values : Dict [str , Any ] = {}
281
- is_obj_subscriptable = is_subscriptable (obj )
329
+ is_obj_subscriptable = _is_subscriptable (obj )
282
330
for field_name in target_cls_fields :
283
331
if (
284
332
(fields_mapping and field_name in fields_mapping )
@@ -293,9 +341,12 @@ def _map_common(
293
341
value = obj [field_name ] # type: ignore [index]
294
342
295
343
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
299
350
elif not skip_none_values :
300
351
mapped_values [field_name ] = None
301
352
@@ -304,5 +355,12 @@ def _map_common(
304
355
return cast (target_cls , target_cls (** mapped_values )) # type: ignore [valid-type]
305
356
306
357
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
+ """
308
366
return MappingWrapper [T ](self , target_cls )
0 commit comments