source: pyson/test/ObjectMapperTest.py@ 189

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

#81 UUID primitive type added

File size: 19.2 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
[189]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)
[134]218 self.assertEquals(3, res)
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)
[172]242 self.assertEquals(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"
[172]246 self.assertEquals(jsonlh, self.pyson.toJson(urilh))
247 self.assertEquals(str, type(self.pyson.toJson(urilh)))
[162]248
[172]249 self.assertEquals(1.1, self.pyson.toJson(Decimal('1.1')))
250 self.assertEquals(Decimal('1.1'), self.pyson.parse(1.1, Decimal))
[171]251
[189]252 def testPrimitiveUUID(self):
253 id=uuid4()
254 self.assertEquals(id, self.pyson.parse(str(id), UUID))
255 self.assertEqual(str(id), self.pyson.toJson(id))
[134]256
257 def testProps(self):
258 propsjson={'age': 10, 'name': 'pietje'}
259 props=Props(10, "pietje")
[172]260 self.assertEquals(propsjson,self.pyson.toJson(props))
261 self.assertEquals(props, self.pyson.parse(propsjson, Props))
[134]262
263 def testParseDeepError(self):
264 propsjson={'age': 10, 'name': 12}
265 try:
[172]266 self.pyson.parse(propsjson, Props)
[134]267 raise AssertionError("parser did not throw")
268 except ValueError as e:
269 # we catch this to assure the exception contains
270 # both top error and details.
271 print("received error "+str(e))
272 self.assertTrue(str(e).find("Error parsing"))
273 self.assertTrue(str(e).find("ValueError"))
274 self.assertTrue(str(e).find("expected"))
275
276
277 def testEmpty(self):
278
279 class EmptyClass:
280 def __init__(self):
281 pass
282 def __eq__(self, other):
283 return isinstance(other, self.__class__)
284
285 obj=EmptyClass()
[172]286 print(self.pyson.toJson(obj))
287 res=self.pyson.parse({}, EmptyClass)
[134]288 self.assertEqual(obj, res)
289
290 def testSubType(self):
291
292 class Cat():
293 def __init__(self, props:Props):
294 self._props=props
295
296 def __str__(self):
297 return "Cat["+str(self._props)+"]"
298
299 def getprops(self):
300 return self._props
301
302 obj=Cat(Props(1,'bruno'))
[172]303 print(self.pyson.toJson(obj))
[134]304
305
306 bson={'props':{'age':1, 'name':'bruno'}}
[172]307 res=self.pyson.parse(bson, Cat)
[134]308 print(res, type(res))
309 self.assertEquals(type(res.getprops()), Props)
310
[155]311 def testDeserializeNoSuchField(self):
312 # Bear has a 'props' field, bot 'b'
[172]313 self.assertRaises(ValueError, lambda:self.pyson.parse({'b':1}, Props))
314 #self.pyson.parse({'b':1}, Props)
[155]315
[134]316 def testInheritance(self):
317 obj=Bear(Props(1,'bruno'))
[172]318 res=self.pyson.toJson(obj)
[134]319 print("result:"+str(res))
320 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
321 self.assertEquals(bson, res)
322
[172]323 res=self.pyson.parse(bson, Animal)
[134]324 print("Deserialized an Animal! -->"+str(res))
325 self. assertEqual(obj, res)
326
[144]327 def testAbcInheritance(self):
328 obj=AbstractBear(Props(1,'bruno'))
[172]329 res=self.pyson.toJson(obj)
[144]330 print("result:"+str(res))
331 bson={'AbstractBear': {'props': {'age': 1, 'name': 'bruno'}}}
332 self.assertEquals(bson, res)
333
[172]334 res=self.pyson.parse(bson, AbstractAnimal)
[144]335 print("Deserialized an Animal! -->"+str(res))
336 self. assertEqual(obj, res)
337
338
339
[134]340 def testUntypedList(self):
341 class Prim:
342 def __init__(self, a:list):
343 self._a=a
344 def geta(self)->list:
345 return self._a
[164]346 def __eq__(self, other):
347 return isinstance(other, self.__class__) and self._a==other._a
[134]348
349 obj=Prim([1,2])
350 objson = {'a':[1,2]}
351
[172]352 self.assertEqual(objson, self.pyson.toJson(obj))
353 self.assertEqual(obj, self.pyson.parse(objson, Prim))
[134]354
[145]355 def testDateTime(self):
356 objson = 1000120 # 1000.12ms since 1970
357 obj=datetime.fromtimestamp(objson/1000.0);
[172]358 self.assertEqual(objson, self.pyson.toJson(obj))
359 self.assertEqual(obj, self.pyson.parse(objson, datetime))
[145]360
361
[134]362 def testTypedList(self):
363 '''
364 deserializes typed list contained in another object
365 '''
366 class Prim:
367 def __init__(self, a:List[str]):
368 self._a=a
369 def geta(self)->List[str]:
370 return self._a
371 def __eq__(self, other):
372 return isinstance(other, self.__class__) and \
373 self._a==other._a
374
375 obj=Prim(["x","y"])
376 objson = {'a':["x","y"]}
377
[172]378 self.assertEqual(objson, self.pyson.toJson(obj))
379 self.assertEqual(obj, self.pyson.parse(objson, Prim))
[134]380
381 def testTypedListDirect(self):
382 '''
383 deserializes typed list directly
384 '''
385
386 obj=["x","y"]
387 objson = ["x","y"]
388
[172]389 self.assertEqual(objson, self.pyson.toJson(obj))
390 self.assertEqual(obj, self.pyson.parse(objson, List[str]))
[158]391
392 def testMixedDict(self):
393 obj={'a':1, 'b':'blabla'}
[134]394
[158]395 # primitive types, should not be changed
[172]396 self.assertEqual(obj, self.pyson.toJson(obj))
397 self.assertEqual(obj, self.pyson.parse(obj, Dict[Any,Any]))
[158]398
[134]399 def testTypedListOfObjMissingAnnotation(self):
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 obj=[Prim(1),Prim(3)]
409 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[170]410
411 # SHOULD WE CHECK THIS?
[172]412 #self.assertRaises(ValueError, lambda:self.pyson.toJson(obj))
[134]413 # object misses annotation, therefore this will try to parse
414 # Prim objects without header here.
[172]415 self.assertRaises(ValueError, lambda:self.pyson.parse(objson, List[Prim]))
[134]416
417 def testTypedListOfObj(self):
418 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
419 class Prim:
420 def __init__(self, a:int):
421 self._a=a
422 def geta(self)->int:
423 return self._a
424 def __eq__(self, other):
425 return isinstance(other, self.__class__) and \
426 self._a==other._a
427
428 obj=[Prim(1),Prim(3)]
429 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[172]430 self.assertEqual(objson, self.pyson.toJson(obj))
431 self.assertEqual(obj, self.pyson.parse(objson, List[Prim]))
[134]432
433 def testTypedSetOfObj(self):
434 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
435 class Prim:
436 def __init__(self, a:int):
437 self._a=a
438 def geta(self)->int:
439 return self._a
440 def __eq__(self, other):
441 return isinstance(other, self.__class__) and \
442 self._a==other._a
443
444 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
445 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
[172]446 self.assertEqual(objson, self.pyson.toJson(obj))
447 parsedobj=self.pyson.parse(objson, Set[SimpleWithHash])
[134]448 self.assertEqual(obj, parsedobj)
449
450
451 def testExpectListButGiveDict(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 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
463 # we request List but obj is a dict.
[172]464 self.assertRaises(ValueError,lambda:self.pyson.parse(objson, List[Prim]))
[134]465
466 def testSerializeDict(self):
467 obj={'a':Simple(1),'c':Simple(3)}
468 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[172]469 self.assertEqual(objson, self.pyson.toJson(obj))
[134]470
471
472 def testTypedDictOfObj(self):
473 obj={'a':Simple(1),'c':Simple(3)}
474 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[172]475 self.assertEqual(obj, self.pyson.parse(objson, Dict[str,Simple]))
[134]476 print("deserialized obj"+str(objson)+"="+str(obj))
477
478 def testTypedDictSimpleKey(self):
479 key=mydict()
480 key["Simple"]={'a':1}
481 # simple is not hashable
482 objson = { key : 'a' }
[172]483 self.assertRaises(ValueError,lambda:self.pyson.parse(objson, Dict[Simple,str]))
[134]484
485 def testTypedDictSimpleKeyHashable(self):
486 # key is now not primitive!
487 obj={SimpleWithHash(1):'a'}
488
489 # simple is not hashable
490 key=mydict()
491 key["SimpleWithHash"]={'a':1}
492 # simple is not hashable
493 objson = { key : 'a' }
[172]494 self.assertEqual(obj, self.pyson.parse(objson, Dict[SimpleWithHash,str]))
[134]495
[143]496 def testDeserializeBadSubclass(self):
497 objson= { 'BadSubclass':{ 'a':1}}
498 # FIXME the error message is poor in this case.
[172]499 self.assertRaises(ValueError,lambda:self.pyson.parse(objson, BadSuperclassMissingTypeInfo))
[143]500
501
502 def testModuleInsteadOfClassAsSubclasses(self):
503 objson= { 'BadSubclass':{ 'a':1}}
[172]504 self.assertRaises(ValueError,lambda:self.pyson.parse(objson, BadSuperclassModuleInstead))
[143]505
506
507
[151]508 def testJsonGetter(self):
509 class Getter:
510 def __init__(self, a:int):
511 self._a=a
512 @JsonGetter("a")
513 def getValue(self):
514 return self._a
515 def __eq__(self, other):
516 return isinstance(other, self.__class__) and \
517 self._a==other._a
518
519 getter=Getter(17)
520 objson={'a':17}
[172]521 self.assertEqual(objson, self.pyson.toJson(getter))
522 self.assertEqual(getter, self.pyson.parse(objson, Getter))
[151]523
[154]524 def testGetterIgnoresCase(self):
525 class Getter:
526 def __init__(self, value:int):
527 self._a=value
528 def getValue(self):
529 return self._a
530 def __eq__(self, other):
531 return isinstance(other, self.__class__) and \
532 self._a==other._a
533
534 getter=Getter(17)
535 objson={'value':17}
[172]536 self.assertEqual(objson, self.pyson.toJson(getter))
537 self.assertEqual(getter, self.pyson.parse(objson, Getter))
[154]538
[161]539
540 def testJsonValue(self):
541 getter=Snare(17)
542 objson=17
[172]543 print(self.pyson.toJson(getter))
544 self.assertEqual(objson, self.pyson.toJson(getter))
545 self.assertEqual(getter, self.pyson.parse(objson, Snare))
[161]546
547
548 def testJsonValueAsPart(self):
549 csnare=ContainsSnare(Snare(18))
550 objson={"snare":18}
[172]551 print(self.pyson.toJson(csnare))
552 self.assertEqual(objson, self.pyson.toJson(csnare))
553 self.assertEqual(csnare, self.pyson.parse(objson, ContainsSnare))
[161]554
555
[165]556 def testUriJsonValue(self):
557 class UriJson:
558 def __init__(self, value:URI):
559 self._a=value
560 @JsonValue()
561 def getValue(self):
562 return self._a
563 def __eq__(self, other):
564 return isinstance(other, self.__class__) and \
565 self._a==other._a
566
567 objson="http://test/"
568 obj=UriJson(URI(objson))
[172]569 self.assertEqual(objson, self.pyson.toJson(obj))
570 self.assertEqual(obj, self.pyson.parse(objson, UriJson))
[170]571
572
573 def testJsonValueInList(self):
574 snare1=Snare(1)
575 snare2=Snare(2)
576 snareList = [snare1, snare2]
[172]577 self.assertEqual([1, 2],self.pyson.toJson(snareList))
578 self.assertEqual(snareList, self.pyson.parse([1,2], List[Snare]))
579
580
581
582
583 def testJsonDeserializerAnnotation(self):
584 self.assertEqual(Custom(Decimal(1)), self.pyson.parse(1.0, Custom))
585 self.assertEqual(1.0, self.pyson.toJson(Custom(Decimal(1))))
586 self.assertEqual(Custom('bla'), self.pyson.parse('bla', Custom))
587 self.assertEqual('bla', self.pyson.toJson(Custom('bla')))
[176]588
589
590 def testDeserializeMissingValue(self):
591 a=DefaultOne()
592 self.assertEquals(1, a.getValue())
[178]593 self.assertEquals(a, self.pyson.parse({}, DefaultOne))
594
595
596 def testDeserializeDefaultNone(self):
597 a=DefaultNone()
598 self.assertEquals(None, a.getValue())
599 self.assertEquals(a, self.pyson.parse({}, DefaultNone))
[182]600
601 def testDeserializeUnion(self):
602 a=WithUnion(None)
603 self.assertEquals(None, a.getValue())
604 self.assertEquals(a, self.pyson.parse({}, WithUnion))
[183]605
606 def testDeserializeOptional(self):
607 NoneType=type(None)
608 defaultone= self.pyson.parse(None, Union[DefaultOne, NoneType])
609 self.assertEquals(None, defaultone)
610
Note: See TracBrowser for help on using the repository browser.