source: packson3/packson/JsonTools.py@ 136

Last change on this file since 136 was 134, checked in by wouter, 4 years ago

added first python project. Needs some more cleanup

File size: 5.7 KB
Line 
1from packson.JsonTypeInfo import Id,As, getTypeWrappingInfo, getClassId
2import importlib
3from packson.JsonSubTypes import getSubTypes
4from typing import Dict, _GenericAlias, cast, Tuple
5import inspect
6from inspect import _empty
7from inspect import Parameter
8'''
9Tools that are working just with a class
10'''
11
12def isPrimitive(clas):
13 '''
14 @return true if clas is primitive: int, float, etc.
15 These are also the class types that can be used for dictionary keys.
16 list can't be primitive because we must check the type of the list elements and
17 use the appropriate parser for them.
18 '''
19 return clas in (int, float, bool, str, complex, range, bytes, bytearray)
20
21def getInitArgs( clas) -> Dict[str, object]:
22 '''
23 @param clas a class object that can be instantiated
24 @return dict with all arguments of clas __init__ and
25 their classes.
26 @throws ValueError if not all args are annotated with a type.
27 '''
28 if not hasattr(clas, '__init__'):
29 return {}
30 f=getattr(clas,'__init__')
31 argclasses = {name:param._annotation for name,param in inspect.signature(f).parameters.items()}
32 argclasses.pop('self')
33 # _empty is indicates an undefined type in the typing system.
34 untyped = [ name for name,param in argclasses.items() if param==_empty ]
35 #untyped = set(argclasses.keys()) - set(['self'])
36 if len(untyped)>0:
37 raise ValueError("init function of Class "+str(clas)+\
38 " must have all arguments typed, but found untyped "+str(untyped))
39 return argclasses
40
41def str_to_class(fullclasspath:str)->object:
42 '''
43 @param fullclasspath : full path specification to a class,
44 so that we can locate and load it.
45 @return a new class, loaded from the given string.
46 '''
47 x=fullclasspath.rfind(".")
48 module_name=""
49 if x>0:
50 module_name=fullclasspath[0:x]
51 # load the module, will raise ImportError if module cannot be loaded
52 m = importlib.import_module(module_name)
53 # get the class, will raise AttributeError if class cannot be found
54 c = getattr(m, fullclasspath[x+1:])
55 return c
56
57
58def id2class(classid:str, realclasses:list):
59 '''
60 @param classid the class id , coming from the json
61 @param use the Id
62 @param realclasses the list of real classes that are allowed
63 @return the real class that has the requested id
64 '''
65 for clas in realclasses:
66 if classid==getClassId(clas):
67 return clas
68 raise ValueError("There is no class with id "+ classid+" in " + str(realclasses) )
69
70def addTypeInfo( clas, jdict:dict)->dict:
71 '''
72 @param clas the class of the object to be serialized
73 @jdict an already json-ized object dict, but without type wrapper
74 @return res, but with type info added
75 '''
76 (use, include) = getTypeWrappingInfo(clas)
77 if use == Id.NONE:
78 return jdict
79 classid = getClassId(clas)
80
81 if include == As.WRAPPER_OBJECT:
82 return { classid:jdict }
83 else:
84 raise ValueError("Not implemented include type "+str(include))
85
86
87
88
89def getActualClass(data:dict,clas)->tuple:
90 '''
91 @param data the json dict to be deserialized. It should contain the
92 class type info.
93 @param clas the expected class. This class IS annotated with jsonsubtypes but
94 clas may differ from the originally annotated class (it may just have inherited the annotation)
95 @return tuple (actualdata,actualclass). with the actualclass contained in the data. It must be one
96 of the classes in the __jsonsubtypes__ of class. actualdata is stripped of the type data contained
97 in the original data dict. ASSUMES typewrappinginfo is set
98 @throws if typewrapping info is not set.
99 '''
100 (use, include) = getTypeWrappingInfo(clas)
101
102 if getSubTypes(clas):
103 (_annotatedclass, subclasslist) = getSubTypes(clas)
104 # we could not load the real classes earlier
105 # because they did not yet exist at parse time.
106 realclasses=[str_to_class(classname) for classname in subclasslist]
107 realclasses.append(clas)
108 else:
109 # class has no subtypes annotation, it can be only the indicated class.
110 realclasses=[clas]
111 '''
112 Algorithm: this ignores annotatedclass.
113 We first try to find which of the subclasslist is actually in the data.
114 Then we just check if that is subclass of clas.
115 '''
116 if include==As.WRAPPER_OBJECT:
117 if len(data.keys())!=1:
118 raise ValueError("WRAPPER_OBJECT requies 1 key (class id) but found "+str(data.keys()))
119 classid = next(iter(data.keys()))
120 actualdata = data[classid]
121 else:
122 raise ValueError("Not implemented: deserialization with include "+str(include))
123
124 # find back the matching full classname
125 actualclas = id2class(classid, realclasses)
126
127 # We found a class that matches the header.
128 # but the clas requested might be more restrictive as we may be in a subclass
129 #FIXME can we do this test once, somewhere, for all of the realclasses?
130 if not issubclass(actualclas, clas):
131 raise ValueError("The class ID ("+str(actualclas)+" is not implementing the requested class "+str(clas))
132 return (actualdata, actualclas)
133
134def getListClass(listofobj:set)->object:
135 '''
136 @param listofobj a list/set/tuple with at least 1 object
137 @return class of the list elements. All elements must be same class
138 @throws ValueError if that is not the case
139 '''
140 if len(listofobj)==0:
141 raise ValueError("bug, getListClass called with empty list")
142 clas=type(next(iter(listofobj))) # object may be a list, dict, set
143 for obj in listofobj:
144 if not type(obj)==clas:
145 raise ValueError("Expected element of type "+str(clas)+"but found "+str(obj))
146 return clas
147
Note: See TracBrowser for help on using the repository browser.