source: pyson/test/ObjectMapperTest.py@ 167

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

fix issues caused by weird python behaviour in URI

File size: 16.4 KB
RevLine 
[134]1
[162]2from abc import ABC
3from datetime import datetime
4import json
[134]5import re
[162]6import sys, traceback
7from typing import Dict, List, Set, Any
[134]8import unittest
[162]9
10from uri import URI
11
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
[134]18
[162]19
[134]20class Props:
21 '''
22 compound class with properties, used for testing
23 '''
24 def __init__(self, age:int, name:str):
25 if age<0:
26 raise ValueError("age must be >0, got "+str(age))
27 self._age=age
28 self._name=name;
29 def __str__(self):
30 return self._name+","+str(self._age)
31 def getage(self):
32 return self._age
33 def getname(self):
34 return self._name
35 def __eq__(self, other):
36 return isinstance(other, self.__class__) and \
37 self._name==other._name and self._age==other._age
38
39@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
40class Simple:
41 def __init__(self, a:int):
42 self._a=a
43 def geta(self)->int:
44 return self._a
45 def __eq__(self, other):
46 return isinstance(other, self.__class__) and \
47 self._a==other._a
48 def __str__(self):
49 return self._name+","+str(self._a)
50
51
52class SimpleWithHash(Simple):
53 def __hash__(self):
54 return hash(self.geta())
55
56# define abstract root class
57# These need to be reachable globally for reference
58@JsonSubTypes(["test.ObjectMapperTest.Bear"])
59@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
60class Animal:
61 pass
62
63
64class Bear(Animal):
65 def __init__(self, props:Props):
66 self._props=props
67
68 def __str__(self):
69 return "Bear["+str(self._props)+"]"
70
71 def getprops(self):
72 return self._props
73 def __eq__(self, other):
74 return isinstance(other, self.__class__) and \
75 self._props==other._props
76
77
[143]78
[154]79# A wrongly configured type, you must add
80#@JsonSubTypes(["test.ObjectMapperTest.BadSubclass"])
[143]81class BadSuperclassMissingTypeInfo:
82 pass
83
84class BadSubclass(BadSuperclassMissingTypeInfo):
85 def __init__(self, a:int):
86 self._a=a
87 def geta(self)->int:
88 return self._a
89 def __eq__(self, other):
90 return isinstance(other, self.__class__) and \
91 self._a==other._a
92 def __str__(self):
93 return self._name+","+str(self._a)
94
95#module instead of class.
96@JsonSubTypes(["test.ObjectMapperTest"])
97@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
98class BadSuperclassModuleInstead:
99 pass
100
[144]101@JsonSubTypes(["test.ObjectMapperTest.AbstractBear"])
102@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
103class AbstractAnimal(ABC):
104 pass
105
106class AbstractBear(AbstractAnimal):
107 def __init__(self, props:Props):
108 self._props=props
109
110 def __str__(self):
111 return "Bear["+str(self._props)+"]"
112
113 def getprops(self):
114 return self._props
115 def __eq__(self, other):
116 return isinstance(other, self.__class__) and \
117 self._props==other._props
118
[143]119
[134]120# our parser supports non-primitive keys but python dict dont.
121# therefore the following fails even before we can start testing our code...
122# we need to create a hashable dict to get around ths
123class mydict(dict):
124 def __hash__(self, *args, **kwargs):
125 return 1
126
127
[161]128# for testing @JsonValue
129class Snare:
130 def __init__(self, value:int):
131 self._a=value
132 @JsonValue()
133 def getValue(self):
134 return self._a
135 def __eq__(self, other):
136 return isinstance(other, self.__class__) and self._a==other._a
137
138class ContainsSnare:
139 def __init__(self, snare:Snare):
140 self._snare=snare
141 def getSnare(self):
142 return self._snare
143 def __eq__(self, other):
144 return isinstance(other, self.__class__) and self._snare==other._snare
145
146
147
[134]148class ObjectMapperTest(unittest.TestCase):
[162]149 '''
150 Test a lot of back-and-forth cases.
151 FIXME Can we make this a parameterized test?
152 '''
153
154
[134]155 def testPrimitives(self):
[141]156 pyson=ObjectMapper()
[134]157
[141]158 res=pyson.parse(3, int)
[134]159 self.assertEquals(3, res)
160
161
162 # this throws correct,
[141]163 self.assertRaises(ValueError, lambda:pyson.parse(3, str))
[134]164
165 # this throws correct,
[141]166 self.assertRaises(ValueError, lambda:pyson.parse("ja", int))
[134]167
168 #DEMO with nested classes of different types.
[141]169 res=pyson.parse('three', str)
[134]170 print(res, type(res))
171
[141]172 pyson.parse(3.0, float)
173 pyson.parse(3.1, float)
174 pyson.parse(3j, complex)
175 pyson.parse(range(6), range)
176 pyson.parse(True, bool)
177 pyson.parse(False, bool)
178 pyson.parse(b"Hello", bytes)
179 pyson.parse(bytearray(b'\x00\x00\x00\x01'), bytearray)
[134]180
[165]181 jsonlh = "http://localhost/"
182 urilh = URI(jsonlh)
[162]183 self.assertEquals(urilh, pyson.parse(jsonlh, URI))
[165]184
185 #WARNING python is cheating us. equals succeeds but the type may be wrong!!
186 # python says URI("hello")=="hello"
[162]187 self.assertEquals(jsonlh, pyson.toJson(urilh))
[165]188 self.assertEquals(str, type(pyson.toJson(urilh)))
[162]189
[134]190
191 def testProps(self):
[141]192 pyson=ObjectMapper()
[134]193 propsjson={'age': 10, 'name': 'pietje'}
194 props=Props(10, "pietje")
[141]195 self.assertEquals(propsjson,pyson.toJson(props))
196 self.assertEquals(props, pyson.parse(propsjson, Props))
[134]197
198 def testParseDeepError(self):
[141]199 pyson=ObjectMapper()
[134]200 propsjson={'age': 10, 'name': 12}
201 try:
[141]202 pyson.parse(propsjson, Props)
[134]203 raise AssertionError("parser did not throw")
204 except ValueError as e:
205 # we catch this to assure the exception contains
206 # both top error and details.
207 print("received error "+str(e))
208 self.assertTrue(str(e).find("Error parsing"))
209 self.assertTrue(str(e).find("ValueError"))
210 self.assertTrue(str(e).find("expected"))
211
212
213 def testEmpty(self):
[141]214 pyson=ObjectMapper()
[134]215
216 class EmptyClass:
217 def __init__(self):
218 pass
219 def __eq__(self, other):
220 return isinstance(other, self.__class__)
221
222 obj=EmptyClass()
[141]223 print(pyson.toJson(obj))
224 res=pyson.parse({}, EmptyClass)
[134]225 self.assertEqual(obj, res)
226
227 def testSubType(self):
[141]228 pyson=ObjectMapper()
[134]229
230 class Cat():
231 def __init__(self, props:Props):
232 self._props=props
233
234 def __str__(self):
235 return "Cat["+str(self._props)+"]"
236
237 def getprops(self):
238 return self._props
239
240 obj=Cat(Props(1,'bruno'))
[141]241 print(pyson.toJson(obj))
[134]242
243
244 bson={'props':{'age':1, 'name':'bruno'}}
[141]245 res=pyson.parse(bson, Cat)
[134]246 print(res, type(res))
247 self.assertEquals(type(res.getprops()), Props)
248
[155]249 def testDeserializeNoSuchField(self):
250 pyson=ObjectMapper()
251 # Bear has a 'props' field, bot 'b'
252 self.assertRaises(ValueError, lambda:pyson.parse({'b':1}, Props))
253 #pyson.parse({'b':1}, Props)
254
[134]255 def testInheritance(self):
[141]256 pyson=ObjectMapper()
[134]257
258
259 obj=Bear(Props(1,'bruno'))
[141]260 res=pyson.toJson(obj)
[134]261 print("result:"+str(res))
262 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
263 self.assertEquals(bson, res)
264
[141]265 res=pyson.parse(bson, Animal)
[134]266 print("Deserialized an Animal! -->"+str(res))
267 self. assertEqual(obj, res)
268
[144]269 def testAbcInheritance(self):
270 pyson=ObjectMapper()
271
272
273 obj=AbstractBear(Props(1,'bruno'))
274 res=pyson.toJson(obj)
275 print("result:"+str(res))
276 bson={'AbstractBear': {'props': {'age': 1, 'name': 'bruno'}}}
277 self.assertEquals(bson, res)
278
279 res=pyson.parse(bson, AbstractAnimal)
280 print("Deserialized an Animal! -->"+str(res))
281 self. assertEqual(obj, res)
282
283
284
[134]285 def testUntypedList(self):
286 class Prim:
287 def __init__(self, a:list):
288 self._a=a
289 def geta(self)->list:
290 return self._a
[164]291 def __eq__(self, other):
292 return isinstance(other, self.__class__) and self._a==other._a
[134]293
[141]294 pyson=ObjectMapper()
[134]295 obj=Prim([1,2])
296 objson = {'a':[1,2]}
297
[141]298 self.assertEqual(objson, pyson.toJson(obj))
[164]299 self.assertEqual(obj, pyson.parse(objson, Prim))
[134]300
[145]301 def testDateTime(self):
302 pyson=ObjectMapper()
303 objson = 1000120 # 1000.12ms since 1970
304 obj=datetime.fromtimestamp(objson/1000.0);
305 self.assertEqual(objson, pyson.toJson(obj))
306 self.assertEqual(obj, pyson.parse(objson, datetime))
307
308
[134]309 def testTypedList(self):
310 '''
311 deserializes typed list contained in another object
312 '''
313 class Prim:
314 def __init__(self, a:List[str]):
315 self._a=a
316 def geta(self)->List[str]:
317 return self._a
318 def __eq__(self, other):
319 return isinstance(other, self.__class__) and \
320 self._a==other._a
321
[141]322 pyson=ObjectMapper()
[134]323 obj=Prim(["x","y"])
324 objson = {'a':["x","y"]}
325
[141]326 self.assertEqual(objson, pyson.toJson(obj))
327 self.assertEqual(obj, pyson.parse(objson, Prim))
[134]328
329 def testTypedListDirect(self):
330 '''
331 deserializes typed list directly
332 '''
333
[141]334 pyson=ObjectMapper()
[134]335 obj=["x","y"]
336 objson = ["x","y"]
337
[141]338 self.assertEqual(objson, pyson.toJson(obj))
339 self.assertEqual(obj, pyson.parse(objson, List[str]))
[158]340
341 def testMixedDict(self):
342 pyson=ObjectMapper()
343 obj={'a':1, 'b':'blabla'}
[134]344
[158]345 # primitive types, should not be changed
346 self.assertEqual(obj, pyson.toJson(obj))
347 self.assertEqual(obj, pyson.parse(obj, Dict[Any,Any]))
348
[134]349 def testTypedListOfObjMissingAnnotation(self):
350 class Prim:
351 def __init__(self, a:int):
352 self._a=a
353 def geta(self)->int:
354 return self._a
355 def __eq__(self, other):
356 return isinstance(other, self.__class__) and \
357 self._a==other._a
[141]358 pyson=ObjectMapper()
[134]359 obj=[Prim(1),Prim(3)]
360 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[141]361 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
[134]362 # object misses annotation, therefore this will try to parse
363 # Prim objects without header here.
[141]364 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
[134]365
366 def testTypedListOfObj(self):
367 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
368 class Prim:
369 def __init__(self, a:int):
370 self._a=a
371 def geta(self)->int:
372 return self._a
373 def __eq__(self, other):
374 return isinstance(other, self.__class__) and \
375 self._a==other._a
376
[141]377 pyson=ObjectMapper()
[134]378 obj=[Prim(1),Prim(3)]
379 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
[141]380 self.assertEqual(objson, pyson.toJson(obj))
381 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
[134]382
383 def testTypedSetOfObj(self):
384 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
385 class Prim:
386 def __init__(self, a:int):
387 self._a=a
388 def geta(self)->int:
389 return self._a
390 def __eq__(self, other):
391 return isinstance(other, self.__class__) and \
392 self._a==other._a
393
[141]394 pyson=ObjectMapper()
[134]395 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
396 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
[141]397 self.assertEqual(objson, pyson.toJson(obj))
398 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
[134]399 self.assertEqual(obj, parsedobj)
400
401
402 def testExpectListButGiveDict(self):
403 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
404 class Prim:
405 def __init__(self, a:int):
406 self._a=a
407 def geta(self)->int:
408 return self._a
409 def __eq__(self, other):
410 return isinstance(other, self.__class__) and \
411 self._a==other._a
412
[141]413 pyson=ObjectMapper()
[134]414 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
415 # we request List but obj is a dict.
[141]416 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
[134]417
418 def testSerializeDict(self):
419
[141]420 pyson=ObjectMapper()
[134]421 obj={'a':Simple(1),'c':Simple(3)}
422 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[141]423 self.assertEqual(objson, pyson.toJson(obj))
[134]424
425
426 def testTypedDictOfObj(self):
[141]427 pyson=ObjectMapper()
[134]428 obj={'a':Simple(1),'c':Simple(3)}
429 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
[141]430 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
[134]431 print("deserialized obj"+str(objson)+"="+str(obj))
432
433 def testTypedDictSimpleKey(self):
[141]434 pyson=ObjectMapper()
[134]435
436 key=mydict()
437 key["Simple"]={'a':1}
438 # simple is not hashable
439 objson = { key : 'a' }
[141]440 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
[134]441
442 def testTypedDictSimpleKeyHashable(self):
[141]443 pyson=ObjectMapper()
[134]444 # key is now not primitive!
445 obj={SimpleWithHash(1):'a'}
446
447 # simple is not hashable
448 key=mydict()
449 key["SimpleWithHash"]={'a':1}
450 # simple is not hashable
451 objson = { key : 'a' }
[141]452 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
[134]453
[143]454 def testDeserializeBadSubclass(self):
455 pyson=ObjectMapper()
456 objson= { 'BadSubclass':{ 'a':1}}
457 # FIXME the error message is poor in this case.
458 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassMissingTypeInfo))
459
460
461 def testModuleInsteadOfClassAsSubclasses(self):
462 pyson=ObjectMapper()
463 objson= { 'BadSubclass':{ 'a':1}}
464 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassModuleInstead))
465
466
467
[151]468 def testJsonGetter(self):
469 class Getter:
470 def __init__(self, a:int):
471 self._a=a
472 @JsonGetter("a")
473 def getValue(self):
474 return self._a
475 def __eq__(self, other):
476 return isinstance(other, self.__class__) and \
477 self._a==other._a
478
[154]479 pyson=ObjectMapper()
[151]480 getter=Getter(17)
481 objson={'a':17}
482 self.assertEqual(objson, pyson.toJson(getter))
483 self.assertEqual(getter, pyson.parse(objson, Getter))
484
[154]485 def testGetterIgnoresCase(self):
486 class Getter:
487 def __init__(self, value:int):
488 self._a=value
489 def getValue(self):
490 return self._a
491 def __eq__(self, other):
492 return isinstance(other, self.__class__) and \
493 self._a==other._a
494
495 pyson=ObjectMapper()
496 getter=Getter(17)
497 objson={'value':17}
498 self.assertEqual(objson, pyson.toJson(getter))
499 self.assertEqual(getter, pyson.parse(objson, Getter))
500
[161]501
502 def testJsonValue(self):
503 pyson=ObjectMapper()
504 getter=Snare(17)
505 objson=17
506 print(pyson.toJson(getter))
507 self.assertEqual(objson, pyson.toJson(getter))
508 self.assertEqual(getter, pyson.parse(objson, Snare))
509
510
511 def testJsonValueAsPart(self):
512 pyson=ObjectMapper()
513 csnare=ContainsSnare(Snare(18))
514 objson={"snare":18}
515 print(pyson.toJson(csnare))
516 self.assertEqual(objson, pyson.toJson(csnare))
517 self.assertEqual(csnare, pyson.parse(objson, ContainsSnare))
518
519
[165]520 def testUriJsonValue(self):
521 class UriJson:
522 def __init__(self, value:URI):
523 self._a=value
524 @JsonValue()
525 def getValue(self):
526 return self._a
527 def __eq__(self, other):
528 return isinstance(other, self.__class__) and \
529 self._a==other._a
530
531 pyson=ObjectMapper()
532 objson="http://test/"
533 obj=UriJson(URI(objson))
534 self.assertEqual(objson, pyson.toJson(obj))
535 self.assertEqual(obj, pyson.parse(objson, UriJson))
Note: See TracBrowser for help on using the repository browser.