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(list)
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 entry in range(len(reg.keys)):
62 self.set_field(reg.keys[entry], data[entry])
65 ''' Returns the full dataset for this object as a dict '''
68 def set_field(self, field, value):
69 self._data[field] = value
71 def get_field(self, field):
72 return self._data.get(field)
74 def get_registry(self):
75 ''' Returns the registry object for this registered class '''
76 return self.__class__.registry
78 def new_object_from_hint(hint):
79 ''' Given a hint, this will create a new object of that
80 type and return it. If this hint is not registered,
81 an object of type NetworkObject.__unknown is returned'''
84 exec('obj = NetworkObject.%s()' % hint)
86 except AttributeError:
87 return NetworkObject.__unknown()
89 def __make_network_accessor(cls, key):
90 ''' Creates and accessor/mutator method for the given class.
91 'key' is the name the method will have and represents
92 the field on the object whose data we are accessing '''
93 def accessor(self, *args):
95 self.set_field(key, args[0])
96 return self.get_field(key)
97 setattr(cls, key, accessor)
100 def register_hint(hint, keys, type='hash'):
101 ''' Registers a new network-serializable object class.
103 'hint' is the class hint
104 'keys' is the list of field names on the object
105 If this is an array-based object, the field names
106 must be sorted to reflect the encoding order of the fields
107 'type' is the wire-protocol of the object. hash or array.
110 # register the class with the global registry
111 registry = NetworkRegistry(hint, keys, type)
113 # create the new class locally with the given hint name
114 exec('class %s(NetworkObject):\n\tpass' % hint)
116 # give the new registered class a local handle
118 exec('cls = %s' % hint)
120 # assign an accessor/mutator for each field on the object
122 __make_network_accessor(cls, k)
124 # attach our new class to the NetworkObject
125 # class so others can access it
126 setattr(NetworkObject, hint , cls)
127 cls.registry = registry
132 # create a unknown object to handle unregistred types
133 register_hint('__unknown', [], 'hash')
135 # -------------------------------------------------------------------
136 # Define the custom object parsing behavior
137 # -------------------------------------------------------------------
138 def parse_net_object(obj):
141 hint = obj[OSRF_JSON_CLASS_KEY]
142 sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
143 reg = NetworkRegistry.get_registry(hint)
147 if reg.protocol == 'array':
148 for entry in range(len(reg.keys)):
149 if len(sub_object) > entry:
150 obj[reg.keys[entry]] = parse_net_object(sub_object[entry])
152 obj[reg.keys[entry]] = None
155 obj[key] = parse_net_object(sub_object.get(key))
157 estr = 'obj = NetworkObject.%s(obj)' % hint
161 # this object has not been registered, shove it into the default container
162 obj = NetworkObject.__unknown(obj)
169 # the current object does not have a class hint
170 if isinstance(obj, list):
171 for entry in range(len(obj)):
172 obj[entry] = parse_net_object(obj[entry])
175 if isinstance(obj, dict):
176 for key, value in obj.iteritems():
177 obj[key] = parse_net_object(value)
183 """ Returns the XML representation of an internal object."""
186 return ''.join(chars)
188 def __to_xml(obj, chars):
189 """ Turns an internal object into OpenSRF XML """
192 chars.append('<null/>')
195 if isinstance(obj, unicode) or isinstance(obj, str):
196 chars.append('<string>%s</string>' % saxutils.escape(obj))
199 if isinstance(obj, int) or isinstance(obj, long):
200 chars.append('<number>%d</number>' % obj)
203 if isinstance(obj, float):
204 chars.append('<number>%f</number>' % obj)
207 if isinstance(obj, NetworkObject):
209 registry = obj.get_registry()
210 data = obj.get_data()
211 hint = saxutils.escape(registry.hint)
213 if registry.protocol == 'array':
214 chars.append("<array class_hint='%s'>" % hint)
215 for key in registry.keys:
216 __to_xml(data.get(key), chars)
217 chars.append('</array>')
220 if registry.protocol == 'hash':
221 chars.append("<object class_hint='%s'>" % hint)
222 for key, value in data.items():
223 chars.append("<element key='%s'>" % saxutils.escape(key))
224 __to_xml(value, chars)
225 chars.append('</element>')
226 chars.append('</object>')
229 if isinstance(obj, list):
230 chars.append('<array>')
232 __to_xml(entry, chars)
233 chars.append('</array>')
236 if isinstance(obj, dict):
237 chars.append('<object>')
238 for key, value in obj.items():
239 chars.append("<element key='%s'>" % saxutils.escape(key))
240 __to_xml(value, chars)
241 chars.append('</element>')
242 chars.append('</object>')
245 if isinstance(obj, bool):
249 chars.append("<boolean value='%s'/>" % val)
252 def find_object_path(obj, path, idx=None):
253 """Searches an object along the given path for a value to return.
255 Path separators can be '/' or '.', '/' is tried first."""
259 if re.search('/', path):
260 parts = path.split('/')
262 parts = path.split('.')
269 if isinstance(val, str):
271 if isinstance(val, list):
275 if isinstance(val, dict):