Cleaned up the network hint / object registration code in net_obj
authorerickson <erickson@9efc2488-bf62-4759-914b-345cdb29e865>
Fri, 15 Jun 2007 03:18:51 +0000 (03:18 +0000)
committererickson <erickson@9efc2488-bf62-4759-914b-345cdb29e865>
Fri, 15 Jun 2007 03:18:51 +0000 (03:18 +0000)
Added object2XML function for posting XML to the opensrf gateway
updated the gateway to deal with the new object API

git-svn-id: svn://svn.open-ils.org/OpenSRF/trunk@949 9efc2488-bf62-4759-914b-345cdb29e865

src/python/osrf/const.py
src/python/osrf/gateway.py
src/python/osrf/json.py
src/python/osrf/net_obj.py

index eacf479..2264fce 100644 (file)
@@ -71,4 +71,7 @@ OSRF_APP_MATH = 'opensrf.math'
 # where do we find the settings config
 OSRF_METHOD_GET_HOST_CONFIG = 'opensrf.settings.host_config.get'
 
+OSRF_JSON_PAYLOAD_KEY = '__p'
+OSRF_JSON_CLASS_KEY = '__c'
+
 
index 3a6efaf..96d03ad 100644 (file)
@@ -2,9 +2,10 @@ from xml.dom import minidom
 from xml.sax import handler, make_parser, saxutils
 from json import *
 from net_obj import *
-import urllib, urllib2, sys
+import urllib, urllib2, sys, re
 
 defaultHost = None
+paramRegex = re.compile('\%27')
 
 class GatewayRequest:
     def __init__(self, service, method, params=[]):
@@ -30,12 +31,14 @@ class GatewayRequest:
         params = urllib.urlencode({   
             'service': self.service,
             'method': self.method,
-            'format': self.getFormat()
+            'format': self.getFormat(),
+            'input_format': self.getInputFormat()
         })
 
         for p in self.params:
-            param = {'param': osrfObjectToJSON(p)}
-            params += '&%s' % urllib.urlencode(param)
+            # XXX for some reason, the gateway does not like escaped single-quotes ?
+            param = paramRegex.sub("'", urllib.quote(self.encodeParam(p)))
+            params += '&param=%s' % urllib.quote(self.encodeParam(param))
 
         return params
 
@@ -56,6 +59,9 @@ class XMLGatewayRequest(GatewayRequest):
     def getFormat(self):
         return 'xml'
 
+    def getInputFormat(self):
+        return self.getFormat()
+
     def handleResponse(self, response):
         handler = XMLGatewayParser()
         parser = make_parser()
@@ -63,6 +69,9 @@ class XMLGatewayRequest(GatewayRequest):
         parser.parse(response)
         return handler.getResult()
 
+    def encodeParam(self, param):
+        return osrfObjectToXML(param);
+
 class XMLGatewayParser(handler.ContentHandler):
 
     def __init__(self):
@@ -83,15 +92,17 @@ class XMLGatewayParser(handler.ContentHandler):
 
         # XXX add support for serializable objects!
 
+        if name == 'null':
+            self.appendChild(None)
+            return
+
         if name == 'element': # this is an object item wrapper
             self.keyStack.append(self.__getAttr(attrs, 'key'))
             return
 
-        if name == 'object':
-            obj = {}
-            self.appendChild(obj)
-            self.objStack.append(obj)
-            return
+        hint = self.__getAttr(attrs, 'class_hint')
+        if hint:
+            obj = osrfNetworkObject.newFromHint(hint)
 
         if name == 'array':
             obj = []
@@ -99,8 +110,10 @@ class XMLGatewayParser(handler.ContentHandler):
             self.objStack.append(obj)
             return
 
-        if name == 'null':
-            self.appendChild(None)
+        if name == 'object':
+            obj = {}
+            self.appendChild(obj)
+            self.objStack.append(obj)
             return
 
         if name == 'boolean':
index 1b73e37..b6b3154 100644 (file)
-# -----------------------------------------------------------------------
-# Copyright (C) 2007  Georgia Public Library Service
-# Bill Erickson <billserickson@gmail.com>
-# 
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-# 
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# -----------------------------------------------------------------------
-
-
 import simplejson, types 
 from osrf.net_obj import *
-
-JSON_PAYLOAD_KEY = '__p'
-JSON_CLASS_KEY = '__c'
-
-#class osrfNetworkObject(object):
-#      """Base class for serializable network objects."""
-#      def getData(self):
-#              """Returns a dict of data contained by this object"""
-#              return self.data
-#
-#
-#class __unknown(osrfNetworkObject):
-#      """Default class for un-registered network objects."""
-#      def __init__(self, data=None):
-#              self.data = data
-#
-#setattr(__unknown,'__keys', [])
-#setattr(osrfNetworkObject,'__unknown', __unknown)
-#
-#
-#def osrfNetworkRegisterHint(hint, keys, type='hash'):
-#      """Register a network hint.  
-#      
-#              This creates a new class at osrfNetworkObject.<hint> with 
-#              methods for accessing/mutating the object's data.  
-#              Method names will match the names found in the keys array
-#
-#              hint - The hint name to encode with the object
-#              type - The data container type.  
-#              keys - An array of data keys.  If type is an 'array', the order of
-#              the keys will determine how the data is accessed
-#      """
-#
-#      estr = "class %s(osrfNetworkObject):\n" % hint
-#      estr += "\tdef __init__(self, data=None):\n"
-#      estr += "\t\tself.data = data\n"
-#      estr += "\t\tif data:\n"
-#
-#      if type == 'hash': 
-#              estr += "\t\t\tpass\n"
-#      else:
-#              # we have to make sure the array is large enough        
-#              estr += "\t\t\twhile len(data) < %d:\n" % len(keys)
-#              estr += "\t\t\t\tdata.append(None)\n"
-#
-#      estr += "\t\telse:\n"
-#
-#      if type == 'array':
-#              estr += "\t\t\tself.data = []\n"
-#              estr += "\t\t\tfor i in range(%s):\n" % len(keys)
-#              estr += "\t\t\t\tself.data.append(None)\n"
-#              for i in range(len(keys)):
-#                      estr += "\tdef %s(self, *args):\n"\
-#                                              "\t\tif len(args) != 0:\n"\
-#                                              "\t\t\tself.data[%s] = args[0]\n"\
-#                                              "\t\treturn self.data[%s]\n" % (keys[i], i, i)
-#
-#      if type == 'hash':
-#              estr += "\t\t\tself.data = {}\n"
-#              estr += "\t\t\tfor i in %s:\n" % str(keys)
-#              estr += "\t\t\t\tself.data[i] = None\n"
-#              for i in keys:
-#                      estr += "\tdef %s(self, *args):\n"\
-#                                              "\t\tif len(args) != 0:\n"\
-#                                              "\t\t\tself.data['%s'] = args[0]\n"\
-#                                              "\t\tval = None\n"\
-#                                              "\t\ttry: val = self.data['%s']\n"\
-#                                              "\t\texcept: return None\n"\
-#                                              "\t\treturn val\n" % (i, i, i)
-#
-#      estr += "setattr(osrfNetworkObject, '%s', %s)\n" % (hint,hint)
-#      estr += "setattr(osrfNetworkObject.%s, '__keys', keys)" % hint
-#      exec(estr)
-#      
-#              
-#
-## -------------------------------------------------------------------
-## Define the custom object parsing behavior 
-## -------------------------------------------------------------------
-#def __parseNetObject(obj):
-#      hint = None
-#      islist = False
-#      try:
-#              hint = obj[JSON_CLASS_KEY]
-#              obj = obj[JSON_PAYLOAD_KEY]
-#      except: pass
-#      if isinstance(obj,list):
-#              islist = True
-#              for i in range(len(obj)):
-#                      obj[i] = __parseNetObject(obj[i])
-#      else: 
-#              if isinstance(obj,dict):
-#                      for k,v in obj.iteritems():
-#                              obj[k] = __parseNetObject(v)
-#
-#      if hint: # Now, "bless" the object into an osrfNetworkObject
-#              estr = 'obj = osrfNetworkObject.%s(obj)' % hint
-#              try:
-#                      exec(estr)
-#              except AttributeError:
-#                      # this object has not been registered, shove it into the default container
-#                      obj = osrfNetworkObject.__unknown(obj)
-#
-#      return obj;
-#
-#
-## -------------------------------------------------------------------
-# Define the custom object encoding behavior 
-# -------------------------------------------------------------------
+from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
 
 class osrfJSONNetworkEncoder(simplejson.JSONEncoder):
-       def default(self, obj):
-               if isinstance(obj, osrfNetworkObject):
-                       return { 
-                               JSON_CLASS_KEY: obj.__class__.__name__,
-                               JSON_PAYLOAD_KEY: self.default(obj.getData())
-                       }       
-               return obj
+    def default(self, obj):
+        if isinstance(obj, osrfNetworkObject):
+            return { 
+                OSRF_JSON_CLASS_KEY: obj.getHint(),
+                OSRF_JSON_PAYLOAD_KEY: self.default(obj.getData())
+            }   
+        return obj
 
 
 def osrfObjectToJSON(obj):
-       """Turns a python object into a wrapped JSON object"""
-       return simplejson.dumps(obj, cls=osrfJSONNetworkEncoder)
+    """Turns a python object into a wrapped JSON object"""
+    return simplejson.dumps(obj, cls=osrfJSONNetworkEncoder)
 
 
 def osrfJSONToObject(json):
-       """Turns a JSON string into python objects"""
-       obj = simplejson.loads(json)
-       return parseNetObject(obj)
+    """Turns a JSON string into python objects"""
+    obj = simplejson.loads(json)
+    return parseNetObject(obj)
 
 def osrfParseJSONRaw(json):
-       """Parses JSON the old fashioned way."""
-       return simplejson.loads(json)
+    """Parses JSON the old fashioned way."""
+    return simplejson.loads(json)
 
 def osrfToJSONRaw(obj):
-       """Stringifies an object as JSON with no additional logic."""
-       return simplejson.dumps(obj)
+    """Stringifies an object as JSON with no additional logic."""
+    return simplejson.dumps(obj)
 
 def __tabs(t):
-       r=''
-       for i in range(t): r += '   '
-       return r
+    r=''
+    for i in range(t): r += '   '
+    return r
 
 def osrfDebugNetworkObject(obj, t=1):
-       """Returns a debug string for a given object.
+    """Returns a debug string for a given object.
 
-       If it's an osrfNetworkObject and has registered keys, key/value p
-       pairs are returned.  Otherwise formatted JSON is returned"""
+    If it's an osrfNetworkObject and has registered keys, key/value p
+    pairs are returned.  Otherwise formatted JSON is returned"""
 
-       s = ''
-       if isinstance(obj, osrfNetworkObject) and len(obj.__keys):
-               obj.__keys.sort()
+    s = ''
+    if isinstance(obj, osrfNetworkObject):
+        reg = obj.getRegistry()
+        keys = list(reg.keys) # clone it, so sorting won't break the original
+        keys.sort()
 
-               for k in obj.__keys:
+        for k in keys:
 
-                       key = k
-                       while len(key) < 24: key += '.' # pad the names to make the values line up somewhat
-                       val = getattr(obj, k)()
+            key = k
+            while len(key) < 24: key += '.' # pad the names to make the values line up somewhat
+            val = getattr(obj, k)()
 
-                       subobj = val and not (isinstance(val,unicode) or \
-                                       isinstance(val, int) or isinstance(val, float) or isinstance(val, long))
+            subobj = val and not (isinstance(val,unicode) or \
+                isinstance(val, int) or isinstance(val, float) or isinstance(val, long))
 
+            s += __tabs(t) + key + ' = '
 
-                       s += __tabs(t) + key + ' = '
+            if subobj:
+                s += '\n'
+                val = osrfDebugNetworkObject(val, t+1)
 
-                       if subobj:
-                               s += '\n'
-                               val = osrfDebugNetworkObject(val, t+1)
+            s += str(val)
 
-                       s += str(val)
+            if not subobj: s += '\n'
 
-                       if not subobj: s += '\n'
-
-       else:
-               s = osrfFormatJSON(osrfObjectToJSON(obj))
-       return s
+    else:
+        s = osrfFormatJSON(osrfObjectToJSON(obj))
+    return s
 
 def osrfFormatJSON(json):
-       """JSON pretty-printer"""
-       r = ''
-       t = 0
-       instring = False
-       inescape = False
-       done = False
+    """JSON pretty-printer"""
+    r = ''
+    t = 0
+    instring = False
+    inescape = False
+    done = False
+
+    for c in json:
+
+        done = False
+        if (c == '{' or c == '[') and not instring:
+            t += 1
+            r += c + '\n' + __tabs(t)
+            done = True
+
+        if (c == '}' or c == ']') and not instring:
+            t -= 1
+            r += '\n' + __tabs(t) + c
+            done = True
+
+        if c == ',' and not instring:
+            r += c + '\n' + __tabs(t)
+            done = True
+
+        if c == '"' and not inescape:
+            instring = not instring
 
-       for c in json:
+        if inescape: 
+            inescape = False
 
-               done = False
-               if (c == '{' or c == '[') and not instring:
-                       t += 1
-                       r += c + '\n' + __tabs(t)
-                       done = True
+        if c == '\\':
+            inescape = True
 
-               if (c == '}' or c == ']') and not instring:
-                       t -= 1
-                       r += '\n' + __tabs(t) + c
-                       done = True
+        if not done:
+            r += c
 
-               if c == ',' and not instring:
-                       r += c + '\n' + __tabs(t)
-                       done = True
+    return r
 
-               if c == '"' and not inescape:
-                       instring = not instring
 
-               if inescape: 
-                       inescape = False
 
-               if c == '\\':
-                       inescape = True
 
-               if not done:
-                       r += c
 
-       return r
 
-       
index 1dafa1e..a95dda0 100644 (file)
-# -----------------------------------------------------------------------
-# Copyright (C) 2007  Georgia Public Library Service
-# 
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-# 
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# -----------------------------------------------------------------------
-
-
-JSON_PAYLOAD_KEY = '__p'
-JSON_CLASS_KEY = '__c'
+from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
+from xml.sax import saxutils
+
 
 class osrfNetworkObject(object):
-       """Base class for serializable network objects."""
-       def getData(self):
-               """Returns a dict of data contained by this object"""
-               return self.data
+    ''' Base class for all network serializable objects '''
+    pass
+    def newFromHint(hint):
+        obj = None
+        exec('obj = osrfNetworkObject.%s()' % hint)
+        return obj
+    newFromHint = staticmethod(newFromHint)
+
+
+''' Global object registry '''
+objectRegistry = {}
+
+class osrfNetworkRegistry(object):
+    ''' Network-serializable objects must be registered.  The class
+        hint maps to a set (ordered in the case of array-base objects)
+        of field names (keys).  
+        '''
+
+    def __init__(self, hint, keys, wireProtocol):
+        global objectRegistry
+        self.hint = hint
+        self.keys = keys
+        self.wireProtocol = wireProtocol
+        objectRegistry[hint] = self
+    
+    def getRegistry(hint):
+        global objectRegistry
+        return objectRegistry.get(hint)
+    getRegistry = staticmethod(getRegistry)
+
+
+def __makeNetworkAccessor(cls, key):
+    '''  Creates and accessor/mutator method for the given class.  
+
+        'key' is the name the method will have and represents
+        the field on the object whose data we are accessing
+        ''' 
+    def accessor(self, *args):
+        if len(args) != 0:
+            self.__data[key] = args[0]
+        return self.__data.get(key)
+    setattr(cls, key, accessor)
+
+
+
+def __makeGetRegistry(cls, registry):
+    ''' Wraps the registry for this class inside an accessor method '''
+    def get(self):
+        return registry
+    setattr(cls, 'getRegistry', get)
+
+def __makeGetData(cls):
+    ''' Wraps the stored data in an accessor method '''
+    def get(self):
+        return self.__data
+    setattr(cls, 'getData', get)
+        
+
+def __osrfNetworkObjectInit(self, data={}):
+    ''' __init__ method for osrNetworkObjects.
+        If this is an array, we pull data out of the data array
+        (if there is any) and translate that into a hash internally
+        '''
+
+    self.__data = data
+    if len(data) > 0:
+        reg = self.getRegistry()
+        if reg.wireProtocol == 'array':
+            self.__data = {}
+            for i in range(len(reg.keys)):
+                try:
+                    self.__data[reg.keys[i]] = data[i]
+                except:
+                    self.__data[reg.keys[i]] = None
+
 
+def osrfNetworkRegisterHint(hint, keys, type='hash'):
+    ''' Registers a new network-serializable object class.
 
-class __unknown(osrfNetworkObject):
-       """Default class for un-registered network objects."""
-       def __init__(self, data=None):
-               self.data = data
+        'hint' is the class hint
+        'keys' is the list of field names on the object
+            If this is an array-based object, the field names
+            must be sorted to reflect the encoding order of the fields
+        'type' is the wire-protocol of the object.  hash or array.
+        '''
 
-setattr(__unknown,'__keys', [])
-setattr(osrfNetworkObject,'__unknown', __unknown)
+    # register the class with the global registry
+    registry = osrfNetworkRegistry(hint, keys, type)
 
+    # create the new class locally with the given hint name
+    exec('class %s(osrfNetworkObject):\n\tpass' % hint)
+
+    # give the new registered class a local handle
+    cls = None
+    exec('cls = %s' % hint)
+
+    # assign an accessor/mutator for each field on the object
+    for k in keys:
+        __makeNetworkAccessor(cls, k)
+
+    # assign our custom init function
+    setattr(cls, '__init__', __osrfNetworkObjectInit)
+    __makeGetRegistry(cls, registry)
+    __makeGetData(cls)
 
-def osrfNetworkRegisterHint(hint, keys, type='hash'):
-       """Register a network hint.  
-       
-               This creates a new class at osrfNetworkObject.<hint> with 
-               methods for accessing/mutating the object's data.  
-               Method names will match the names found in the keys array
-
-               hint - The hint name to encode with the object
-               type - The data container type.  
-               keys - An array of data keys.  If type is an 'array', the order of
-               the keys will determine how the data is accessed
-       """
-
-    #
-    # XXX Surely there is a cleaner way to accomplish this via 
-    # the PythonAPI
-    #
-
-       estr = "class %s(osrfNetworkObject):\n" % hint
-       estr += "\tdef __init__(self, data=None):\n"
-       estr += "\t\tself.data = data\n"
-       estr += "\t\tif data:\n"
-
-       if type == 'hash': 
-               estr += "\t\t\tpass\n"
-       else:
-               # we have to make sure the array is large enough        
-               estr += "\t\t\twhile len(data) < %d:\n" % len(keys)
-               estr += "\t\t\t\tdata.append(None)\n"
-
-       estr += "\t\telse:\n"
-
-       if type == 'array':
-               estr += "\t\t\tself.data = []\n"
-               estr += "\t\t\tfor i in range(%s):\n" % len(keys)
-               estr += "\t\t\t\tself.data.append(None)\n"
-               for i in range(len(keys)):
-                       estr += "\tdef %s(self, *args):\n"\
-                                               "\t\tif len(args) != 0:\n"\
-                                               "\t\t\tself.data[%s] = args[0]\n"\
-                                               "\t\treturn self.data[%s]\n" % (keys[i], i, i)
-
-       if type == 'hash':
-               estr += "\t\t\tself.data = {}\n"
-               estr += "\t\t\tfor i in %s:\n" % str(keys)
-               estr += "\t\t\t\tself.data[i] = None\n"
-               for i in keys:
-                       estr += "\tdef %s(self, *args):\n"\
-                                               "\t\tif len(args) != 0:\n"\
-                                               "\t\t\tself.data['%s'] = args[0]\n"\
-                                               "\t\tval = None\n"\
-                                               "\t\ttry: val = self.data['%s']\n"\
-                                               "\t\texcept: return None\n"\
-                                               "\t\treturn val\n" % (i, i, i)
-
-       estr += "setattr(osrfNetworkObject, '%s', %s)\n" % (hint,hint)
-       estr += "setattr(osrfNetworkObject.%s, '__keys', keys)" % hint
-       exec(estr)
-       
-               
+
+    # attach our new class to the osrfNetworkObject 
+    # class so others can access it
+    setattr(osrfNetworkObject, hint , cls)
+
+
+
+
+# create a unknown object to handle unregistred types
+osrfNetworkRegisterHint('__unknown', [], 'hash')
 
 # -------------------------------------------------------------------
 # Define the custom object parsing behavior 
 # -------------------------------------------------------------------
 def parseNetObject(obj):
-       hint = None
-       islist = False
-       try:
-               hint = obj[JSON_CLASS_KEY]
-               obj = obj[JSON_PAYLOAD_KEY]
-       except: pass
-       if isinstance(obj,list):
-               islist = True
-               for i in range(len(obj)):
-                       obj[i] = parseNetObject(obj[i])
-       else: 
-               if isinstance(obj,dict):
-                       for k,v in obj.iteritems():
-                               obj[k] = parseNetObject(v)
-
-       if hint: # Now, "bless" the object into an osrfNetworkObject
-               estr = 'obj = osrfNetworkObject.%s(obj)' % hint
-               try:
-                       exec(estr)
-               except AttributeError:
-                       # this object has not been registered, shove it into the default container
-                       obj = osrfNetworkObject.__unknown(obj)
-
-       return obj;
-
-
-
-       
+    hint = None
+    islist = False
+    try:
+        hint = obj[OSRF_JSON_CLASS_KEY]
+        obj = obj[OSRF_JSON_PAYLOAD_KEY]
+    except: pass
+    if isinstance(obj,list):
+        islist = True
+        for i in range(len(obj)):
+            obj[i] = parseNetObject(obj[i])
+    else: 
+        if isinstance(obj,dict):
+            for k,v in obj.iteritems():
+                obj[k] = parseNetObject(v)
+
+    if hint: # Now, "bless" the object into an osrfNetworkObject
+        estr = 'obj = osrfNetworkObject.%s(obj)' % hint
+        try:
+            exec(estr)
+        except AttributeError:
+            # this object has not been registered, shove it into the default container
+            obj = osrfNetworkObject.__unknown(obj)
+
+    return obj;
+
+
+
+def osrfObjectToXML(obj):
+    """ Returns the XML representation of an internal object."""
+    chars = []
+    __osrfObjectToXML(obj, chars)
+    return ''.join(chars)
+
+def __osrfObjectToXML(obj, chars):
+    """ Turns an internal object into OpenSRF XML """
+
+    if obj is None:
+        chars.append('<null/>')
+        return
+
+    if isinstance(obj, unicode) or isinstance(obj, str):
+        chars.append('<string>%s</string>' % saxutils.escape(obj))
+        return
+
+    if isinstance(obj, int)  or isinstance(obj, long):
+        chars.append('<number>%d</number>' % obj)
+        return
+
+    if isinstance(obj, float):
+        chars.append('<number>%f</number>' % obj)
+        return
+
+    classHint = None
+
+    if isinstance(obj, osrfNetworkObject): 
+
+        registry = obj.getRegistry()
+        data = obj.getData()
+        hint = saxutils.escape(registry.hint)
+
+        if registry.wireProtocol == 'array':
+            chars.append("<array class_hint='%s'>" % hint)
+            for k in registry.keys:
+                __osrfObjectToXML(data.get(k), chars)
+            chars.append('</array>')
+
+        else:
+            if registry.wireProtocol == 'hash':
+                chars.append("<object class_hint='%s'>" % hint)
+                for k,v in data.items():
+                    chars.append("<element key='%s'>" % saxutils.escape(k))
+                    __osrfObjectToXML(v, chars)
+                    chars.append('</element>')
+                chars.append('</object>')
+                
+
+    if isinstance(obj, list):
+        chars.append('<array>')
+        for i in obj:
+            __osrfObjectToXML(i, chars)
+        chars.append('</array>')
+        return
+
+    if isinstance(obj, dict):
+        chars.append('<object>')
+        for k,v in obj.items():
+            chars.append("<element key='%s'>" % saxutils.escape(k))
+            __osrfObjectToXML(v, chars)
+            chars.append('</element>')
+        chars.append('</object>')
+        return
+
+    if isinstance(obj, bool):
+        val = 'false'
+        if obj: val = 'true'
+        chars.append("<boolean value='%s'/>" % val)
+        return
+