Skip to content

Commit 2464d93

Browse files
author
direbearform
committed
merge recent changes from upstream branch
2 parents 8a6fd14 + f60f58f commit 2464d93

10 files changed

+114
-46
lines changed

.travis.yml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
language: python # this works for Linux but is an error on macOS or Windows
2+
matrix:
3+
include:
4+
- name: "Python 3.7.1 on Xenial Linux"
5+
python: 3.7 # this works for Linux but is ignored on macOS or Windows
6+
dist: xenial # required for Python >= 3.7
7+
script: bash run_tests.sh
8+
- name: "Python 3.7.3 on Windows "
9+
os: windows # Windows 10.0.17134 N/A Build 17134
10+
language: shell # 'language: python' is an error on Travis CI Windows
11+
before_install:
12+
- choco install python3
13+
- pip install virtualenv
14+
- virtualenv $HOME/venv
15+
- source $HOME/venv/Scripts/activate
16+
- pip install -r requirements.txt
17+
env: PATH=/c/Python37:/c/Python37/Scripts:$PATH
18+
script: nose2 tests --plugin nose2.plugins.junitxml --config myconfig.cfg

CHANGES.txt

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
1.0.7 - 2019/06/19
2+
* Fix type name inside mapper dict to avoid collision, thanks [@renanvieira](https://github.com/renanvieira)
3+
14
1.0.6 - 2018/10/28
25
* Added ability to specify excluded fields, thanks [@uralov](https://github.com/uralov)
36

README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,24 @@ marazt
1515
The MIT License (MIT)
1616

1717
**Last updated**
18-
10 June 2019
18+
13 July 2019
1919

2020
**Package Download**
2121
https://pypi.python.org/pypi/object-mapper
2222

23-
---
23+
**Build Status**
24+
[![Build Status](https://travis-ci.com/marazt/object-mapper.svg?branch=master)](https://travis-ci.com/marazt/object-mapper)
2425

2526
## Versions
2627

27-
**1.1.0 - 2019/06/10**
28+
**1.1.0 - 2019/07/13**
2829

2930
- Add basic support for nested object, thanks [@direbearform](https://github.com/direbearform)
3031

32+
**1.0.7 - 2019/06/19**
33+
34+
* Fix type name inside mapper dict to avoid collision, thanks [@renanvieira](https://github.com/renanvieira)
35+
3136
**1.0.6 - 2018/10/28**
3237

3338
- Added ability to specify excluded fields, thanks [@uralov](https://github.com/uralov)

README.rst

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
Object Mapper
22
=============
33

4-
**Version** 1.0.6
4+
**Version** 1.1.0
55

66
**Author** marazt
77

88
**Copyright** marazt
99

1010
**License** The MIT License (MIT)
1111

12-
**Last updated** 28 October 2018
12+
**Last updated** 13 July 2019
13+
1314

1415
**Package Download** https://pypi.python.org/pypi/object-mapper ---
1516

17+
---
18+
1619
Versions
1720
--------
1821

22+
**1.1.0 - 2019/07/13**
23+
24+
- Add basic support for nested object, thanks [@direbearform](https://github.com/direbearform)
25+
26+
**1.0.7 - 2019/06/19**
27+
28+
- Fix type name inside mapper dict to avoid collision, thanks [@renanvieira](https://github.com/renanvieira)
29+
1930
**1.0.6 - 2018/10/28**
2031

2132
- Added ability to specify excluded fields, thanks [@uralov](https://github.com/uralov)

mapper/object_mapper.py

+25-24
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
"""
33
Copyright (C) 2015, marazt. All rights reserved.
44
"""
5-
from mapper.object_mapper_exception import ObjectMapperException
6-
from mapper.casedict import CaseDict
75
from inspect import getmembers, isroutine
86
from datetime import date, datetime
97

8+
from mapper.casedict import CaseDict
9+
from mapper.object_mapper_exception import ObjectMapperException
10+
11+
1012
class ObjectMapper(object):
1113
"""
1214
Base class for mapping class attributes from one class to another one
@@ -108,13 +110,15 @@ def create_map(self, type_from, type_to, mapping=None):
108110
if (mapping is not None and not isinstance(mapping, dict)):
109111
raise ObjectMapperException("mapping, if provided, must be a Dict type")
110112

111-
key_from = "{0}.{1}".format(type_from.__module__, type_from.__name__)
112-
key_to = "{0}.{1}".format(type_to.__module__, type_to.__name__)
113+
key_from = type_from
114+
key_to = type_to
113115

114116
if key_from in self.mappings:
115117
inner_map = self.mappings[key_from]
116118
if key_to in inner_map:
117-
raise ObjectMapperException("Mapping for {0} -> {1} already exists".format(key_from, key_to))
119+
raise ObjectMapperException(
120+
"Mapping for {0}.{1} -> {2}.{3} already exists".format(key_from.__module__, key_from.__name__,
121+
key_to.__module__, key_to.__name__))
118122
else:
119123
inner_map[key_to] = (type_to, mapping)
120124
else:
@@ -142,33 +146,30 @@ def map(self, from_obj, to_type=type(None), ignore_case=False, allow_none=False,
142146
# one of the tests is explicitly checking for an attribute error on __dict__ if it's not set
143147
from_obj.__dict__
144148

145-
key_from = "{0}.{1}".format(from_obj.__class__.__module__, from_obj.__class__.__name__)
149+
key_from = from_obj.__class__
146150

147151
if key_from not in self.mappings:
148-
raise ObjectMapperException("No mapping defined for {0}".format(key_from))
152+
raise ObjectMapperException("No mapping defined for {0}.{1}"
153+
.format(key_from.__module__, key_from.__name__))
149154

150-
custom_mappings = None
151-
key_to_cls = type(None)
152-
153155
if to_type is None or to_type is type(None):
154156
# automatically infer to to_type
155157
# if this is a nested call and we do not currently support more than one to_types
156158
assert(len(self.mappings[key_from]) > 0)
157159
if len(self.mappings[key_from]) > 1:
158-
raise ObjectMapperException("Ambiguous type mapping exists for {0}, must specifiy to_type explicitly".format(key_from))
159-
the_only_key_to = next(iter(self.mappings[key_from]))
160-
key_to_cls = self.mappings[key_from][the_only_key_to][0]
161-
custom_mappings = self.mappings[key_from][the_only_key_to][1]
160+
raise ObjectMapperException("Ambiguous type mapping exists for {0}.{1}, must specifiy to_type explicitly"
161+
.format(key_from.__module__, key_from.__name__))
162+
key_to = next(iter(self.mappings[key_from]))
162163
else:
163-
key_to = "{0}.{1}".format(to_type.__module__, to_type.__name__)
164-
if key_to not in self.mappings[key_from]:
165-
raise ObjectMapperException("No mapping defined for {0} to {1}".format(key_from, key_to))
166-
key_to_cls = self.mappings[key_from][key_to][0]
167-
custom_mappings = self.mappings[key_from][key_to][1]
164+
if to_type not in self.mappings[key_from]:
165+
raise ObjectMapperException("No mapping defined for {0}.{1} -> {2}.{3}"
166+
.format(key_from.__module__, key_from.__name__, to_type.__module__, to_type.__name__))
167+
key_to = to_type
168+
custom_mappings = self.mappings[key_from][key_to][1]
168169

169170
# Currently, all target class data members need to have default value
170171
# Object with __init__ that carries required non-default arguments are not supported
171-
inst = key_to_cls()
172+
inst = key_to()
172173

173174
def not_private(s):
174175
return not s.startswith('_')
@@ -194,20 +195,20 @@ def is_included(s, mapping):
194195

195196
def map_obj(o, allow_unmapped):
196197
if o is not None:
197-
key_from_child_cls = o.__class__
198-
key_from_child = "{0}.{1}".format(key_from_child_cls.__module__, key_from_child_cls.__name__)
198+
key_from_child = o.__class__
199199
if (key_from_child in self.mappings):
200200
# if key_to has a mapping defined, nests the map() call
201201
return self.map(o, type(None), ignore_case, allow_none, excluded, included, allow_unmapped)
202-
elif (key_from_child_cls in ObjectMapper.primitive_types):
202+
elif (key_from_child in ObjectMapper.primitive_types):
203203
# allow primitive types without mapping
204204
return o
205205
else:
206206
# fail complex type conversion if mapping was not defined, unless explicitly allowed
207207
if allow_unmapped:
208208
return o
209209
else:
210-
raise ObjectMapperException("No mapping defined for {0}".format(key_from_child))
210+
raise ObjectMapperException("No mapping defined for {0}.{1}"
211+
.format(key_from_child.__module__, key_from_child.__name__))
211212
else:
212213
return None
213214

myconfig.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[junit-xml]
2+
always-on = True
3+
path = TEST-results.xml

requirements.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
datetime
2-
nose
1+
nose2==0.9.1

run_tests.bat

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
REM prerequisites: pip, virtualenv in path
22
virtualenv venv
33
cd venv/Scripts/
4-
activate.bat & cd ../.. & pip install -r requirements.txt & nosetests tests --with-xunit --xunit-file=TEST-results.xml
4+
activate.bat & cd ../.. & pip install -r requirements.txt & nose2 tests --plugin nose2.plugins.junitxml --config myconfig.cfg

run_tests.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!bin/bash
22
# prerequisites: pip, virtualenv in path
3+
pip install virtualenv
34
virtualenv venv
45
source venv/bin/activate
56
pip install -r requirements.txt
6-
nosetests tests --with-xunit --xunit-file=TEST-results.xml
7+
nose2 tests --plugin nose2.plugins.junitxml --config myconfig.cfg

tests/object_mapper_test.py tests/test_object_mapper.py

+40-13
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
Copyright (C) 2015, marazt. All rights reserved.
44
"""
55
import unittest
6-
76
from datetime import datetime
7+
88
from mapper.object_mapper import ObjectMapper
99
from mapper.object_mapper_exception import ObjectMapperException
1010

11+
NO_MAPPING_FOUND_EXCEPTION_MESSAGE = "No mapping defined for {0}.{1}"
12+
NO_MAPPING_PAIR_FOUND_EXCEPTION_MESSAGE = "No mapping defined for {0}.{1} -> {2}.{3}"
13+
MAPPING_ALREADY_EXISTS_EXCEPTION_MESSAGE = "Mapping for {0}.{1} -> {2}.{3} already exists"
14+
1115

1216
class ToTestClass(object):
1317
""" To Test Class """
18+
1419
def __init__(self):
1520
self.name = ""
1621
self.date = ""
@@ -20,19 +25,15 @@ def __init__(self):
2025

2126
class ToTestClassTwo(object):
2227
""" To Test Class Two """
28+
2329
def __init__(self):
2430
self.all = ""
2531
pass
2632

2733

2834
class ToTestClassEmpty(object):
2935
""" To Test Class Empty """
30-
def __init__(self):
31-
pass
3236

33-
34-
class ToTestClassEmpty(object):
35-
""" To Test Class Empty """
3637
def __init__(self):
3738
pass
3839

@@ -56,6 +57,7 @@ def __init__(self):
5657

5758
class FromTestClass(object):
5859
""" From Test Class """
60+
5961
def __init__(self):
6062
self.name = "Igor"
6163
self.surname = "Hnizdo"
@@ -147,7 +149,8 @@ def test_mapping_creation_duplicate_mapping(self):
147149

148150
# Arrange
149151
exc = False
150-
msg = "Mapping for tests.object_mapper_test.FromTestClass -> tests.object_mapper_test.ToTestClass already exists"
152+
msg = MAPPING_ALREADY_EXISTS_EXCEPTION_MESSAGE.format(FromTestClass.__module__, FromTestClass.__name__,
153+
ToTestClass.__module__, ToTestClass.__name__)
151154
mapper = ObjectMapper()
152155

153156
mapper.create_map(FromTestClass, ToTestClass)
@@ -231,9 +234,8 @@ def test_mapping_creation_no_mapping_defined(self):
231234

232235
# Arrange
233236
exc = False
234-
msg = "No mapping defined for tests.object_mapper_test.FromTestClass"
235237
from_class = FromTestClass()
236-
238+
msg = NO_MAPPING_FOUND_EXCEPTION_MESSAGE.format(from_class.__module__, from_class.__class__.__name__)
237239
mapper = ObjectMapper()
238240

239241
# Act
@@ -246,6 +248,28 @@ def test_mapping_creation_no_mapping_defined(self):
246248
# Assert
247249
self.assertTrue(exc, "Exception must be thrown")
248250

251+
def test_mapping_creation_no_mapping_pair_defined(self):
252+
""" Test mapping creation with no mapping defined for a from -> to pair"""
253+
254+
# Arrange
255+
exc = False
256+
from_class = FromTestClass()
257+
to_class = ToTestClass()
258+
msg = NO_MAPPING_PAIR_FOUND_EXCEPTION_MESSAGE.format(from_class.__module__, from_class.__class__.__name__,
259+
to_class.__module__, to_class.__class__.__name__)
260+
mapper = ObjectMapper()
261+
mapper.create_map(FromTestClass, ToTestClassTwo, {})
262+
263+
# Act
264+
try:
265+
mapper.map(from_class, ToTestClass)
266+
except ObjectMapperException as ex:
267+
self.assertEqual(str(ex), msg, "Exception message must be correct")
268+
exc = True
269+
270+
# Assert
271+
self.assertTrue(exc, "Exception must be thrown")
272+
249273
def test_mapping_creation_with_mapping_suppression(self):
250274
""" Test mapping creation with mapping suppression """
251275

@@ -270,11 +294,13 @@ def test_mapping_with_case_insensitivity(self):
270294
# Arrange
271295
class ToTestClass2(object):
272296
""" To Test Class 2 """
297+
273298
def __init__(self):
274299
self.name = ""
275300

276301
class FromTestClass2(object):
277302
""" From Test Class 2 """
303+
278304
def __init__(self):
279305
self.Name = "Name"
280306

@@ -283,7 +309,7 @@ def __init__(self):
283309
mapper.create_map(FromTestClass2, ToTestClass2)
284310

285311
# Act
286-
result = mapper.map(FromTestClass2(), ignore_case=True)
312+
result = mapper.map(FromTestClass2(), ToTestClass2, ignore_case=True)
287313

288314
# Assert
289315
self.assertEqual(result.name, from_class.Name, "Name mapping must be equal")
@@ -312,6 +338,7 @@ def test_mapping_creation_with_custom_dir(self):
312338

313339
# Arrange
314340
_propNames = ['name', 'date']
341+
315342
class ToCustomDirClass(object):
316343
def __dir__(self):
317344
props = list(self.__dict__.keys())
@@ -349,16 +376,16 @@ def __setattr__(self, name, value):
349376

350377
def test_mapping_excluded_field(self):
351378
"""Test mapping with excluded fields"""
352-
#Arrange
379+
# Arrange
353380
from_class = FromTestClass()
354381
mapper = ObjectMapper()
355382
mapper.create_map(FromTestClass, ToTestClass)
356383

357384
#Act
358385
result = mapper.map(FromTestClass(), excluded=['date'])
359386

360-
#Assert
361-
print(result)
387+
# Assert
388+
print(result)
362389
self.assertTrue(isinstance(result, ToTestClass), "Type must be ToTestClass")
363390
self.assertEqual(result.name, from_class.name, "Name mapping must be equal")
364391
self.assertEqual(result.date, '', "Date mapping must be equal")

0 commit comments

Comments
 (0)