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 shallow_clone(self):
79 ''' Makes a shallow copy '''
80 reg = self.get_registry()
81 obj = new_object_from_hint(reg.hint)
82 for field in reg.keys:
83 obj.set_field(field, self.get_field(field))
88 def new_object_from_hint(hint):
89 ''' Given a hint, this will create a new object of that
90 type and return it. If this hint is not registered,
91 an object of type NetworkObject.__unknown is returned'''
94 exec('obj = NetworkObject.%s()' % hint)
96 except AttributeError:
97 return NetworkObject.__unknown()
99 def __make_network_accessor(cls, key):
100 ''' Creates and accessor/mutator method for the given class.
101 'key' is the name the method will have and represents
102 the field on the object whose data we are accessing '''
103 def accessor(self, *args):
105 self.set_field(key, args[0])
106 return self.get_field(key)
107 setattr(cls, key, accessor)
110 def register_hint(hint, keys, type='hash'):
111 ''' Registers a new network-serializable object class.
113 'hint' is the class hint
114 'keys' is the list of field names on the object
115 If this is an array-based object, the field names
116 must be sorted to reflect the encoding order of the fields
117 'type' is the wire-protocol of the object. hash or array.
120 # register the class with the global registry
121 registry = NetworkRegistry(hint, keys, type)
123 # create the new class locally with the given hint name
124 exec('class %s(NetworkObject):\n\tpass' % hint)
126 # give the new registered class a local handle
128 exec('cls = %s' % hint)
130 # assign an accessor/mutator for each field on the object
132 __make_network_accessor(cls, k)
134 # attach our new class to the NetworkObject
135 # class so others can access it
136 setattr(NetworkObject, hint , cls)
137 cls.registry = registry
142 # create a unknown object to handle unregistred types
143 register_hint('__unknown', [], 'hash')
145 # -------------------------------------------------------------------
146 # Define the custom object parsing behavior
147 # -------------------------------------------------------------------
148 def parse_net_object(obj):
150 if isinstance(obj, dict):
151 if OSRF_JSON_CLASS_KEY in obj and OSRF_JSON_PAYLOAD_KEY in obj:
153 hint = obj[OSRF_JSON_CLASS_KEY]
154 sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
155 reg = NetworkRegistry.get_registry(hint)
161 if reg.protocol == 'array':
162 for entry in range(len(reg.keys)):
163 if len(sub_object) > entry:
164 obj[reg.keys[entry]] = parse_net_object(sub_object[entry])
166 obj[reg.keys[entry]] = None
169 obj[key] = parse_net_object(sub_object.get(key))
171 # vivicate the network object
172 estr = 'obj = NetworkObject.%s(obj)' % hint
176 # dict, but not a registered NetworObject
177 for key, value in obj.iteritems():
178 obj[key] = parse_net_object(value)
180 elif isinstance(obj, list):
181 for idx, value in enumerate(obj):
182 obj[idx] = 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):