""" $Id: SOAP.py,v 1.6 2001/01/12 16:52:58 kmacleod Exp $ """ from StringIO import StringIO from types import * import string import Orchard.Node from Orchard.Node import Node import Orchard.Parsers.SAX from urllib import splittype, splithost, splitport import httplib # just constants _DICT = "dict" _ARRAY = "array" _CHAR = "char" _xmlns_ns = "http://www.w3.org/2000/xmlns/" _soap_enc_ns = "http://schemas.xmlsoap.org/soap/encoding/" _soap_env_ns = "http://schemas.xmlsoap.org/soap/envelope/" _xsi_ns = "http://www.w3.org/1999/XMLSchema-instance" _xsd_ns = "http://www.w3.org/1999/XMLSchema" _orchard_internal_ns = "http://casbah.org/orchard-soap-internal-namespace" class PicklingError(Exception): """Orchard.SOAP.PicklingError""" pass class RemoteError(Exception): """Orchard.SOAP.RemoteError""" pass class Pickler: def __init__(self, file): self._write = file.write def dump(self, object): self._write('') self.prefixes = {} if (type(object) is InstanceType and object.__class__.__name__ == 'Node'): self.namespaces = ' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"' new_object = Node() for key in object._keys(): if ( type(key) is TupleType and key[0] == _xmlns_ns): self.prefixes[object[key]] = key[1] self.namespaces = (self.namespaces + ' xmlns:' + key[1] + '="' + object[key] + '"') else: new_object[key] = object[key] self._save(new_object) else: self.namespaces = (' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"' + ' xmlns:SOAP-ENV="' + _soap_env_ns + '"' + ' xmlns:SOAP-ENC="' + _soap_enc_ns + '"' + ' xmlns:xsi="' + _xsi_ns + '"') self._save(object) def _save(self, object): object_type = type(object) if object_type in (IntType, LongType, FloatType, StringType): self._write(str(object)) elif object_type in (TupleType, ListType): for item in object: self._write_item('item', item) elif object_type is DictType: for key in object.keys(): self._write_item(key, object[key]) elif (object_type is InstanceType and object.__class__.__name__ == 'Node'): if object._has_key((_orchard_internal_ns, 'element-order')): order = object[(_orchard_internal_ns, 'element-order')] else: order = object._keys() for key in order: if key != (_orchard_internal_ns, 'element-order'): if type(key) is TupleType: key_name = self.prefixes[key[0]] + ':' + key[1] else: key_name = key self._write_item(key_name, object[key]) elif object_type is NoneType: pass else: raise PicklingError, "unpicklable type " + str(object_type) def _write_item(self, key, item): item_type = type(item) item_type_str = "" if item_type in (TupleType, ListType): item_type_str = ' SOAP-ENC:arrayType="item[]"' if item_type is NoneType: item_type_str = ' xsi:null="1"' if item_type in (IntType, LongType): item_type_str = item_type_str + ' xsi:type="xsd:int"' elif item_type is FloatType: item_type_str = item_type_str + ' xsi:type="xsd:float"' elif item_type is StringType: item_type_str = item_type_str + ' xsi:type="xsd:string"' self._write('<' + key + item_type_str + self.namespaces + '>') self.namespaces = "" self._save(item) self._write('') class Unpickler: def __init__(self, file): self.file = file def load(self): self.parse_value_stack = [ {} ] self.parse_utype_stack = [ _DICT ] self.parse_type_stack = [ ] parser = Orchard.Parsers.SAX.SAX( handler=self ) lines = [] file = self.file # FIXME SAX parsers should support this on streams line = file.readline() while (line != "\f\n") and (line != ""): lines.append(line) line = file.readline() if len(lines) == 0: raise EOFError file = StringIO(string.join(lines)) parser.parse(file) object = self.parse_value_stack[0] delattr(self, 'parse_value_stack') return object def startElement(self, element): attributes = element.attributes self.chars = "" xsi_type = None if attributes._has_key((_xsi_ns, 'type')): xsi_type = attributes[(_xsi_ns, 'type')].value elif (attributes._has_key((_xsi_ns, 'null')) and attributes[(_xsi_ns, 'null')].value == '1'): xsi_type = "None" self.parse_type_stack.append(xsi_type) if attributes._has_key((_soap_enc_ns, 'arrayType')): self.parse_utype_stack.append(_ARRAY) self.parse_value_stack.append( [ ] ) else: # will be set to _DICT if a sub-element is found self.parse_utype_stack.append(_CHAR) def endElement(self, element): xsi_type = self.parse_type_stack.pop() utype = self.parse_utype_stack.pop() if utype is _CHAR: if xsi_type == "None": value = None elif xsi_type == 'xsd:int': value = int(self.chars) elif xsi_type == 'xsd:float': value = float(self.chars) else: value = self.chars else: value = self.parse_value_stack.pop() # if we're in an element, and our parent element was defaulted # to _CHAR, then we're in a struct and we need to create that # dictionary. if self.parse_utype_stack[-1] is _CHAR: self.parse_value_stack.append( Node() ) self.parse_utype_stack[-1] = _DICT if self.parse_utype_stack[-1] is _DICT: self.parse_value_stack[-1][(element.namespace_uri, element.local_name)] = value else: self.parse_value_stack[-1].append(value) def characters(self, chars): self.chars = self.chars + chars.data def fatalError(self, exc): raise exc def dump(object, file): Pickler(file).dump(object) def dumps(object): file = StringIO() Pickler(file).dump(object) return file.getvalue() def load(file): return Unpickler(file).load() def loads(str): file = StringIO(str) return Unpickler(file).load() class Context(Orchard.Node): def __init__(self, *positional_args, **keyword_args): positional_args = (self,) + positional_args apply(Orchard.Node.__init__, positional_args, keyword_args) def encode_call(self, method, arguments): content = Node() args = Node() argument_order = [] ii = 1 for arg in arguments: arg_name = "arg" + str(ii) args[arg_name] = arg argument_order.append(arg_name) ii = ii + 1 args[(_orchard_internal_ns, 'element-order')] = argument_order content[(self.namespace, method)] = args body = Node() body[(_soap_env_ns, 'Body')] = content envelope = Node() envelope[(_xmlns_ns, 'SOAP-ENC')] = _soap_enc_ns envelope[(_xmlns_ns, 'SOAP-ENV')] = _soap_env_ns envelope[(_xmlns_ns, 'xsi')] = _xsi_ns envelope[(_xmlns_ns, 'xsd')] = _xsd_ns envelope[(_xmlns_ns, 'content')] = self.namespace envelope[(_soap_env_ns, 'Envelope')] = body return dumps(envelope) def decode_result(self, method, data): object = loads(data) envelope = object[(_soap_env_ns, 'Envelope')] body = envelope[(_soap_env_ns, 'Body')] if body._has_key((_soap_env_ns, 'Fault')): raise RemoteError, body[(_soap_env_ns, 'Fault')] content = body[(self.namespace, method + 'Response')] return content class HTTPConnection(Orchard.Node): def __init__(self, *positional_args, **keyword_args): positional_args = (self,) + positional_args apply(Orchard.Node.__init__, positional_args, keyword_args) if not hasattr(self, 'debug'): self.debug = 0 if hasattr(self, 'url'): self.type, self.path = splittype(self.url) self.host, self.path = splithost(self.path) self.host, self.port = splitport(self.host) if self.port == None: self.port = 80 self.port = int(self.port) else: raise "missing url in HTTPConnection" if hasattr(self, 'namespace'): self.context = Context() self.context.namespace = self.namespace def proxy(self): return Proxy(self) def call(self, method, arguments): soap = self.context.encode_call(method, arguments) if self.debug: print soap h = httplib.HTTP(self.host, self.port) h.putrequest("POST", self.path) h.putheader("Content-type", "text/xml") h.putheader("Content-length", str(len(soap))) h.putheader('Accept', 'text/plain') h.putheader('Host', self.host) h.endheaders() h.send(soap) errcode, msg, hdrs = h.getreply() data = h.getfile().read() if self.debug: print data result = self.context.decode_result(method, data) if type(result) is InstanceType: result_keys = result._keys() return result[result_keys[0]] else: return result class Proxy: def __init__(self, connection): self.connection = connection def __getattr__(self, attr): forwarder = Proxy.Method(self, attr) self.__dict__[attr] = forwarder return forwarder class Method: def __init__(self, proxy, method): self.proxy = proxy self.method = method def __call__(self, *args): return self.proxy.connection.call(self.method, args) def connect(*positional_args, **keyword_args): # This forwards a variable number of keyword arguments to the # HTTPConnection constructor by creating a dummy instance, # reassigning the instance into the HTTPConnection class, and then # applying the HTTPConnection class' constructor connection = _EmptyClass() connection.__class__ = HTTPConnection positional_args = (connection,) + positional_args apply(HTTPConnection.__init__, positional_args, keyword_args) return connection.proxy() class _EmptyClass: pass