source: pyson/test/ObjectMapperTest.py@ 145

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

#64 added datetime primitive

File size: 12.5 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
12from datetime import datetime
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
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
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
113
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):
124 pyson=ObjectMapper()
125
126 res=pyson.parse(3, int)
127 self.assertEquals(3, res)
128
129
130 # this throws correct,
131 self.assertRaises(ValueError, lambda:pyson.parse(3, str))
132
133 # this throws correct,
134 self.assertRaises(ValueError, lambda:pyson.parse("ja", int))
135
136 #DEMO with nested classes of different types.
137 res=pyson.parse('three', str)
138 print(res, type(res))
139
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)
148
149
150 def testProps(self):
151 pyson=ObjectMapper()
152 propsjson={'age': 10, 'name': 'pietje'}
153 props=Props(10, "pietje")
154 self.assertEquals(propsjson,pyson.toJson(props))
155 self.assertEquals(props, pyson.parse(propsjson, Props))
156
157 def testParseDeepError(self):
158 pyson=ObjectMapper()
159 propsjson={'age': 10, 'name': 12}
160 try:
161 pyson.parse(propsjson, Props)
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):
173 pyson=ObjectMapper()
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()
182 print(pyson.toJson(obj))
183 res=pyson.parse({}, EmptyClass)
184 self.assertEqual(obj, res)
185
186 def testSubType(self):
187 pyson=ObjectMapper()
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'))
200 print(pyson.toJson(obj))
201
202
203 bson={'props':{'age':1, 'name':'bruno'}}
204 res=pyson.parse(bson, Cat)
205 print(res, type(res))
206 self.assertEquals(type(res.getprops()), Props)
207
208
209
210 def testInheritance(self):
211 pyson=ObjectMapper()
212
213
214 obj=Bear(Props(1,'bruno'))
215 res=pyson.toJson(obj)
216 print("result:"+str(res))
217 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
218 self.assertEquals(bson, res)
219
220 res=pyson.parse(bson, Animal)
221 print("Deserialized an Animal! -->"+str(res))
222 self. assertEqual(obj, res)
223
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
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
247 pyson=ObjectMapper()
248 obj=Prim([1,2])
249 objson = {'a':[1,2]}
250
251 self.assertEqual(objson, pyson.toJson(obj))
252
253 self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim))
254
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
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
276 pyson=ObjectMapper()
277 obj=Prim(["x","y"])
278 objson = {'a':["x","y"]}
279
280 self.assertEqual(objson, pyson.toJson(obj))
281 self.assertEqual(obj, pyson.parse(objson, Prim))
282
283 def testTypedListDirect(self):
284 '''
285 deserializes typed list directly
286 '''
287
288 pyson=ObjectMapper()
289 obj=["x","y"]
290 objson = ["x","y"]
291
292 self.assertEqual(objson, pyson.toJson(obj))
293 self.assertEqual(obj, pyson.parse(objson, List[str]))
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
304 pyson=ObjectMapper()
305 obj=[Prim(1),Prim(3)]
306 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
307 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
308 # object misses annotation, therefore this will try to parse
309 # Prim objects without header here.
310 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
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
323 pyson=ObjectMapper()
324 obj=[Prim(1),Prim(3)]
325 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
326 self.assertEqual(objson, pyson.toJson(obj))
327 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
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
340 pyson=ObjectMapper()
341 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
342 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
343 self.assertEqual(objson, pyson.toJson(obj))
344 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
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
359 pyson=ObjectMapper()
360 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
361 # we request List but obj is a dict.
362 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
363
364 def testSerializeDict(self):
365
366 pyson=ObjectMapper()
367 obj={'a':Simple(1),'c':Simple(3)}
368 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
369 self.assertEqual(objson, pyson.toJson(obj))
370
371
372 def testTypedDictOfObj(self):
373 pyson=ObjectMapper()
374 obj={'a':Simple(1),'c':Simple(3)}
375 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
376 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
377 print("deserialized obj"+str(objson)+"="+str(obj))
378
379 def testTypedDictSimpleKey(self):
380 pyson=ObjectMapper()
381
382 key=mydict()
383 key["Simple"]={'a':1}
384 # simple is not hashable
385 objson = { key : 'a' }
386 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
387
388 def testTypedDictSimpleKeyHashable(self):
389 pyson=ObjectMapper()
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' }
398 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
399
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.