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 # -----------------------------------------------------------
10 # Global object registry
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).
19 def __init__(self, hint, keys, protocol):
20 global OBJECT_REGISTRY
23 self.protocol = protocol
24 OBJECT_REGISTRY[hint] = self
26 def get_registry(hint):
27 global OBJECT_REGISTRY
28 return OBJECT_REGISTRY.get(hint)
30 get_registry = staticmethod(get_registry)
33 # -----------------------------------------------------------
34 # Define the base class for all network-serializable objects
35 # -----------------------------------------------------------
37 class NetworkObject(object):
38 ''' Base class for all network serializable objects '''
40 # link to our registry object for this registered class
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 '''
48 if not data: self._data = {}
49 if isinstance(data, list):
50 self.import_array_data(list)
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 '''
60 reg = self.get_registry()
61 if reg.protocol == 'array':
62 for entry in range(len(reg.keys)):
65 self.set_field(reg.keys[entry], data[entry])
68 ''' Returns the full dataset for this object as a dict '''
71 def set_field(self, field, value):
72 self._data[field] = value
74 def get_field(self, field):
75 return self._data.get(field)
77 def get_registry(cls):
78 ''' Returns the registry object for this registered class '''
80 get_registry = classmethod(get_registry)
83 def new_object_from_hint(hint):
84 ''' Given a hint, this will create a new object of that
85 type and return it. If this hint is not registered,
86 an object of type NetworkObject.__unknown is returned'''
89 exec('obj = NetworkObject.%s()' % hint)
91 except AttributeError:
92 return NetworkObject.__unknown()
94 def __make_network_accessor(cls, key):
95 ''' Creates and accessor/mutator method for the given class.
96 'key' is the name the method will have and represents
97 the field on the object whose data we are accessing '''
98 def accessor(self, *args):
100 self.set_field(key, args[0])
101 return self.get_field(key)
102 setattr(cls, key, accessor)
105 def register_hint(hint, keys, type='hash'):
106 ''' Registers a new network-serializable object class.
108 'hint' is the class hint
109 'keys' is the list of field names on the object
110 If this is an array-based object, the field names
111 must be sorted to reflect the encoding order of the fields
112 'type' is the wire-protocol of the object. hash or array.
115 # register the class with the global registry
116 registry = NetworkRegistry(hint, keys, type)
118 # create the new class locally with the given hint name
119 exec('class %s(NetworkObject):\n\tpass' % hint)
121 # give the new registered class a local handle
123 exec('cls = %s' % hint)
125 # assign an accessor/mutator for each field on the object
127 __make_network_accessor(cls, k)
129 # attach our new class to the NetworkObject
130 # class so others can access it
131 setattr(NetworkObject, hint , cls)
132 cls.registry = registry
137 # create a unknown object to handle unregistred types
138 register_hint('__unknown', [], 'hash')
140 # -------------------------------------------------------------------
141 # Define the custom object parsing behavior
142 # -------------------------------------------------------------------
143 def parse_net_object(obj):
146 hint = obj[OSRF_JSON_CLASS_KEY]
147 sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
148 reg = NetworkRegistry.get_registry(hint)
152 if reg.protocol == 'array':
153 for entry in range(len(reg.keys)):
154 if len(sub_object) > entry:
155 obj[reg.keys[entry]] = parse_net_object(sub_object[entry])
157 obj[reg.keys[entry]] = None
160 obj[key] = parse_net_object(sub_object.get(key))
162 estr = 'obj = NetworkObject.%s(obj)' % hint
166 # this object has not been registered, shove it into the default container
167 obj = NetworkObject.__unknown(obj)
174 # the current object does not have a class hint
175 if isinstance(obj, list):
176 for entry in range(len(obj)):
177 obj[entry] = parse_net_object(obj[entry])
180 if isinstance(obj, dict):
181 for key, value in obj.iteritems():
182 obj[key] = parse_net_object(value)
188 """ Returns the XML representation of an internal object."""
191 return ''.join(chars)
193 def __to_xml(obj, chars):
194 """ Turns an internal object into OpenSRF XML """
197 chars.append('<null/>')
200 if isinstance(obj, unicode) or isinstance(obj, str):
201 chars.append('<string>%s</string>' % saxutils.escape(obj))
204 if isinstance(obj, int) or isinstance(obj, long):
205 chars.append('<number>%d</number>' % obj)
208 if isinstance(obj, float):
209 chars.append('<number>%f</number>' % obj)
212 if isinstance(obj, NetworkObject):
214 registry = obj.get_registry()
215 data = obj.get_data()
216 hint = saxutils.escape(registry.hint)
218 if registry.protocol == 'array':
219 chars.append("<array class_hint='%s'>" % hint)
220 for key in registry.keys:
221 __to_xml(data.get(key), chars)
222 chars.append('</array>')
225 if registry.protocol == 'hash':
226 chars.append("<object class_hint='%s'>" % hint)
227 for key, value in data.items():
228 chars.append("<element key='%s'>" % saxutils.escape(key))
229 __to_xml(value, chars)
230 chars.append('</element>')
231 chars.append('</object>')
234 if isinstance(obj, list):
235 chars.append('<array>')
237 __to_xml(entry, chars)
238 chars.append('</array>')
241 if isinstance(obj, dict):
242 chars.append('<object>')
243 for key, value in obj.items():
244 chars.append("<element key='%s'>" % saxutils.escape(key))
245 __to_xml(value, chars)
246 chars.append('</element>')
247 chars.append('</object>')
250 if isinstance(obj, bool):
254 chars.append("<boolean value='%s'/>" % val)
257 def find_object_path(obj, path, idx=None):
258 """Searches an object along the given path for a value to return.
260 Path separators can be '/' or '.', '/' is tried first."""
264 if re.search('/', path):
265 parts = path.split('/')
267 parts = path.split('.')
274 if isinstance(val, str):
276 if isinstance(val, list):
280 if isinstance(val, dict):