@@ -65,26 +65,29 @@ def map(
6565 * ,
6666 skip_none_values : bool = False ,
6767 fields_mapping : FieldsMap = None ,
68+ deepcopy : bool = True ,
6869 ) -> T :
6970 """Produces output object mapped from source object and custom arguments
7071
7172 Parameters:
7273 skip_none_values - do not map fields that has None value
7374 fields_mapping - mapping for fields with different names
75+ deepcopy - should we deepcopy all attributes? [default: True]
7476 """
7577 return self .__mapper ._map_common (
7678 obj ,
7779 self .__target_cls ,
7880 set (),
7981 skip_none_values = skip_none_values ,
8082 fields_mapping = fields_mapping ,
83+ deepcopy = deepcopy ,
8184 )
8285
8386
8487class Mapper :
8588 def __init__ (self ) -> None :
8689 """Initializes internal containers"""
87- self ._mappings : Dict [Type [S ], Tuple [T , FieldsMap ]] = {} # type: ignore [valid-type]
90+ self ._mappings : Dict [Type [S ], Tuple [T , FieldsMap , bool ]] = {} # type: ignore [valid-type]
8891 self ._class_specs : Dict [Type [T ], SpecFunction [T ]] = {} # type: ignore [valid-type]
8992 self ._classifier_specs : Dict [ # type: ignore [valid-type]
9093 ClassifierFunction [T ], SpecFunction [T ]
@@ -102,9 +105,7 @@ def add_spec(self, classifier: Type[T], spec_func: SpecFunction[T]) -> None:
102105 ...
103106
104107 @overload
105- def add_spec (
106- self , classifier : ClassifierFunction [T ], spec_func : SpecFunction [T ]
107- ) -> None :
108+ def add_spec (self , classifier : ClassifierFunction [T ], spec_func : SpecFunction [T ]) -> None :
108109 """Add a spec function for all classes identified by classifier function.
109110
110111 Parameters:
@@ -141,6 +142,7 @@ def add(
141142 target_cls : Type [T ],
142143 override : bool = False ,
143144 fields_mapping : FieldsMap = None ,
145+ deepcopy : bool = True ,
144146 ) -> None :
145147 """Adds mapping between object of `source class` to an object of `target class`.
146148
@@ -152,35 +154,37 @@ def add(
152154 Target class to map to
153155 override : bool, optional
154156 Override existing `source class` mapping to use new `target class`
157+ deepcopy : bool, optional
158+ Should we deepcopy all attributes? [default: True]
155159 """
156160 if source_cls in self ._mappings and not override :
157161 raise DuplicatedRegistrationError (
158162 f"source_cls { source_cls } was already added for mapping"
159163 )
160- self ._mappings [source_cls ] = (target_cls , fields_mapping )
164+ self ._mappings [source_cls ] = (target_cls , fields_mapping , deepcopy )
161165
162166 def map (
163167 self ,
164168 obj : object ,
165169 * ,
166170 skip_none_values : bool = False ,
167171 fields_mapping : FieldsMap = None ,
172+ deepcopy : bool = None ,
168173 ) -> T : # type: ignore [type-var]
169174 """Produces output object mapped from source object and custom arguments"""
170175 obj_type = type (obj )
171176 if obj_type not in self ._mappings :
172177 raise MappingError (f"Missing mapping type for input type { obj_type } " )
173178 obj_type_preffix = f"{ obj_type .__name__ } ."
174179
175- target_cls , target_cls_field_mappings = self ._mappings [obj_type ]
180+ target_cls , target_cls_field_mappings , target_deepcopy = self ._mappings [obj_type ]
176181
177182 common_fields_mapping = fields_mapping
178183 if target_cls_field_mappings :
179184 # transform mapping if it's from source class field
180185 common_fields_mapping = {
181186 target_obj_field : getattr (obj , source_field [len (obj_type_preffix ) :])
182- if isinstance (source_field , str )
183- and source_field .startswith (obj_type_preffix )
187+ if isinstance (source_field , str ) and source_field .startswith (obj_type_preffix )
184188 else source_field
185189 for target_obj_field , source_field in target_cls_field_mappings .items ()
186190 }
@@ -190,12 +194,17 @@ def map(
190194 ** fields_mapping ,
191195 } # merge two dict into one, fields_mapping has priority
192196
197+ # If deepcopy is not explicitly given, we use target_deepcopy
198+ if deepcopy is None :
199+ deepcopy = target_deepcopy
200+
193201 return self ._map_common (
194202 obj ,
195203 target_cls ,
196204 set (),
197205 skip_none_values = skip_none_values ,
198206 fields_mapping = common_fields_mapping ,
207+ deepcopy = deepcopy ,
199208 )
200209
201210 def _get_fields (self , target_cls : Type [T ]) -> Iterable [str ]:
@@ -208,9 +217,7 @@ def _get_fields(self, target_cls: Type[T]) -> Iterable[str]:
208217 if classifier (target_cls ):
209218 return self ._classifier_specs [classifier ](target_cls )
210219
211- raise MappingError (
212- f"No spec function is added for base class of { type (target_cls )} "
213- )
220+ raise MappingError (f"No spec function is added for base class of { type (target_cls )} " )
214221
215222 def _map_subobject (
216223 self , obj : S , _visited_stack : Set [int ], skip_none_values : bool = False
@@ -224,7 +231,7 @@ def _map_subobject(
224231 raise CircularReferenceError ()
225232
226233 if type (obj ) in self ._mappings :
227- target_cls , _ = self ._mappings [type (obj )]
234+ target_cls , _ , _ = self ._mappings [type (obj )]
228235 result : Any = self ._map_common (
229236 obj , target_cls , _visited_stack , skip_none_values = skip_none_values
230237 )
@@ -234,9 +241,7 @@ def _map_subobject(
234241 if is_sequence (obj ):
235242 if isinstance (obj , dict ):
236243 result = {
237- k : self ._map_subobject (
238- v , _visited_stack , skip_none_values = skip_none_values
239- )
244+ k : self ._map_subobject (v , _visited_stack , skip_none_values = skip_none_values )
240245 for k , v in obj .items ()
241246 }
242247 else :
@@ -262,12 +267,14 @@ def _map_common(
262267 _visited_stack : Set [int ],
263268 skip_none_values : bool = False ,
264269 fields_mapping : FieldsMap = None ,
270+ deepcopy : bool = True ,
265271 ) -> T :
266272 """Produces output object mapped from source object and custom arguments
267273
268274 Parameters:
269275 skip_none_values - do not map fields that has None value
270276 fields_mapping - fields mappings for fields with different names
277+ deepcopy - Should we deepcopy all attributes? [default: True]
271278 """
272279 obj_id = id (obj )
273280
@@ -293,9 +300,14 @@ def _map_common(
293300 value = obj [field_name ] # type: ignore [index]
294301
295302 if value is not None :
296- mapped_values [field_name ] = self ._map_subobject (
297- value , _visited_stack , skip_none_values
298- )
303+ if deepcopy :
304+ mapped_values [field_name ] = self ._map_subobject (
305+ value , _visited_stack , skip_none_values
306+ )
307+ else :
308+ # if deepcopy is disabled, we can act as if value was a primitive type and
309+ # avoid the ._map_subobject() call entirely.
310+ mapped_values [field_name ] = value
299311 elif not skip_none_values :
300312 mapped_values [field_name ] = None
301313
0 commit comments