source: pyson/test/ObjectMapperTest.py@ 196

Last change on this file since 196 was 194, checked in by wouter, 4 years ago

#31 added more tests. Still not found any issue

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