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
Line 
1
2import re
3import unittest
4import sys, traceback
5from pyson.ObjectMapper import ObjectMapper
6from pyson.JsonSubTypes import JsonSubTypes
7from pyson.JsonTypeInfo import JsonTypeInfo
8from pyson.JsonTypeInfo import Id,As
9from typing import Dict,List,Set
10import json
11from abc import ABC
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
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
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
112
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):
123 pyson=ObjectMapper()
124
125 res=pyson.parse(3, int)
126 self.assertEquals(3, res)
127
128
129 # this throws correct,
130 self.assertRaises(ValueError, lambda:pyson.parse(3, str))
131
132 # this throws correct,
133 self.assertRaises(ValueError, lambda:pyson.parse("ja", int))
134
135 #DEMO with nested classes of different types.
136 res=pyson.parse('three', str)
137 print(res, type(res))
138
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)
147
148
149 def testProps(self):
150 pyson=ObjectMapper()
151 propsjson={'age': 10, 'name': 'pietje'}
152 props=Props(10, "pietje")
153 self.assertEquals(propsjson,pyson.toJson(props))
154 self.assertEquals(props, pyson.parse(propsjson, Props))
155
156 def testParseDeepError(self):
157 pyson=ObjectMapper()
158 propsjson={'age': 10, 'name': 12}
159 try:
160 pyson.parse(propsjson, Props)
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):
172 pyson=ObjectMapper()
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()
181 print(pyson.toJson(obj))
182 res=pyson.parse({}, EmptyClass)
183 self.assertEqual(obj, res)
184
185 def testSubType(self):
186 pyson=ObjectMapper()
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'))
199 print(pyson.toJson(obj))
200
201
202 bson={'props':{'age':1, 'name':'bruno'}}
203 res=pyson.parse(bson, Cat)
204 print(res, type(res))
205 self.assertEquals(type(res.getprops()), Props)
206
207
208
209 def testInheritance(self):
210 pyson=ObjectMapper()
211
212
213 obj=Bear(Props(1,'bruno'))
214 res=pyson.toJson(obj)
215 print("result:"+str(res))
216 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
217 self.assertEquals(bson, res)
218
219 res=pyson.parse(bson, Animal)
220 print("Deserialized an Animal! -->"+str(res))
221 self. assertEqual(obj, res)
222
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
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
246 pyson=ObjectMapper()
247 obj=Prim([1,2])
248 objson = {'a':[1,2]}
249
250 self.assertEqual(objson, pyson.toJson(obj))
251
252 self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim))
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
267 pyson=ObjectMapper()
268 obj=Prim(["x","y"])
269 objson = {'a':["x","y"]}
270
271 self.assertEqual(objson, pyson.toJson(obj))
272 self.assertEqual(obj, pyson.parse(objson, Prim))
273
274 def testTypedListDirect(self):
275 '''
276 deserializes typed list directly
277 '''
278
279 pyson=ObjectMapper()
280 obj=["x","y"]
281 objson = ["x","y"]
282
283 self.assertEqual(objson, pyson.toJson(obj))
284 self.assertEqual(obj, pyson.parse(objson, List[str]))
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
295 pyson=ObjectMapper()
296 obj=[Prim(1),Prim(3)]
297 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
298 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
299 # object misses annotation, therefore this will try to parse
300 # Prim objects without header here.
301 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
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
314 pyson=ObjectMapper()
315 obj=[Prim(1),Prim(3)]
316 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
317 self.assertEqual(objson, pyson.toJson(obj))
318 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
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
331 pyson=ObjectMapper()
332 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
333 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
334 self.assertEqual(objson, pyson.toJson(obj))
335 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
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
350 pyson=ObjectMapper()
351 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
352 # we request List but obj is a dict.
353 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
354
355 def testSerializeDict(self):
356
357 pyson=ObjectMapper()
358 obj={'a':Simple(1),'c':Simple(3)}
359 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
360 self.assertEqual(objson, pyson.toJson(obj))
361
362
363 def testTypedDictOfObj(self):
364 pyson=ObjectMapper()
365 obj={'a':Simple(1),'c':Simple(3)}
366 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
367 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
368 print("deserialized obj"+str(objson)+"="+str(obj))
369
370 def testTypedDictSimpleKey(self):
371 pyson=ObjectMapper()
372
373 key=mydict()
374 key["Simple"]={'a':1}
375 # simple is not hashable
376 objson = { key : 'a' }
377 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
378
379 def testTypedDictSimpleKeyHashable(self):
380 pyson=ObjectMapper()
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' }
389 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
390
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.