]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/python/osrf/net_obj.py
LP#1729610: return new OpenSRF status if backlog queue fills up
[OpenSRF.git] / src / python / osrf / net_obj.py
1 from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
2 import re
3 from xml.sax import saxutils
4
5
6 # -----------------------------------------------------------
7 # Define the global network-class registry
8 # -----------------------------------------------------------
9
10
11 class NetworkRegistry(object):
12     ''' Network-serializable objects must be registered.  The class
13         hint maps to a set (ordered in the case of array-base objects)
14         of field names (keys).  
15         '''
16
17     # Global object registry 
18     registry = {}
19
20     def __init__(self, hint, keys, protocol):
21         self.hint = hint
22         self.keys = keys
23         self.protocol = protocol
24         NetworkRegistry.registry[hint] = self
25     
26     @staticmethod
27     def get_registry(hint):
28         return NetworkRegistry.registry.get(hint)
29
30 # -----------------------------------------------------------
31 # Define the base class for all network-serializable objects
32 # -----------------------------------------------------------
33
34 class NetworkObject(object):
35     ''' Base class for all network serializable objects '''
36
37     # link to our registry object for this registered class
38     registry = None
39
40     def __init__(self, data=None):
41         ''' If this is an array, we pull data out of the data array
42             (if there is any) and translate that into a hash internally '''
43
44         self._data = data
45         if not data: self._data = {}
46         if isinstance(data, list):
47             self.import_array_data(data)
48
49     def import_array_data(self, data):
50         ''' If an array-based object is created with an array
51             of data, cycle through and load the data '''
52
53         self._data = {}
54         if len(data) == 0:
55             return
56
57         reg = self.get_registry()
58         if reg.protocol == 'array':
59             for idx, key in enumerate(reg.keys):
60                 self._data[key] = data[idx]
61
62     def get_data(self):
63         ''' Returns the full dataset for this object as a dict '''
64         return self._data
65
66     def set_field(self, field, value):
67         self._data[field] = value
68
69     def get_field(self, field):
70         return self._data.get(field)
71
72     def get_registry(self):
73         ''' Returns the registry object for this registered class '''
74         return self.__class__.registry
75
76     def shallow_clone(self):
77         ''' Makes a shallow copy '''
78         reg = self.get_registry()
79         obj = new_object_from_hint(reg.hint)
80         for field in reg.keys:
81             obj._data[field] = self._data[field]
82         return obj
83             
84
85
86 def new_object_from_hint(hint):
87     ''' Given a hint, this will create a new object of that 
88         type and return it.  If this hint is not registered,
89         an object of type NetworkObject.__unknown is returned'''
90     try:
91         obj = None
92         exec('obj = NetworkObject.%s()' % hint)
93         return obj
94     except AttributeError:
95         return NetworkObject.__unknown()
96
97 def __make_network_accessor(cls, key):
98     ''' Creates and accessor/mutator method for the given class.  
99         'key' is the name the method will have and represents
100         the field on the object whose data we are accessing ''' 
101     def accessor(self, *args):
102         if len(args) != 0:
103             self._data[key] = args[0]
104         return self._data[key]
105     setattr(cls, key, accessor)
106
107
108 def register_hint(hint, keys, type='hash'):
109     ''' Registers a new network-serializable object class.
110
111         'hint' is the class hint
112         'keys' is the list of field names on the object
113             If this is an array-based object, the field names
114             must be sorted to reflect the encoding order of the fields
115         'type' is the wire-protocol of the object.  hash or array.
116         '''
117
118     # register the class with the global registry
119     registry = NetworkRegistry(hint, keys, type)
120
121     # create the new class locally with the given hint name
122     exec('class %s(NetworkObject):\n\tpass' % hint)
123
124     # give the new registered class a local handle
125     cls = None
126     exec('cls = %s' % hint)
127
128     # assign an accessor/mutator for each field on the object
129     for k in keys:
130         __make_network_accessor(cls, k)
131
132     # attach our new class to the NetworkObject 
133     # class so others can access it
134     setattr(NetworkObject, hint , cls)
135     cls.registry = registry
136
137
138
139
140 # create a unknown object to handle unregistred types
141 register_hint('__unknown', [], 'hash')
142
143 # -------------------------------------------------------------------
144 # Define the custom object parsing behavior 
145 # -------------------------------------------------------------------
146 def parse_net_object(obj):
147
148     if isinstance(obj, dict):
149         if OSRF_JSON_CLASS_KEY in obj and OSRF_JSON_PAYLOAD_KEY in obj:
150
151             hint = obj[OSRF_JSON_CLASS_KEY]
152             sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
153             reg = NetworkRegistry.get_registry(hint)
154             
155             if reg:
156
157                 obj = {}
158
159                 if reg.protocol == 'array':
160                     subobj_len = len(sub_object)
161
162                     for idx, key in enumerate(reg.keys):
163                         if idx < subobj_len: 
164                             # don't attempt acces past the end of the data list
165                             obj[key] = parse_net_object(sub_object[idx])
166                         else:
167                             # make sure all keys are accounted for, even if there 
168                             # is no data for the key in the parsed object
169                             obj[key] = None 
170                 else:
171                     for key in reg.keys:
172                         obj[key] = parse_net_object(sub_object.get(key))
173
174                 # vivicate the network object
175                 estr = 'obj = NetworkObject.%s(obj)' % hint
176                 exec(estr) 
177                 return obj
178
179         # dict, but not a registered NetworkObject
180         for key, value in obj.iteritems():
181             obj[key] = parse_net_object(value)
182
183     elif isinstance(obj, list):
184         for idx, value in enumerate(obj):
185             obj[idx] = parse_net_object(value)
186
187     return obj
188
189
190 def to_xml(obj):
191     """ Returns the XML representation of an internal object."""
192     chars = []
193     __to_xml(obj, chars)
194     return ''.join(chars)
195
196 def __to_xml(obj, chars):
197     """ Turns an internal object into OpenSRF XML """
198
199     if obj is None:
200         chars.append('<null/>')
201         return
202
203     if isinstance(obj, unicode) or isinstance(obj, str):
204         chars.append('<string>%s</string>' % saxutils.escape(obj))
205         return
206
207     if isinstance(obj, int)  or isinstance(obj, long):
208         chars.append('<number>%d</number>' % obj)
209         return
210
211     if isinstance(obj, float):
212         chars.append('<number>%f</number>' % obj)
213         return
214
215     if isinstance(obj, NetworkObject): 
216
217         registry = obj.get_registry()
218         data = obj.get_data()
219         hint = saxutils.escape(registry.hint)
220
221         if registry.protocol == 'array':
222             chars.append("<array class_hint='%s'>" % hint)
223             for key in registry.keys:
224                 __to_xml(data.get(key), chars)
225             chars.append('</array>')
226
227         else:
228             if registry.protocol == 'hash':
229                 chars.append("<object class_hint='%s'>" % hint)
230                 for key, value in data.items():
231                     chars.append("<element key='%s'>" % saxutils.escape(key))
232                     __to_xml(value, chars)
233                     chars.append('</element>')
234                 chars.append('</object>')
235                 
236
237     if isinstance(obj, list):
238         chars.append('<array>')
239         for entry in obj:
240             __to_xml(entry, chars)
241         chars.append('</array>')
242         return
243
244     if isinstance(obj, dict):
245         chars.append('<object>')
246         for key, value in obj.items():
247             chars.append("<element key='%s'>" % saxutils.escape(key))
248             __to_xml(value, chars)
249             chars.append('</element>')
250         chars.append('</object>')
251         return
252
253     if isinstance(obj, bool):
254         val = 'false'
255         if obj:
256             val = 'true'
257         chars.append("<boolean value='%s'/>" % val)
258         return
259
260 def find_object_path(obj, path, idx=None):
261     """Searches an object along the given path for a value to return.
262
263     Path separators can be '/' or '.', '/' is tried first."""
264
265     parts = []
266
267     if re.search('/', path):
268         parts = path.split('/')
269     else:
270         parts = path.split('.')
271
272     for part in parts:
273         try:
274             val = obj[part]
275         except:
276             return None
277         if isinstance(val, str): 
278             return val
279         if isinstance(val, list):
280             if idx != None:
281                 return val[idx]
282             return val
283         if isinstance(val, dict):
284             obj = val
285         else:
286             return val
287
288     return obj