source: pyson/test/ObjectMapperTest.py@ 164

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

small fixes #73

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