1 from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
3 from xml.sax import saxutils
6 # -----------------------------------------------------------
7 # Define the global network-class registry
8 # -----------------------------------------------------------
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).
17 # Global object registry
20 def __init__(self, hint, keys, protocol):
23 self.protocol = protocol
24 NetworkRegistry.registry[hint] = self
27 def get_registry(hint):
28 return NetworkRegistry.registry.get(hint)
30 # -----------------------------------------------------------
31 # Define the base class for all network-serializable objects
32 # -----------------------------------------------------------
34 class NetworkObject(object):
35 ''' Base class for all network serializable objects '''
37 # link to our registry object for this registered class
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 '''
45 if not data: self._data = {}
46 if isinstance(data, list):
47 self.import_array_data(data)
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 '''
57 reg = self.get_registry()
58 if reg.protocol == 'array':
59 for idx, key in enumerate(reg.keys):
60 self._data[key] = data[idx]
63 ''' Returns the full dataset for this object as a dict '''
66 def set_field(self, field, value):
67 self._data[field] = value
69 def get_field(self, field):
70 return self._data.get(field)
72 def get_registry(self):
73 ''' Returns the registry object for this registered class '''
74 return self.__class__.registry
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]
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'''
92 exec('obj = NetworkObject.%s()' % hint)
94 except AttributeError:
95 return NetworkObject.__unknown()
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):
103 self._data[key] = args[0]
104 return self._data[key]
105 setattr(cls, key, accessor)
108 def register_hint(hint, keys, type='hash'):
109 ''' Registers a new network-serializable object class.
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.
118 # register the class with the global registry
119 registry = NetworkRegistry(hint, keys, type)
121 # create the new class locally with the given hint name
122 exec('class %s(NetworkObject):\n\tpass' % hint)
124 # give the new registered class a local handle
126 exec('cls = %s' % hint)
128 # assign an accessor/mutator for each field on the object
130 __make_network_accessor(cls, k)
132 # attach our new class to the NetworkObject
133 # class so others can access it
134 setattr(NetworkObject, hint , cls)
135 cls.registry = registry
140 # create a unknown object to handle unregistred types
141 register_hint('__unknown', [], 'hash')
143 # -------------------------------------------------------------------
144 # Define the custom object parsing behavior
145 # -------------------------------------------------------------------
146 def parse_net_object(obj):
148 if isinstance(obj, dict):
149 if OSRF_JSON_CLASS_KEY in obj and OSRF_JSON_PAYLOAD_KEY in obj:
151 hint = obj[OSRF_JSON_CLASS_KEY]
152 sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
153 reg = NetworkRegistry.get_registry(hint)
159 if reg.protocol == 'array':
160 subobj_len = len(sub_object)
162 for idx, key in enumerate(reg.keys):
164 # don't attempt acces past the end of the data list
165 obj[key] = parse_net_object(sub_object[idx])
167 # make sure all keys are accounted for, even if there
168 # is no data for the key in the parsed object
172 obj[key] = parse_net_object(sub_object.get(key))
174 # vivicate the network object
175 estr = 'obj = NetworkObject.%s(obj)' % hint
179 # dict, but not a registered NetworkObject
180 for key, value in obj.iteritems():
181 obj[key] = parse_net_object(value)
183 elif isinstance(obj, list):
184 for idx, value in enumerate(obj):
185 obj[idx] = parse_net_object(value)
191 """ Returns the XML representation of an internal object."""
194 return ''.join(chars)
196 def __to_xml(obj, chars):
197 """ Turns an internal object into OpenSRF XML """
200 chars.append('<null/>')
203 if isinstance(obj, unicode) or isinstance(obj, str):
204 chars.append('<string>%s</string>' % saxutils.escape(obj))
207 if isinstance(obj, int) or isinstance(obj, long):
208 chars.append('<number>%d</number>' % obj)
211 if isinstance(obj, float):
212 chars.append('<number>%f</number>' % obj)
215 if isinstance(obj, NetworkObject):
217 registry = obj.get_registry()
218 data = obj.get_data()
219 hint = saxutils.escape(registry.hint)
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>')
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>')
237 if isinstance(obj, list):
238 chars.append('<array>')
240 __to_xml(entry, chars)
241 chars.append('</array>')
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>')
253 if isinstance(obj, bool):
257 chars.append("<boolean value='%s'/>" % val)
260 def find_object_path(obj, path, idx=None):
261 """Searches an object along the given path for a value to return.
263 Path separators can be '/' or '.', '/' is tried first."""
267 if re.search('/', path):
268 parts = path.split('/')
270 parts = path.split('.')
277 if isinstance(val, str):
279 if isinstance(val, list):
283 if isinstance(val, dict):