]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/python/osrf/net_obj.py
python only evaluates default function param values once, so mutable types will alway...
[OpenSRF.git] / src / python / osrf / net_obj.py
1 from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
2 from xml.sax import saxutils
3
4
5 class osrfNetworkObject(object):
6     ''' Base class for all network serializable objects '''
7     pass
8
9 def osrfNewObjectFromHint(hint):
10     try:
11         obj = None
12         exec('obj = osrfNetworkObject.%s()' % hint)
13         return obj
14     except AttributeError:
15         return osrfNetworkObject.__unknown()
16
17
18 ''' Global object registry '''
19 objectRegistry = {}
20
21 class osrfNetworkRegistry(object):
22     ''' Network-serializable objects must be registered.  The class
23         hint maps to a set (ordered in the case of array-base objects)
24         of field names (keys).  
25         '''
26
27     def __init__(self, hint, keys, wireProtocol):
28         global objectRegistry
29         self.hint = hint
30         self.keys = keys
31         self.wireProtocol = wireProtocol
32         objectRegistry[hint] = self
33     
34     def getRegistry(hint):
35         global objectRegistry
36         return objectRegistry.get(hint)
37     getRegistry = staticmethod(getRegistry)
38
39
40 def __makeNetworkAccessor(cls, key):
41     '''  Creates and accessor/mutator method for the given class.  
42
43         'key' is the name the method will have and represents
44         the field on the object whose data we are accessing
45         ''' 
46     def accessor(self, *args):
47         if len(args) != 0:
48             self.__data[key] = args[0]
49         return self.__data.get(key)
50     setattr(cls, key, accessor)
51
52
53
54 def __makeGetRegistry(cls, registry):
55     ''' Wraps the registry for this class inside an accessor method '''
56     def get(self):
57         return registry
58     setattr(cls, 'getRegistry', get)
59
60 def __makeGetData(cls):
61     ''' Wraps the stored data in an accessor method '''
62     def get(self):
63         return self.__data
64     setattr(cls, 'getData', get)
65
66 def __makeSetField(cls):
67     ''' Creates a generic mutator for fields by fieldname '''
68     def set(self, field, value):
69         self.__data[field] = value
70     setattr(cls, 'setField', set)
71         
72
73 def __osrfNetworkObjectInit(self, data=None):
74     ''' __init__ method for osrNetworkObjects.
75         If this is an array, we pull data out of the data array
76         (if there is any) and translate that into a hash internally
77         '''
78     self.__data = data
79     if not data: self.__data = {}
80
81     if isinstance(data, list):
82         self.__data = {}
83         if len(data) > 0:
84             reg = self.getRegistry()
85             if reg.wireProtocol == 'array':
86                 for i in range(len(reg.keys)):
87                     try:
88                         self.__data[reg.keys[i]] = data[i]
89                     except:
90                         self.__data[reg.keys[i]] = None
91
92
93 def osrfNetworkRegisterHint(hint, keys, type='hash'):
94     ''' Registers a new network-serializable object class.
95
96         'hint' is the class hint
97         'keys' is the list of field names on the object
98             If this is an array-based object, the field names
99             must be sorted to reflect the encoding order of the fields
100         'type' is the wire-protocol of the object.  hash or array.
101         '''
102
103     # register the class with the global registry
104     registry = osrfNetworkRegistry(hint, keys, type)
105
106     # create the new class locally with the given hint name
107     exec('class %s(osrfNetworkObject):\n\tpass' % hint)
108
109     # give the new registered class a local handle
110     cls = None
111     exec('cls = %s' % hint)
112
113     # assign an accessor/mutator for each field on the object
114     for k in keys:
115         __makeNetworkAccessor(cls, k)
116
117     # assign our custom init function
118     setattr(cls, '__init__', __osrfNetworkObjectInit)
119     __makeGetRegistry(cls, registry)
120     __makeGetData(cls)
121     __makeSetField(cls)
122
123
124     # attach our new class to the osrfNetworkObject 
125     # class so others can access it
126     setattr(osrfNetworkObject, hint , cls)
127
128
129
130
131 # create a unknown object to handle unregistred types
132 osrfNetworkRegisterHint('__unknown', [], 'hash')
133
134 # -------------------------------------------------------------------
135 # Define the custom object parsing behavior 
136 # -------------------------------------------------------------------
137 def parseNetObject(obj):
138     
139     try:
140
141         hint = obj[OSRF_JSON_CLASS_KEY]
142         subObj = obj[OSRF_JSON_PAYLOAD_KEY]
143         reg = osrfNetworkRegistry.getRegistry(hint)
144
145         obj = {}
146
147         if reg.wireProtocol == 'array':
148             for i in range(len(reg.keys)):
149                 if len(subObj) > i:
150                     obj[reg.keys[i]] = parseNetObject(subObj[i])
151                 else:
152                     obj[reg.keys[i]] = None
153         else:
154             for k in reg.keys:
155                 obj[k] = parseNetObject(subObj.get(k))
156
157         estr = 'obj = osrfNetworkObject.%s(obj)' % hint
158         try:
159             exec(estr)
160         except e:
161             # this object has not been registered, shove it into the default container
162             obj = osrfNetworkObject.__unknown(obj)
163
164         return obj
165
166     except: pass
167
168     # the current object does not have a class hint
169     if isinstance(obj, list):
170         for i in range(len(obj)):
171             obj[i] = parseNetObject(obj[i])
172
173     else:
174         if isinstance(obj, dict):
175             for k,v in obj.iteritems():
176                 obj[k] = parseNetObject(v)
177
178     return obj;
179
180
181 def osrfObjectToXML(obj):
182     """ Returns the XML representation of an internal object."""
183     chars = []
184     __osrfObjectToXML(obj, chars)
185     return ''.join(chars)
186
187 def __osrfObjectToXML(obj, chars):
188     """ Turns an internal object into OpenSRF XML """
189
190     if obj is None:
191         chars.append('<null/>')
192         return
193
194     if isinstance(obj, unicode) or isinstance(obj, str):
195         chars.append('<string>%s</string>' % saxutils.escape(obj))
196         return
197
198     if isinstance(obj, int)  or isinstance(obj, long):
199         chars.append('<number>%d</number>' % obj)
200         return
201
202     if isinstance(obj, float):
203         chars.append('<number>%f</number>' % obj)
204         return
205
206     classHint = None
207
208     if isinstance(obj, osrfNetworkObject): 
209
210         registry = obj.getRegistry()
211         data = obj.getData()
212         hint = saxutils.escape(registry.hint)
213
214         if registry.wireProtocol == 'array':
215             chars.append("<array class_hint='%s'>" % hint)
216             for k in registry.keys:
217                 __osrfObjectToXML(data.get(k), chars)
218             chars.append('</array>')
219
220         else:
221             if registry.wireProtocol == 'hash':
222                 chars.append("<object class_hint='%s'>" % hint)
223                 for k,v in data.items():
224                     chars.append("<element key='%s'>" % saxutils.escape(k))
225                     __osrfObjectToXML(v, chars)
226                     chars.append('</element>')
227                 chars.append('</object>')
228                 
229
230     if isinstance(obj, list):
231         chars.append('<array>')
232         for i in obj:
233             __osrfObjectToXML(i, chars)
234         chars.append('</array>')
235         return
236
237     if isinstance(obj, dict):
238         chars.append('<object>')
239         for k,v in obj.items():
240             chars.append("<element key='%s'>" % saxutils.escape(k))
241             __osrfObjectToXML(v, chars)
242             chars.append('</element>')
243         chars.append('</object>')
244         return
245
246     if isinstance(obj, bool):
247         val = 'false'
248         if obj: val = 'true'
249         chars.append("<boolean value='%s'/>" % val)
250         return
251