From 358bc816abd3bcfd41a1812b2afa65668ca6bc55 Mon Sep 17 00:00:00 2001 From: erickson Date: Fri, 15 Jun 2007 03:18:51 +0000 Subject: [PATCH] Cleaned up the network hint / object registration code in net_obj 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 | 3 + src/python/osrf/gateway.py | 35 ++-- src/python/osrf/json.py | 271 +++++++++--------------------- src/python/osrf/net_obj.py | 326 ++++++++++++++++++++++++------------- 4 files changed, 314 insertions(+), 321 deletions(-) diff --git a/src/python/osrf/const.py b/src/python/osrf/const.py index eacf479..2264fce 100644 --- a/src/python/osrf/const.py +++ b/src/python/osrf/const.py @@ -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' + diff --git a/src/python/osrf/gateway.py b/src/python/osrf/gateway.py index 3a6efaf..96d03ad 100644 --- a/src/python/osrf/gateway.py +++ b/src/python/osrf/gateway.py @@ -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 += '¶m=%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': diff --git a/src/python/osrf/json.py b/src/python/osrf/json.py index 1b73e37..b6b3154 100644 --- a/src/python/osrf/json.py +++ b/src/python/osrf/json.py @@ -1,235 +1,116 @@ -# ----------------------------------------------------------------------- -# Copyright (C) 2007 Georgia Public Library Service -# Bill Erickson -# -# 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. 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 - diff --git a/src/python/osrf/net_obj.py b/src/python/osrf/net_obj.py index 1dafa1e..a95dda0 100644 --- a/src/python/osrf/net_obj.py +++ b/src/python/osrf/net_obj.py @@ -1,127 +1,223 @@ -# ----------------------------------------------------------------------- -# 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. 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('') + return + + if isinstance(obj, unicode) or isinstance(obj, str): + chars.append('%s' % saxutils.escape(obj)) + return + + if isinstance(obj, int) or isinstance(obj, long): + chars.append('%d' % obj) + return + + if isinstance(obj, float): + chars.append('%f' % 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("" % hint) + for k in registry.keys: + __osrfObjectToXML(data.get(k), chars) + chars.append('') + + else: + if registry.wireProtocol == 'hash': + chars.append("" % hint) + for k,v in data.items(): + chars.append("" % saxutils.escape(k)) + __osrfObjectToXML(v, chars) + chars.append('') + chars.append('') + + + if isinstance(obj, list): + chars.append('') + for i in obj: + __osrfObjectToXML(i, chars) + chars.append('') + return + + if isinstance(obj, dict): + chars.append('') + for k,v in obj.items(): + chars.append("" % saxutils.escape(k)) + __osrfObjectToXML(v, chars) + chars.append('') + chars.append('') + return + + if isinstance(obj, bool): + val = 'false' + if obj: val = 'true' + chars.append("" % val) + return + -- 2.43.2