source: pyson/test/ObjectMapperTest.py@ 498

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

#104 fix rounding issue in pyson datetime

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