source: pyson/test/ObjectMapperTest.py@ 148

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

#64 added datetime primitive

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