source: pyson/test/ObjectMapperTest.py@ 193

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

#84 use int instead of float for Decimals containing integral value

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