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