Skip to content

Commit bf0141c

Browse files
author
direbearform
committed
add forced inclusion for protected fields
1 parent be5b3bf commit bf0141c

File tree

3 files changed

+34
-6
lines changed

3 files changed

+34
-6
lines changed

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"python.formatting.provider": "autopep8",
3+
"python.pythonPath": "F:\\Anaconda3\\envs\\mlexp\\python.exe"
4+
}

mapper/object_mapper.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,15 @@ def create_map(self, type_from: type, type_to: type, mapping: Dict =None):
120120
self.mappings[key_from][key_to] = (type_to, mapping)
121121

122122

123-
def map(self, from_obj, to_type: type=type(None), ignore_case=False, allow_none=False, excluded=None, allow_unmapped=False):
123+
def map(self, from_obj, to_type: type=type(None), ignore_case: bool=False, allow_none: bool=False, excluded: List[str]=None, included: List[str]=None, allow_unmapped: bool=False):
124124
"""Method for creating target object instance
125125
126126
:param from_obj: source object to be mapped from
127127
:param to_type: target type
128128
:param ignore_case: if set to true, ignores attribute case when performing the mapping
129129
:param allow_none: if set to true, returns None if the source object is None; otherwise throws an exception
130130
:param excluded: A list of fields to exclude when performing the mapping
131+
:param included: A list of fields to force inclusion when performing the mapping
131132
:param allow_unmapped: if set to true, copy over the non-primitive object that didn't have a mapping defined; otherwise exception
132133
133134
:return: Instance of the target class with mapped attributes
@@ -172,12 +173,14 @@ def not_private(s):
172173
def not_excluded(s):
173174
return not (excluded and s in excluded)
174175

176+
def is_included(s):
177+
return included and s in included
178+
175179
from_obj_attributes = getmembers(from_obj, lambda a: not isroutine(a))
176-
from_obj_dict = {k: v for k, v in from_obj_attributes
177-
if not_private(k) and not_excluded(k)}
180+
from_obj_dict = {k: v for k, v in from_obj_attributes}
178181

179182
to_obj_attributes = getmembers(inst, lambda a: not isroutine(a))
180-
to_obj_dict = {k: v for k, v in to_obj_attributes if not_private(k)}
183+
to_obj_dict = {k: v for k, v in to_obj_attributes if not_excluded(k) and (not_private(k) or is_included(k))}
181184

182185
if ignore_case:
183186
from_props = CaseDict(from_obj_dict)
@@ -192,7 +195,7 @@ def map_obj(o, allow_unmapped):
192195
key_from_child = f"{key_from_child_cls.__module__}.{key_from_child_cls.__name__}"
193196
if (key_from_child in self.mappings):
194197
# if key_to has a mapping defined, nests the map() call
195-
return self.map(o, type(None), ignore_case, allow_none, excluded, allow_unmapped)
198+
return self.map(o, type(None), ignore_case, allow_none, excluded, included, allow_unmapped)
196199
elif (key_from_child_cls in ObjectMapper.primitive_types):
197200
# allow primitive types without mapping
198201
return o
@@ -209,7 +212,7 @@ def map_obj(o, allow_unmapped):
209212

210213
val = None
211214
suppress_mapping = False
212-
215+
213216
# mapping function take precedence over complex type mapping
214217
if custom_mappings is not None and prop in custom_mappings:
215218
try:

tests/object_mapper_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class ToTestClass(object):
1414
def __init__(self):
1515
self.name = ""
1616
self.date = ""
17+
self._actor_name = ""
1718
pass
1819

1920

@@ -59,6 +60,7 @@ def __init__(self):
5960
self.name = "Igor"
6061
self.surname = "Hnizdo"
6162
self.date = datetime(2015, 1, 1)
63+
self._actor_name = "Jan Triska"
6264
pass
6365

6466

@@ -101,6 +103,7 @@ def test_mapping_creation_without_mappings_correct(self):
101103
self.assertTrue(isinstance(result, ToTestClass), "Target types must be same")
102104
self.assertEqual(result.name, from_class.name, "Name mapping must be equal")
103105
self.assertEqual(result.date, from_class.date, "Date mapping must be equal")
106+
self.assertEqual(result._actor_name, "", "Private should not be copied by default")
104107
self.assertNotIn("surname", result.__dict__, "To class must not contain surname")
105108

106109
def test_mapping_creation_with_mappings_correct(self):
@@ -361,6 +364,24 @@ def test_mapping_excluded_field(self):
361364
self.assertEqual(result.date, '', "Date mapping must be equal")
362365
self.assertNotIn("surname", result.__dict__, "To class must not contain surname")
363366

367+
def test_mapping_included_field(self):
368+
"""Test mapping with included fields"""
369+
#Arrange
370+
from_class = FromTestClass()
371+
mapper = ObjectMapper()
372+
mapper.create_map(FromTestClass, ToTestClass)
373+
374+
#Act
375+
result = mapper.map(FromTestClass(), excluded=['name'], included=['name', '_actor_name'])
376+
377+
#Assert
378+
print(result)
379+
self.assertTrue(isinstance(result, ToTestClass), "Type must be ToTestClass")
380+
self.assertEqual(result.name, '', "Name must not be copied despite of inclusion, as exclusion take precedence")
381+
self.assertEqual(result.date, from_class.date, "Date mapping must be equal")
382+
self.assertEqual(result._actor_name, from_class._actor_name, "Private is copied if explicitly included")
383+
self.assertNotIn("surname", result.__dict__, "To class must not contain surname")
384+
364385
def test_mapping_creation_complex_without_mappings_correct(self):
365386
""" Test mapping creation for complex class without mappings """
366387

0 commit comments

Comments
 (0)