Skip to content

Commit

Permalink
renamed deepcopy argument, removed from method for now
Browse files Browse the repository at this point in the history
  • Loading branch information
anikolaienko committed Oct 25, 2022
1 parent 8ac61b1 commit 7010841
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 44 deletions.
44 changes: 14 additions & 30 deletions automapper/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,16 @@ def map(
*,
skip_none_values: bool = False,
fields_mapping: FieldsMap = None,
deepcopy: bool = True,
use_deepcopy: bool = True,
) -> T:
"""Produces output object mapped from source object and custom arguments.
Parameters:
skip_none_values - do not map fields that has None value
fields_mapping - mapping for fields with different names
deepcopy - should we deepcopy all attributes? [default: True]
Args:
obj (S): _description_
skip_none_values (bool, optional): Skip None values when creating `target class` obj. Defaults to False.
fields_mapping (FieldsMap, optional): Custom mapping.
Specify dictionary in format {"field_name": value_object}. Defaults to None.
deepcopy (bool, optional): Applies deepcopy to all child objects when copy into output instance.
use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
Defaults to True.
Raises:
Expand All @@ -94,14 +89,14 @@ def map(
set(),
skip_none_values=skip_none_values,
fields_mapping=fields_mapping,
deepcopy=deepcopy,
use_deepcopy=use_deepcopy,
)


class Mapper:
def __init__(self) -> None:
"""Initializes internal containers"""
self._mappings: Dict[Type[S], Tuple[T, FieldsMap, bool]] = {} # type: ignore [valid-type]
self._mappings: Dict[Type[S], Tuple[T, FieldsMap]] = {} # type: ignore [valid-type]
self._class_specs: Dict[Type[T], SpecFunction[T]] = {} # type: ignore [valid-type]
self._classifier_specs: Dict[ # type: ignore [valid-type]
ClassifierFunction[T], SpecFunction[T]
Expand Down Expand Up @@ -156,7 +151,6 @@ def add(
target_cls: Type[T],
override: bool = False,
fields_mapping: FieldsMap = None,
deepcopy: bool = True,
) -> None:
"""Adds mapping between object of `source class` to an object of `target class`.
Expand All @@ -167,8 +161,6 @@ def add(
Defaults to False.
fields_mapping (FieldsMap, optional): Custom mapping.
Specify dictionary in format {"field_name": value_object}. Defaults to None.
deepcopy (bool, optional): Applies deepcopy to all child objects when copy into output instance.
Defaults to True.
Raises:
DuplicatedRegistrationError: Same mapping for `source class` was added.
Expand All @@ -180,15 +172,15 @@ def add(
raise DuplicatedRegistrationError(
f"source_cls {source_cls} was already added for mapping"
)
self._mappings[source_cls] = (target_cls, fields_mapping, deepcopy)
self._mappings[source_cls] = (target_cls, fields_mapping)

def map(
self,
obj: object,
*,
skip_none_values: bool = False,
fields_mapping: FieldsMap = None,
deepcopy: bool = True,
use_deepcopy: bool = True,
) -> T: # type: ignore [type-var]
"""Produces output object mapped from source object and custom arguments
Expand All @@ -197,7 +189,7 @@ def map(
skip_none_values (bool, optional): Skip None values when creating `target class` obj. Defaults to False.
fields_mapping (FieldsMap, optional): Custom mapping.
Specify dictionary in format {"field_name": value_object}. Defaults to None.
deepcopy (bool, optional): Applies deepcopy to all child objects when copy into output instance.
use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
Defaults to True.
Raises:
Expand All @@ -213,9 +205,7 @@ def map(
raise MappingError(f"Missing mapping type for input type {obj_type}")
obj_type_preffix = f"{obj_type.__name__}."

target_cls, target_cls_field_mappings, target_deepcopy = self._mappings[
obj_type
]
target_cls, target_cls_field_mappings = self._mappings[obj_type]

common_fields_mapping = fields_mapping
if target_cls_field_mappings:
Expand All @@ -233,17 +223,13 @@ def map(
**fields_mapping,
} # merge two dict into one, fields_mapping has priority

# If deepcopy is not explicitly given, we use target_deepcopy
if deepcopy is None:
deepcopy = target_deepcopy

return self._map_common(
obj,
target_cls,
set(),
skip_none_values=skip_none_values,
fields_mapping=common_fields_mapping,
deepcopy=deepcopy,
use_deepcopy=use_deepcopy,
)

def _get_fields(self, target_cls: Type[T]) -> Iterable[str]:
Expand Down Expand Up @@ -272,7 +258,7 @@ def _map_subobject(
raise CircularReferenceError()

if type(obj) in self._mappings:
target_cls, _, _ = self._mappings[type(obj)]
target_cls, _ = self._mappings[type(obj)]
result: Any = self._map_common(
obj, target_cls, _visited_stack, skip_none_values=skip_none_values
)
Expand Down Expand Up @@ -310,7 +296,7 @@ def _map_common(
_visited_stack: Set[int],
skip_none_values: bool = False,
fields_mapping: FieldsMap = None,
deepcopy: bool = True,
use_deepcopy: bool = True,
) -> T:
"""Produces output object mapped from source object and custom arguments.
Expand All @@ -321,7 +307,7 @@ def _map_common(
skip_none_values (bool, optional): Skip None values when creating `target class` obj. Defaults to False.
fields_mapping (FieldsMap, optional): Custom mapping.
Specify dictionary in format {"field_name": value_object}. Defaults to None.
deepcopy (bool, optional): Applies deepcopy to all child objects when copy into output instance.
use_deepcopy (bool, optional): Apply deepcopy to all child objects when copy from source to target object.
Defaults to True.
Raises:
Expand Down Expand Up @@ -354,13 +340,11 @@ def _map_common(
value = obj[field_name] # type: ignore [index]

if value is not None:
if deepcopy:
if use_deepcopy:
mapped_values[field_name] = self._map_subobject(
value, _visited_stack, skip_none_values
)
else:
# if deepcopy is disabled, we can act as if value was a primitive type and
# avoid the ._map_subobject() call entirely.
else: # if use_deepcopy is False, simply assign value to target obj.
mapped_values[field_name] = value
elif not skip_none_values:
mapped_values[field_name] = None
Expand Down
14 changes: 2 additions & 12 deletions tests/test_automapper_dict_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ def test_map__with_dict_field(self):
self.assertEqual(public_info.products["candies"][1].name, "Snickers")
self.assertEqual(public_info.products["candies"][1].brand, "Mars, Incorporated")

def test_deepcopy_disabled(self):
public_info_deep = mapper.to(ShopPublicInfo).map(self.shop, deepcopy=False)
def test_map__use_deepcopy_false(self):
public_info_deep = mapper.to(ShopPublicInfo).map(self.shop, use_deepcopy=False)
public_info = mapper.to(ShopPublicInfo).map(self.shop)

self.assertIsNot(public_info.products, self.shop.products)
Expand All @@ -78,13 +78,3 @@ def test_deepcopy_disabled(self):
id(public_info_deep.products["magazines"]),
id(self.shop.products["magazines"]),
)

def test_deepcopy_disabled_in_add(self):
self.mapper.add(Shop, ShopPublicInfo, deepcopy=False)
public_info: ShopPublicInfo = self.mapper.map(self.shop)

self.assertIs(public_info.products, self.shop.products)

# Manually enable deepcopy on .map()
public_info2: ShopPublicInfo = self.mapper.map(self.shop, deepcopy=True)
self.assertIsNot(public_info2.products, self.shop.products)
4 changes: 2 additions & 2 deletions tests/test_automapper_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ def test_map__override_field_value_register():
mapper._mappings.clear()


def test_deepcopy():
def test_map__check_deepcopy_not_applied_if_use_deepcopy_false():
address = Address(street="Main Street", number=1, zip_code=100001, city="Test City")
info = PersonInfo("John Doe", age=35, address=address)

public_info = mapper.to(PublicPersonInfo).map(info)
assert address is not public_info.address

public_info = mapper.to(PublicPersonInfo).map(info, deepcopy=False)
public_info = mapper.to(PublicPersonInfo).map(info, use_deepcopy=False)
assert address is public_info.address

0 comments on commit 7010841

Please sign in to comment.