source: pyson/test/ObjectMapperTest.py@ 143

Last change on this file since 143 was 143, checked in by wouter, 3 years ago

#62 fix issue if referred class is actually not a class but a module

File size: 11.3 KB
RevLine 
[134]1
2import re
3import unittest
4import sys, traceback
[141]5from pyson.ObjectMapper import ObjectMapper
6from pyson.JsonSubTypes import JsonSubTypes
7from pyson.JsonTypeInfo import JsonTypeInfo
8from pyson.JsonTypeInfo import Id,As
[134]9from typing import Dict,List,Set
10import json
11
12class Props:
13 '''
14 compound class with properties, used for testing
15 '''
16 def __init__(self, age:int, name:str):
17 if age<0:
18 raise ValueError("age must be >0, got "+str(age))
19 self._age=age
20 self._name=name;
21 def __str__(self):
22 return self._name+","+str(self._age)
23 def getage(self):
24 return self._age
25 def getname(self):
26 return self._name
27 def __eq__(self, other):
28 return isinstance(other, self.__class__) and \
29 self._name==other._name and self._age==other._age
30
31@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
32class Simple:
33 def __init__(self, a:int):
34 self._a=a
35 def geta(self)->int:
36 return self._a
37 def __eq__(self, other):
38 return isinstance(other, self.__class__) and \
39 self._a==other._a
40 def __str__(self):
41 return self._name+","+str(self._a)
42
43
44class SimpleWithHash(Simple):
45 def __hash__(self):
46 return hash(self.geta())
47
48# define abstract root class
49# These need to be reachable globally for reference
50@JsonSubTypes(["test.ObjectMapperTest.Bear"])
51@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
52class Animal:
53 pass
54
55
56class Bear(Animal):
57 def __init__(self, props:Props):
58 self._props=props
59
60 def __str__(self):
61 return "Bear["+str(self._props)+"]"
62
63 def getprops(self):
64 return self._props
65 def __eq__(self, other):
66 return isinstance(other, self.__class__) and \
67 self._props==other._props
68
69
[143]70
71# A wrongly configured type, you must add jsonsubtypes here.
72@JsonSubTypes(["test.ObjectMapperTest.BadSubclass"])
73class BadSuperclassMissingTypeInfo:
74 pass
75
76class BadSubclass(BadSuperclassMissingTypeInfo):
77 def __init__(self, a:int):
78 self._a=a
79 def geta(self)->int:
80 return self._a
81 def __eq__(self, other):
82 return isinstance(other, self.__class__) and \
83 self._a==other._a
84 def __str__(self):
85 return self._name+","+str(self._a)
86
87#module instead of class.
88@JsonSubTypes(["test.ObjectMapperTest"])
89@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
90class BadSuperclassModuleInstead:
91 pass
92
93
[134]94# our parser supports non-primitive keys but python dict dont.
95# therefore the following fails even before we can start testing our code...
96# we need to create a hashable dict to get around ths
97class mydict(dict):
98 def __hash__(self, *args, **kwargs):
99 return 1
100
101
102class ObjectMapperTest(unittest.TestCase):
103 def testPrimitives(self):
[141]104 pyson=ObjectMapper()
[134]105
[141]106 res=pyson.parse(3, int)
[134]107 self.assertEquals(3, res)
108
109
110 # this throws correct,
[141]111 self.assertRaises(ValueError, lambda:pyson.parse(3, str))
[134]112
113 # this throws correct,
[141]114 self.assertRaises(ValueError, lambda:pyson.parse("ja", int))
[134]115
116 #DEMO with nested classes of different types.
[141]117 res=pyson.parse('three', str)
[134]118 print(res, type(res))
119
[141]120 pyson.parse(3.0, float)
121 pyson.parse(3.1, float)
122 pyson.parse(3j, complex)
123 pyson.parse(range(6), range)
124 pyson.parse(True, bool)
125 pyson.parse(False, bool)
126 pyson.parse(b"Hello", bytes)
127 pyson.parse(bytearray(b'\x00\x00\x00\x01'), bytearray)
[134]128
129
130 def testProps(self):
[141]131 pyson=ObjectMapper()
[134]132 propsjson={'age': 10, 'name': 'pietje'}
133 props=Props(10, "pietje")
[141]134 self.assertEquals(propsjson,pyson.toJson(props))
135 self.assertEquals(props, pyson.parse(propsjson, Props))
[134]136
137 def testParseDeepError(self):
[141]138 pyson=ObjectMapper()
[134]139 propsjson={'age': 10, 'name': 12}
140 try:
[141]141 pyson.parse(propsjson, Props)
[134]142 raise AssertionError("parser did not throw")
143 except ValueError as e:
144 # we catch this to assure the exception contains
145 # both top error and details.
146 print("received error "+str(e))
147 self.assertTrue(str(e).find("Error parsing"))
148 self.assertTrue(str(e).find("ValueError"))
149 self.assertTrue(str(e).find("expected"))
150
151
152 def testEmpty(self):
[141]153 pyson=ObjectMapper()
[134]154
155 class EmptyClass:
156 def __init__(self):
157 pass
158 def __eq__(self, other):
159 return isinstance(other, self.__class__)
160
161 obj=EmptyClass()
[141]162 print(pyson.toJson(obj))
163 res=pyson.parse({}, EmptyClass)
[134]164 self.assertEqual(obj, res)
165
166 def testSubType(self):
[141]167 pyson=ObjectMapper()
[134]168
169 class Cat():
170 def __init__(self, props:Props):
171 self._props=props
172
173 def __str__(self):
174 return "Cat["+str(self._props)+"]"
175
176 def getprops(self):
177 return self._props
178
179 obj=Cat(Props(1,'bruno'))
[141]180 print(pyson.toJson(obj))
[134]181
182
183 bson={'props':{'age':1, 'name':'bruno'}}
[141]184 res=pyson.parse(bson, Cat)
[134]185 print(res, type(res))
186 self.assertEquals(type(res.getprops()), Props)
187
188
189
190 def testInheritance(self):
[141]191 pyson=ObjectMapper()
[134]192
193
194 obj=Bear(Props(1,'bruno'))
[141]195 res=pyson.toJson(obj)
[134]196 print("result:"+str(res))
197 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
198 self.assertEquals(bson, res)
199
[141]200 res=pyson.parse(bson, Animal)
[134]201 print("Deserialized an Animal! -->"+str(res))
202 self. assertEqual(obj, res)
203
204 def testUntypedList(self):
205 class Prim:
206 def __init__(self, a:list):
207 self._a=a
208 def geta(self)->list:
209 return self._a
210
[141]211 pyson=ObjectMapper()
[134]212 obj=Prim([1,2])
213 objson = {'a':[1,2]}
214
[141]215 self.assertEqual(objson, pyson.toJson(obj))
[134]216
[141]217 self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim))
[134]218
219 def testTypedList(self):
220 '''
221 deserializes typed list contained in another object
222 '''
223 class Prim:
224 def __init__(self, a:List[str]):
225 self._a=a
226 def geta(self)->List[str]:
227 return self._a
228 def __eq__(self, other):
229 return isinstance(other, self.__class__) and \
230 self._a==other._a
231
[141]232 pyson=ObjectMapper()
[134]233 obj=Prim(["x","y"])
234 objson = {'a':["x","y"]}
235
[141]236 self.assertEqual(objson, pyson.toJson(obj))
237 self.assertEqual(obj, pyson.parse(objson, Prim))
[134]238
239 def testTypedListDirect(self):
240 '''
241 deserializes typed list directly
242 '''
243
[141]244 pyson=ObjectMapper()
[134]245 obj=["x","y"]
246 objson = ["x","y"]
247
[141]248 self.assertEqual(objson, pyson.toJson(obj))
249 self.assertEqual(obj, pyson.parse(objson, List[str]))
[134]250
251 def testTypedListOfObjMissingAnnotation(self):
252 class Prim:
253 def __init__(self, a:int):
254 self._a=a
255 def geta(self)->int:
256 return self._a
257 def __eq__(self, other):
258 return isinstance(other, self.__class__) and \
259 self._a==other._a
[141]260 pyson=ObjectMapper()
[134]261 obj=[Prim(1),Prim(3)]
262 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[141]263 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
[134]264 # object misses annotation, therefore this will try to parse
265 # Prim objects without header here.
[141]266 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
[134]267
268 def testTypedListOfObj(self):
269 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
270 class Prim:
271 def __init__(self, a:int):
272 self._a=a
273 def geta(self)->int:
274 return self._a
275 def __eq__(self, other):
276 return isinstance(other, self.__class__) and \
277 self._a==other._a
278
[141]279 pyson=ObjectMapper()
[134]280 obj=[Prim(1),Prim(3)]
281 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[141]282 self.assertEqual(objson, pyson.toJson(obj))
283 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
[134]284
285 def testTypedSetOfObj(self):
286 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
287 class Prim:
288 def __init__(self, a:int):
289 self._a=a
290 def geta(self)->int:
291 return self._a
292 def __eq__(self, other):
293 return isinstance(other, self.__class__) and \
294 self._a==other._a
295
[141]296 pyson=ObjectMapper()
[134]297 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
298 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
[141]299 self.assertEqual(objson, pyson.toJson(obj))
300 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
[134]301 self.assertEqual(obj, parsedobj)
302
303
304 def testExpectListButGiveDict(self):
305 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
306 class Prim:
307 def __init__(self, a:int):
308 self._a=a
309 def geta(self)->int:
310 return self._a
311 def __eq__(self, other):
312 return isinstance(other, self.__class__) and \
313 self._a==other._a
314
[141]315 pyson=ObjectMapper()
[134]316 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
317 # we request List but obj is a dict.
[141]318 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
[134]319
320 def testSerializeDict(self):
321
[141]322 pyson=ObjectMapper()
[134]323 obj={'a':Simple(1),'c':Simple(3)}
324 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[141]325 self.assertEqual(objson, pyson.toJson(obj))
[134]326
327
328 def testTypedDictOfObj(self):
[141]329 pyson=ObjectMapper()
[134]330 obj={'a':Simple(1),'c':Simple(3)}
331 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[141]332 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
[134]333 print("deserialized obj"+str(objson)+"="+str(obj))
334
335 def testTypedDictSimpleKey(self):
[141]336 pyson=ObjectMapper()
[134]337
338 key=mydict()
339 key["Simple"]={'a':1}
340 # simple is not hashable
341 objson = { key : 'a' }
[141]342 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
[134]343
344 def testTypedDictSimpleKeyHashable(self):
[141]345 pyson=ObjectMapper()
[134]346 # key is now not primitive!
347 obj={SimpleWithHash(1):'a'}
348
349 # simple is not hashable
350 key=mydict()
351 key["SimpleWithHash"]={'a':1}
352 # simple is not hashable
353 objson = { key : 'a' }
[141]354 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
[134]355
[143]356 def testDeserializeBadSubclass(self):
357 pyson=ObjectMapper()
358 objson= { 'BadSubclass':{ 'a':1}}
359 # FIXME the error message is poor in this case.
360 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassMissingTypeInfo))
361
362
363 def testModuleInsteadOfClassAsSubclasses(self):
364 pyson=ObjectMapper()
365 objson= { 'BadSubclass':{ 'a':1}}
366 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassModuleInstead))
367
368
369
370
Note: See TracBrowser for help on using the repository browser.