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 '''
57 if len(data) == 0: return
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])
66 ''' Returns the full dataset for this object as a dict '''
69 def set_field(self, field, value):
70 self._data[field] = value
72 def get_field(self, field):
73 return self._data.get(field)
75 def get_registry(cls):
76 ''' Returns the registry object for this registered class '''
78 get_registry = classmethod(get_registry)
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'''
87 exec('obj = NetworkObject.%s()' % hint)
89 except AttributeError:
90 return NetworkObject.__unknown()
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):
98 self.set_field(key, args[0])
99 return self.get_field(key)
100 setattr(cls, key, accessor)
103 def NetworkRegisterHint(hint, keys, type='hash'):
104 ''' Registers a new network-serializable object class.
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.
113 # register the class with the global registry
114 registry = NetworkRegistry(hint, keys, type)
116 # create the new class locally with the given hint name
117 exec('class %s(NetworkObject):\n\tpass' % hint)
119 # give the new registered class a local handle
121 exec('cls = %s' % hint)
123 # assign an accessor/mutator for each field on the object
125 __makeNetworkAccessor(cls, k)
127 # attach our new class to the NetworkObject
128 # class so others can access it
129 setattr(NetworkObject, hint , cls)
130 cls.registry = registry
135 # create a unknown object to handle unregistred types
136 NetworkRegisterHint('__unknown', [], 'hash')
138 # -------------------------------------------------------------------
139 # Define the custom object parsing behavior
140 # -------------------------------------------------------------------
141 def parse_net_object(obj):
144 hint = obj[OSRF_JSON_CLASS_KEY]
145 sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
146 reg = NetworkRegistry.get_registry(hint)
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])
155 obj[reg.keys[entry]] = None
158 obj[key] = parse_net_object(sub_object.get(key))
160 estr = 'obj = NetworkObject.%s(obj)' % hint
164 # this object has not been registered, shove it into the default container
165 obj = NetworkObject.__unknown(obj)
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])
178 if isinstance(obj, dict):
179 for key, value in obj.iteritems():
180 obj[key] = parse_net_object(value)
186 """ Returns the XML representation of an internal object."""
189 return ''.join(chars)
191 def __to_xml(obj, chars):
192 """ Turns an internal object into OpenSRF XML """
195 chars.append('<null/>')
198 if isinstance(obj, unicode) or isinstance(obj, str):
199 chars.append('<string>%s</string>' % saxutils.escape(obj))
202 if isinstance(obj, int) or isinstance(obj, long):
203 chars.append('<number>%d</number>' % obj)
206 if isinstance(obj, float):
207 chars.append('<number>%f</number>' % obj)
210 if isinstance(obj, NetworkObject):
212 registry = obj.get_registry()
213 data = obj.get_data()
214 hint = saxutils.escape(registry.hint)
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>')
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>')
232 if isinstance(obj, list):
233 chars.append('<array>')
235 __to_xml(entry, chars)
236 chars.append('</array>')
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>')
248 if isinstance(obj, bool):
252 chars.append("<boolean value='%s'/>" % val)
255 def find_object_path(obj, path, idx=None):
256 """Searches an object along the given path for a value to return.
258 Path separators can be '/' or '.', '/' is tried first."""
262 if re.search('/', path):
263 parts = path.split('/')
265 parts = path.split('.')
272 if isinstance(val, str):
274 if isinstance(val, list):
278 if isinstance(val, dict):