Skip to content

Commit 5f7b5f1

Browse files
added unit test to verify partial attribute mapping
added file encodings, docstrings, and new line at end of files using new style of class declaration and docstrings removed parameters to datetime that were using the default values
1 parent 668847b commit 5f7b5f1

9 files changed

+113
-36
lines changed

Diff for: CHANGES.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# coding=utf-8
2+
"""
3+
Copyright (C) 2015, marazt. All rights reserved.
4+
"""

Diff for: MANIFEST.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# coding=utf-8
2+
"""
3+
Copyright (C) 2015, marazt. All rights reserved.
4+
"""

Diff for: README.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# coding=utf-8
2+
"""
3+
Copyright (C) 2015, marazt. All rights reserved.
4+
"""

Diff for: mapper/casedict.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
# Copyright (c) 2013 Optiflows
2-
# https://bitbucket.org/optiflowsrd/obelus/src/tip/obelus/casedict.py
1+
# coding=utf-8
2+
"""
3+
Copyright (c) 2013 Optiflows
4+
https://bitbucket.org/optiflowsrd/obelus/src/tip/obelus/casedict.py
5+
"""
36

47
from collections import MutableMapping
58

@@ -45,9 +48,20 @@ def __contains__(self, key):
4548
return key.lower() in self._data
4649

4750
def clear(self):
51+
"""
52+
Removes all items from dictionary
53+
"""
4854
self._data.clear()
4955

5056
def get(self, key, default=_sentinel):
57+
"""
58+
Gets the value from the key.
59+
If the key doesn't exist, the default value is returned, otherwise None.
60+
61+
:param key: The key
62+
:param default: The default value
63+
:return: The value
64+
"""
5165
tup = self._data.get(key.lower())
5266
if tup is not None:
5367
return tup[1]
@@ -57,6 +71,14 @@ def get(self, key, default=_sentinel):
5771
return None
5872

5973
def pop(self, key, default=_sentinel):
74+
"""
75+
Removes the specified key and returns the corresponding value.
76+
If key is not found, the default is returned if given, otherwise KeyError is raised.
77+
78+
:param key: The key
79+
:param default: The default value
80+
:return: The value
81+
"""
6082
if default is not _sentinel:
6183
tup = self._data.pop(key.lower(), default)
6284
else:

Diff for: mapper/object_mapper.py

+17-19
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
# Copyright (C) 2015, marazt. All rights reserved.
1+
# coding=utf-8
2+
"""
3+
Copyright (C) 2015, marazt. All rights reserved.
4+
"""
25
from mapper.object_mapper_exception import ObjectMapperException
36
from casedict import CaseDict
47

58

6-
class ObjectMapper:
9+
class ObjectMapper(object):
710
"""
811
Base class for mapping class attributes from one class to another one
912
Supports mapping conversions too
@@ -72,8 +75,7 @@ class 'B' with attributes 'name' and 'age' and we want to map 'A' to 'B' in a wa
7275
In this case, the value of A.Name will be copied into B.name and
7376
the value of A.Age will be copied into B.age.
7477
75-
Returns:
76-
Instance of the ObjectMapper
78+
:return: Instance of the ObjectMapper
7779
"""
7880
# self.to_type = to_type
7981
self.mappings = {}
@@ -82,14 +84,12 @@ class 'B' with attributes 'name' and 'age' and we want to map 'A' to 'B' in a wa
8284
def create_map(self, type_from, type_to, mapping=None):
8385
"""Method for adding mapping definitions
8486
85-
Args:
86-
type_from: source type
87-
type_to: target type
88-
mapping: dictionary of mapping definitions in a form {'target_property_name',
89-
lambda function from rhe source}
90-
91-
Returns:
87+
:param type_from: source type
88+
:param type_to: target type
89+
:param mapping: dictionary of mapping definitions in a form {'target_property_name',
90+
lambda function from rhe source}
9291
92+
:return: None
9393
"""
9494
key_from = type_from.__name__
9595
key_to = type_to.__name__
@@ -110,14 +110,12 @@ def create_map(self, type_from, type_to, mapping=None):
110110
def map(self, from_obj, to_type, ignore_case=False, allow_none=False):
111111
"""Method for creating target object instance
112112
113-
Args:
114-
from_obj: source object to be mapped from
115-
to_type: target type
116-
ignore_case: if set to true, ignores attribute case when performing the mapping
117-
allow_none: if set to true, returns None if the source object is None; otherwise throws an exception
113+
:param from_obj: source object to be mapped from
114+
:param to_type: target type
115+
:param ignore_case: if set to true, ignores attribute case when performing the mapping
116+
:param allow_none: if set to true, returns None if the source object is None; otherwise throws an exception
118117
119-
Returns:
120-
Instance of the target class with mapped attributes
118+
:return: Instance of the target class with mapped attributes
121119
"""
122120
if (from_obj is None) and allow_none:
123121
return None
@@ -156,4 +154,4 @@ def map(self, from_obj, to_type, ignore_case=False, allow_none=False):
156154
else:
157155
raise ObjectMapperException("No mapping defined for {0} -> {1}".format(key_from, key_to))
158156

159-
return inst
157+
return inst

Diff for: mapper/object_mapper_exception.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
# Copyright (C) 2015, marazt. All rights reserved.
1+
# coding=utf-8
2+
"""
3+
Copyright (C) 2015, marazt. All rights reserved.
4+
"""
25

36

47
class ObjectMapperException(Exception):
58
"""
69
Object Mapper exception
710
"""
8-
pass
11+
pass

Diff for: run.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# coding=utf-8
2+
"""
3+
Copyright (C) 2015, marazt. All rights reserved.
4+
"""

Diff for: setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# coding=utf-8
12
"""
23
Package setup configuration needed for correct package creation
34
Taken from: https://www.digitalocean.com/community/tutorials/how-to-package-and-distribute-python-applications
@@ -40,4 +41,4 @@
4041
"datetime",
4142
"nose",
4243
],
43-
)
44+
)

Diff for: tests/object_mapper_test.py

+49-12
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,41 @@
1-
# Copyright (C) 2015, marazt. All rights reserved.
1+
# coding=utf-8
2+
"""
3+
Copyright (C) 2015, marazt. All rights reserved.
4+
"""
25
import unittest
36

47
from datetime import datetime
58
from mapper.object_mapper import ObjectMapper
69
from mapper.object_mapper_exception import ObjectMapperException
710

811

9-
class ToTestClass:
12+
class ToTestClass(object):
13+
""" To Test Class """
1014
def __init__(self):
1115
self.name = ""
1216
self.date = ""
1317
pass
1418

1519

16-
class ToTestClassTwo:
20+
class ToTestClassTwo(object):
21+
""" To Test Class Two """
1722
def __init__(self):
1823
self.all = ""
1924
pass
2025

2126

22-
class ToTestClassEmpty:
27+
class ToTestClassEmpty(object):
28+
""" To Test Class Empty """
2329
def __init__(self):
2430
pass
2531

2632

27-
class FromTestClass:
33+
class FromTestClass(object):
34+
""" From Test Class """
2835
def __init__(self):
2936
self.name = "Igor"
3037
self.surname = "Hnizdo"
31-
self.date = datetime(2015, 1, 1, 0, 0)
38+
self.date = datetime(2015, 1, 1)
3239
pass
3340

3441

@@ -37,10 +44,8 @@ class ObjectMapperTest(unittest.TestCase):
3744
Unit tests for the `ObjectMapper` module.
3845
"""
3946

40-
def setUp(self):
41-
pass
42-
4347
def test_mapping_creation_without_mappings_correct(self):
48+
""" Test mapping creation without mappings """
4449

4550
# Arrange
4651
from_class = FromTestClass()
@@ -57,6 +62,7 @@ def test_mapping_creation_without_mappings_correct(self):
5762
self.assertNotIn("surname", result.__dict__, "To class must not contain surname")
5863

5964
def test_mapping_creation_with_mappings_correct(self):
65+
""" Test mapping creation with mappings """
6066

6167
# Arrange
6268
from_class = FromTestClass()
@@ -92,6 +98,7 @@ def test_mapping_creation_with_mappings_correct(self):
9298
self.assertTrue(len(result3.__dict__) == 0, "There must be no attributes")
9399

94100
def test_mapping_creation_duplicate_mapping(self):
101+
""" Test mapping creation with duplicate mappings """
95102

96103
# Arrange
97104
exc = False
@@ -111,6 +118,7 @@ def test_mapping_creation_duplicate_mapping(self):
111118
self.assertTrue(exc, "Exception must be thrown")
112119

113120
def test_mapping_creation_invalid_mapping_function(self):
121+
""" Test mapping creation with invalid mapping function """
114122

115123
# Arrange
116124
exc = False
@@ -129,6 +137,8 @@ def test_mapping_creation_invalid_mapping_function(self):
129137
self.assertTrue(exc, "Exception must be thrown")
130138

131139
def test_mapping_creation_none_target(self):
140+
""" Test mapping creation with none target """
141+
132142
# Arrange
133143
exc = None
134144
from_class = None
@@ -152,6 +162,8 @@ def test_mapping_creation_none_target(self):
152162
self.assertEqual("'NoneType' object has no attribute '__dict__'", exc.message)
153163

154164
def test_mapping_with_none_source_and_allow_none_returns_none(self):
165+
""" Test mapping with none source and allow none returns none """
166+
155167
# Arrange
156168
from_class = None
157169
mappings = \
@@ -170,6 +182,8 @@ def test_mapping_with_none_source_and_allow_none_returns_none(self):
170182
self.assertEqual(None, result)
171183

172184
def test_mapping_creation_no_mapping_defined(self):
185+
""" Test mapping creation with no mapping defined """
186+
173187
# Arrange
174188
exc = False
175189
msg = "No mapping defined for FromTestClass -> ToTestClass"
@@ -188,6 +202,7 @@ def test_mapping_creation_no_mapping_defined(self):
188202
self.assertTrue(exc, "Exception must be thrown")
189203

190204
def test_mapping_creation_with_mapping_suppression(self):
205+
""" Test mapping creation with mapping suppression """
191206

192207
# Arrange
193208
from_class = FromTestClass()
@@ -205,13 +220,16 @@ def test_mapping_creation_with_mapping_suppression(self):
205220
self.assertNotIn("surname", result1.__dict__, "To class must not contain surname")
206221

207222
def test_mapping_with_case_insensitivity(self):
223+
""" Test mapping with case insensitivity """
208224

209225
# Arrange
210-
class ToTestClass2:
226+
class ToTestClass2(object):
227+
""" To Test Class 2 """
211228
def __init__(self):
212229
self.name = ""
213230

214-
class FromTestClass2:
231+
class FromTestClass2(object):
232+
""" From Test Class 2 """
215233
def __init__(self):
216234
self.Name = "Name"
217235

@@ -223,4 +241,23 @@ def __init__(self):
223241
result = mapper.map(FromTestClass2(), ToTestClass2, ignore_case=True)
224242

225243
# Assert
226-
self.assertEqual(result.name, from_class.Name, "Name mapping must be equal")
244+
self.assertEqual(result.name, from_class.Name, "Name mapping must be equal")
245+
246+
def test_mapping_creation_with_partial_mapping_correct(self):
247+
""" Test mapping creation with partial mapping """
248+
249+
# Arrange
250+
from_class = FromTestClass()
251+
mapper = ObjectMapper()
252+
mapper.create_map(FromTestClass, ToTestClass,
253+
{"name": lambda x: "{0} {1}".format(x.name, x.surname)})
254+
255+
# Act
256+
result1 = mapper.map(from_class, ToTestClass)
257+
258+
# Assert
259+
self.assertTrue(isinstance(result1, ToTestClass), "Type must be ToTestClass")
260+
self.assertEqual(result1.name, "{0} {1}".format(from_class.name, from_class.surname),
261+
"Name mapping must be equal")
262+
self.assertEqual(result1.date, from_class.date, "Date mapping must be equal")
263+
self.assertNotIn("surname", result1.__dict__, "To class must not contain surname")

0 commit comments

Comments
 (0)