Skip to content

Commit 7590d4c

Browse files
committed
extracting new util method + added test coverage
1 parent fa1a1ad commit 7590d4c

File tree

4 files changed

+152
-16
lines changed

4 files changed

+152
-16
lines changed

automapper/mapper.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
DuplicatedRegistrationError,
2222
MappingError,
2323
)
24-
from .utils import is_enum, is_primitive, is_sequence, object_contains
24+
from .utils import is_dictionary, is_enum, is_primitive, is_sequence, object_contains
2525

2626
# Custom Types
2727
S = TypeVar("S")
@@ -264,23 +264,24 @@ def _map_subobject(
264264
else:
265265
_visited_stack.add(obj_id)
266266

267-
if is_sequence(obj):
268-
if isinstance(obj, dict):
269-
result = {
267+
if is_dictionary(obj):
268+
result = type(obj)( # type: ignore [call-arg]
269+
{
270270
k: self._map_subobject(
271271
v, _visited_stack, skip_none_values=skip_none_values
272272
)
273-
for k, v in obj.items()
273+
for k, v in obj.items() # type: ignore [attr-defined]
274274
}
275-
else:
276-
result = type(obj)( # type: ignore [call-arg]
277-
[
278-
self._map_subobject(
279-
x, _visited_stack, skip_none_values=skip_none_values
280-
)
281-
for x in cast(Iterable[Any], obj)
282-
]
283-
)
275+
)
276+
elif is_sequence(obj):
277+
result = type(obj)( # type: ignore [call-arg]
278+
[
279+
self._map_subobject(
280+
x, _visited_stack, skip_none_values=skip_none_values
281+
)
282+
for x in cast(Iterable[Any], obj)
283+
]
284+
)
284285
else:
285286
result = deepcopy(obj)
286287

automapper/utils.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import collections
1+
import sys
2+
3+
if sys.version_info >= (3, 10):
4+
from collections.abc import Sequence
5+
else:
6+
from collections import Sequence
7+
28
from enum import Enum
39
from typing import Any
410

@@ -7,7 +13,12 @@
713

814
def is_sequence(obj: Any) -> bool:
915
"""Check if object implements `__iter__` method"""
10-
return isinstance(obj, collections.Sequence)
16+
return isinstance(obj, Sequence)
17+
18+
19+
def is_dictionary(obj: Any) -> bool:
20+
"""Check is object is of type dictionary"""
21+
return isinstance(obj, dict)
1122

1223

1324
def is_subscriptable(obj: Any) -> bool:

tests/test_automapper_dict.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from collections import OrderedDict
2+
from dataclasses import dataclass
3+
4+
from automapper import mapper
5+
6+
7+
@dataclass
8+
class Teacher:
9+
teacher: str
10+
11+
12+
class Student:
13+
def __init__(self, name: str, classes: dict[str, Teacher]):
14+
self.name = name
15+
self.classes = classes
16+
self.ordered_classes = OrderedDict(classes)
17+
18+
19+
class PublicUserInfo:
20+
def __init__(
21+
self,
22+
name: str,
23+
classes: dict[str, Teacher],
24+
ordered_classes: dict[str, Teacher],
25+
):
26+
self.name = name
27+
self.classes = classes
28+
self.ordered_classes = ordered_classes
29+
30+
31+
def test_map__dict_and_ordereddict_are_mapped_correctly_to_same_types():
32+
classes = {"math": Teacher("Ms G"), "art": Teacher("Mr A")}
33+
student = Student("Tim", classes)
34+
35+
public_info = mapper.to(PublicUserInfo).map(student)
36+
37+
assert public_info.name is student.name
38+
39+
assert public_info.classes == student.classes
40+
assert public_info.classes is not student.classes
41+
assert isinstance(public_info.classes, dict)
42+
43+
assert public_info.ordered_classes == student.ordered_classes
44+
assert public_info.ordered_classes is not student.ordered_classes
45+
assert isinstance(public_info.ordered_classes, OrderedDict)

tests/test_utils.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from collections import OrderedDict
2+
from enum import Enum
3+
4+
from automapper.utils import (
5+
is_dictionary,
6+
is_enum,
7+
is_primitive,
8+
is_sequence,
9+
is_subscriptable,
10+
object_contains,
11+
)
12+
13+
14+
def test_is_sequence__list_is_sequence():
15+
assert is_sequence([1, 2])
16+
17+
18+
def test_is_sequence__tuple_is_sequence():
19+
assert is_sequence((1, 2, 3))
20+
21+
22+
def test_is_sequence__dict_is_not_a_sequence():
23+
assert not is_sequence({"a": 1})
24+
25+
26+
def test_is_dictionary__dict_is_of_type_dictionary():
27+
assert is_dictionary({"a1": 1})
28+
29+
30+
def test_is_dictionary__ordered_dict_is_of_type_dictionary():
31+
assert is_dictionary(OrderedDict({"a1": 1}))
32+
33+
34+
def test_is_subscriptable__dict_is_subscriptable():
35+
assert is_subscriptable({"a": 1})
36+
37+
38+
def test_is_subscriptable__custom_class_can_be_subscriptable():
39+
class A:
40+
def __getitem__(self):
41+
yield 1
42+
43+
assert is_subscriptable(A())
44+
45+
46+
def test_object_contains__dict_contains_field():
47+
assert object_contains({"a1": 1, "b2": 2}, "a1")
48+
49+
50+
def test_object_contains__dict_does_not_contain_field():
51+
assert not object_contains({"a1": 1, "b2": 2}, "c3")
52+
53+
54+
def test_is_primitive__int_is_primitive():
55+
assert is_primitive(1)
56+
57+
58+
def test_is_primitive__float_is_primitive():
59+
assert is_primitive(1.2)
60+
61+
62+
def test_is_primitive__str_is_primitive():
63+
assert is_primitive("hello")
64+
65+
66+
def test_is_primitive__bool_is_primitive():
67+
assert is_primitive(False)
68+
69+
70+
def test_is_enum__object_is_enum():
71+
class EnumValue(Enum):
72+
A = "A"
73+
B = "B"
74+
75+
assert is_enum(EnumValue("A"))
76+
77+
78+
def test_is_enum__dict_is_not_enum():
79+
assert not is_enum({"A": 1, "B": 2})

0 commit comments

Comments
 (0)