source: pyson/test/ObjectMapperTest.py@ 166

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

fix issues caused by weird python behaviour in URI

File size: 16.4 KB
Line 
1
2from abc import ABC
3from datetime import datetime
4import json
5import re
6import sys, traceback
7from typing import Dict, List, Set, Any
8import unittest
9
10from uri import URI
11
12from pyson.JsonGetter import JsonGetter
13from pyson.JsonSubTypes import JsonSubTypes
14from pyson.JsonTypeInfo import Id, As
15from pyson.JsonTypeInfo import JsonTypeInfo
16from pyson.JsonValue import JsonValue
17from pyson.ObjectMapper import ObjectMapper
18
19
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
78
79# A wrongly configured type, you must add
80#@JsonSubTypes(["test.ObjectMapperTest.BadSubclass"])
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
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
119
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
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
148class ObjectMapperTest(unittest.TestCase):
149 '''
150 Test a lot of back-and-forth cases.
151 FIXME Can we make this a parameterized test?
152 '''
153
154
155 def testPrimitives(self):
156 pyson=ObjectMapper()
157
158 res=pyson.parse(3, int)
159 self.assertEquals(3, res)
160
161
162 # this throws correct,
163 self.assertRaises(ValueError, lambda:pyson.parse(3, str))
164
165 # this throws correct,
166 self.assertRaises(ValueError, lambda:pyson.parse("ja", int))
167
168 #DEMO with nested classes of different types.
169 res=pyson.parse('three', str)
170 print(res, type(res))
171
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)
180
181 jsonlh = "http://localhost/"
182 urilh = URI(jsonlh)
183 self.assertEquals(urilh, pyson.parse(jsonlh, URI))
184
185 #WARNING python is cheating us. equals succeeds but the type may be wrong!!
186 # python says URI("hello")=="hello"
187 self.assertEquals(jsonlh, pyson.toJson(urilh))
188 self.assertEquals(str, type(pyson.toJson(urilh)))
189
190
191 def testProps(self):
192 pyson=ObjectMapper()
193 propsjson={'age': 10, 'name': 'pietje'}
194 props=Props(10, "pietje")
195 self.assertEquals(propsjson,pyson.toJson(props))
196 self.assertEquals(props, pyson.parse(propsjson, Props))
197
198 def testParseDeepError(self):
199 pyson=ObjectMapper()
200 propsjson={'age': 10, 'name': 12}
201 try:
202 pyson.parse(propsjson, Props)
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):
214 pyson=ObjectMapper()
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()
223 print(pyson.toJson(obj))
224 res=pyson.parse({}, EmptyClass)
225 self.assertEqual(obj, res)
226
227 def testSubType(self):
228 pyson=ObjectMapper()
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'))
241 print(pyson.toJson(obj))
242
243
244 bson={'props':{'age':1, 'name':'bruno'}}
245 res=pyson.parse(bson, Cat)
246 print(res, type(res))
247 self.assertEquals(type(res.getprops()), Props)
248
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
255 def testInheritance(self):
256 pyson=ObjectMapper()
257
258
259 obj=Bear(Props(1,'bruno'))
260 res=pyson.toJson(obj)
261 print("result:"+str(res))
262 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
263 self.assertEquals(bson, res)
264
265 res=pyson.parse(bson, Animal)
266 print("Deserialized an Animal! -->"+str(res))
267 self. assertEqual(obj, res)
268
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
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
291 def __eq__(self, other):
292 return isinstance(other, self.__class__) and self._a==other._a
293
294 pyson=ObjectMapper()
295 obj=Prim([1,2])
296 objson = {'a':[1,2]}
297
298 self.assertEqual(objson, pyson.toJson(obj))
299 self.assertEqual(obj, pyson.parse(objson, Prim))
300
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
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
322 pyson=ObjectMapper()
323 obj=Prim(["x","y"])
324 objson = {'a':["x","y"]}
325
326 self.assertEqual(objson, pyson.toJson(obj))
327 self.assertEqual(obj, pyson.parse(objson, Prim))
328
329 def testTypedListDirect(self):
330 '''
331 deserializes typed list directly
332 '''
333
334 pyson=ObjectMapper()
335 obj=["x","y"]
336 objson = ["x","y"]
337
338 self.assertEqual(objson, pyson.toJson(obj))
339 self.assertEqual(obj, pyson.parse(objson, List[str]))
340
341 def testMixedDict(self):
342 pyson=ObjectMapper()
343 obj={'a':1, 'b':'blabla'}
344
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
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
358 pyson=ObjectMapper()
359 obj=[Prim(1),Prim(3)]
360 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
361 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
362 # object misses annotation, therefore this will try to parse
363 # Prim objects without header here.
364 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
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
377 pyson=ObjectMapper()
378 obj=[Prim(1),Prim(3)]
379 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
380 self.assertEqual(objson, pyson.toJson(obj))
381 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
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
394 pyson=ObjectMapper()
395 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
396 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
397 self.assertEqual(objson, pyson.toJson(obj))
398 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
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
413 pyson=ObjectMapper()
414 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
415 # we request List but obj is a dict.
416 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
417
418 def testSerializeDict(self):
419
420 pyson=ObjectMapper()
421 obj={'a':Simple(1),'c':Simple(3)}
422 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
423 self.assertEqual(objson, pyson.toJson(obj))
424
425
426 def testTypedDictOfObj(self):
427 pyson=ObjectMapper()
428 obj={'a':Simple(1),'c':Simple(3)}
429 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
430 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
431 print("deserialized obj"+str(objson)+"="+str(obj))
432
433 def testTypedDictSimpleKey(self):
434 pyson=ObjectMapper()
435
436 key=mydict()
437 key["Simple"]={'a':1}
438 # simple is not hashable
439 objson = { key : 'a' }
440 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
441
442 def testTypedDictSimpleKeyHashable(self):
443 pyson=ObjectMapper()
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' }
452 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
453
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
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
479 pyson=ObjectMapper()
480 getter=Getter(17)
481 objson={'a':17}
482 self.assertEqual(objson, pyson.toJson(getter))
483 self.assertEqual(getter, pyson.parse(objson, Getter))
484
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
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
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.