source: pyson/test/ObjectMapperTest.py@ 144

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

#63 now handling case where superclass extends ABC

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