source: pyson/test/ObjectMapperTest.py@ 188

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

#79 parse null issue

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