source: pyson/test/ObjectMapperTest.py@ 233

Last change on this file since 233 was 228, checked in by wouter, 4 years ago

fixing type errors

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