source: pyson/test/ObjectMapperTest.py@ 1099

Last change on this file since 1099 was 1080, checked in by wouter, 2 months ago

#359 fixed @JsonSubTypes inheritance

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