source: pyson/test/ObjectMapperTest.py@ 162

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

#71 uri now available as primitive

File size: 15.6 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
287
[141]288 pyson=ObjectMapper()
[134]289 obj=Prim([1,2])
290 objson = {'a':[1,2]}
291
[141]292 self.assertEqual(objson, pyson.toJson(obj))
[134]293
[141]294 self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim))
[134]295
[145]296 def testDateTime(self):
297 pyson=ObjectMapper()
298 objson = 1000120 # 1000.12ms since 1970
299 obj=datetime.fromtimestamp(objson/1000.0);
300 self.assertEqual(objson, pyson.toJson(obj))
301 self.assertEqual(obj, pyson.parse(objson, datetime))
302
303
[134]304 def testTypedList(self):
305 '''
306 deserializes typed list contained in another object
307 '''
308 class Prim:
309 def __init__(self, a:List[str]):
310 self._a=a
311 def geta(self)->List[str]:
312 return self._a
313 def __eq__(self, other):
314 return isinstance(other, self.__class__) and \
315 self._a==other._a
316
[141]317 pyson=ObjectMapper()
[134]318 obj=Prim(["x","y"])
319 objson = {'a':["x","y"]}
320
[141]321 self.assertEqual(objson, pyson.toJson(obj))
322 self.assertEqual(obj, pyson.parse(objson, Prim))
[134]323
324 def testTypedListDirect(self):
325 '''
326 deserializes typed list directly
327 '''
328
[141]329 pyson=ObjectMapper()
[134]330 obj=["x","y"]
331 objson = ["x","y"]
332
[141]333 self.assertEqual(objson, pyson.toJson(obj))
334 self.assertEqual(obj, pyson.parse(objson, List[str]))
[158]335
336 def testMixedDict(self):
337 pyson=ObjectMapper()
338 obj={'a':1, 'b':'blabla'}
[134]339
[158]340 # primitive types, should not be changed
341 self.assertEqual(obj, pyson.toJson(obj))
342 self.assertEqual(obj, pyson.parse(obj, Dict[Any,Any]))
343
[134]344 def testTypedListOfObjMissingAnnotation(self):
345 class Prim:
346 def __init__(self, a:int):
347 self._a=a
348 def geta(self)->int:
349 return self._a
350 def __eq__(self, other):
351 return isinstance(other, self.__class__) and \
352 self._a==other._a
[141]353 pyson=ObjectMapper()
[134]354 obj=[Prim(1),Prim(3)]
355 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[141]356 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
[134]357 # object misses annotation, therefore this will try to parse
358 # Prim objects without header here.
[141]359 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
[134]360
361 def testTypedListOfObj(self):
362 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
363 class Prim:
364 def __init__(self, a:int):
365 self._a=a
366 def geta(self)->int:
367 return self._a
368 def __eq__(self, other):
369 return isinstance(other, self.__class__) and \
370 self._a==other._a
371
[141]372 pyson=ObjectMapper()
[134]373 obj=[Prim(1),Prim(3)]
374 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[141]375 self.assertEqual(objson, pyson.toJson(obj))
376 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
[134]377
378 def testTypedSetOfObj(self):
379 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
380 class Prim:
381 def __init__(self, a:int):
382 self._a=a
383 def geta(self)->int:
384 return self._a
385 def __eq__(self, other):
386 return isinstance(other, self.__class__) and \
387 self._a==other._a
388
[141]389 pyson=ObjectMapper()
[134]390 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
391 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
[141]392 self.assertEqual(objson, pyson.toJson(obj))
393 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
[134]394 self.assertEqual(obj, parsedobj)
395
396
397 def testExpectListButGiveDict(self):
398 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
399 class Prim:
400 def __init__(self, a:int):
401 self._a=a
402 def geta(self)->int:
403 return self._a
404 def __eq__(self, other):
405 return isinstance(other, self.__class__) and \
406 self._a==other._a
407
[141]408 pyson=ObjectMapper()
[134]409 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
410 # we request List but obj is a dict.
[141]411 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
[134]412
413 def testSerializeDict(self):
414
[141]415 pyson=ObjectMapper()
[134]416 obj={'a':Simple(1),'c':Simple(3)}
417 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[141]418 self.assertEqual(objson, pyson.toJson(obj))
[134]419
420
421 def testTypedDictOfObj(self):
[141]422 pyson=ObjectMapper()
[134]423 obj={'a':Simple(1),'c':Simple(3)}
424 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[141]425 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
[134]426 print("deserialized obj"+str(objson)+"="+str(obj))
427
428 def testTypedDictSimpleKey(self):
[141]429 pyson=ObjectMapper()
[134]430
431 key=mydict()
432 key["Simple"]={'a':1}
433 # simple is not hashable
434 objson = { key : 'a' }
[141]435 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
[134]436
437 def testTypedDictSimpleKeyHashable(self):
[141]438 pyson=ObjectMapper()
[134]439 # key is now not primitive!
440 obj={SimpleWithHash(1):'a'}
441
442 # simple is not hashable
443 key=mydict()
444 key["SimpleWithHash"]={'a':1}
445 # simple is not hashable
446 objson = { key : 'a' }
[141]447 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
[134]448
[143]449 def testDeserializeBadSubclass(self):
450 pyson=ObjectMapper()
451 objson= { 'BadSubclass':{ 'a':1}}
452 # FIXME the error message is poor in this case.
453 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassMissingTypeInfo))
454
455
456 def testModuleInsteadOfClassAsSubclasses(self):
457 pyson=ObjectMapper()
458 objson= { 'BadSubclass':{ 'a':1}}
459 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassModuleInstead))
460
461
462
[151]463 def testJsonGetter(self):
464 class Getter:
465 def __init__(self, a:int):
466 self._a=a
467 @JsonGetter("a")
468 def getValue(self):
469 return self._a
470 def __eq__(self, other):
471 return isinstance(other, self.__class__) and \
472 self._a==other._a
473
[154]474 pyson=ObjectMapper()
[151]475 getter=Getter(17)
476 objson={'a':17}
477 self.assertEqual(objson, pyson.toJson(getter))
478 self.assertEqual(getter, pyson.parse(objson, Getter))
479
[154]480 def testGetterIgnoresCase(self):
481 class Getter:
482 def __init__(self, value:int):
483 self._a=value
484 def getValue(self):
485 return self._a
486 def __eq__(self, other):
487 return isinstance(other, self.__class__) and \
488 self._a==other._a
489
490 pyson=ObjectMapper()
491 getter=Getter(17)
492 objson={'value':17}
493 self.assertEqual(objson, pyson.toJson(getter))
494 self.assertEqual(getter, pyson.parse(objson, Getter))
495
[161]496
497 def testJsonValue(self):
498 pyson=ObjectMapper()
499 getter=Snare(17)
500 objson=17
501 print(pyson.toJson(getter))
502 self.assertEqual(objson, pyson.toJson(getter))
503 self.assertEqual(getter, pyson.parse(objson, Snare))
504
505
506 def testJsonValueAsPart(self):
507 pyson=ObjectMapper()
508 csnare=ContainsSnare(Snare(18))
509 objson={"snare":18}
510 print(pyson.toJson(csnare))
511 self.assertEqual(objson, pyson.toJson(csnare))
512 self.assertEqual(csnare, pyson.parse(objson, ContainsSnare))
513
514
515
Note: See TracBrowser for help on using the repository browser.