# 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'
+
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=[]):
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 += '¶m=%s' % urllib.quote(self.encodeParam(param))
return params
def getFormat(self):
return 'xml'
+ def getInputFormat(self):
+ return self.getFormat()
+
def handleResponse(self, response):
handler = XMLGatewayParser()
parser = make_parser()
parser.parse(response)
return handler.getResult()
+ def encodeParam(self, param):
+ return osrfObjectToXML(param);
+
class XMLGatewayParser(handler.ContentHandler):
def __init__(self):
# 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 = []
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':
-# -----------------------------------------------------------------------
-# 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
-
-# -----------------------------------------------------------------------
-# 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
+