home *** CD-ROM | disk | FTP | other *** search
/ PC World 2002 May / PCWorld_2002-05_cd.bin / Software / TemaCD / activepython / ActivePython-2.1.1.msi / Python21_PPM_soaplib.py < prev    next >
Encoding:
Python Source  |  2001-07-26  |  22.8 KB  |  857 lines

  1. #
  2. # SOAP CLIENT LIBRARY
  3. # $Id$
  4. #
  5. # an SOAP 1.1 client interface for Python.
  6. #
  7. # the marshalling and response parser code can also be used to
  8. # implement SOAP servers.
  9. #
  10. # Notes:
  11. # this version uses the sgmlop XML parser, if installed.  this is
  12. # typically 10-15x faster than using Python's standard XML parser.
  13. #
  14. # you can get the sgmlop distribution from:
  15. #
  16. #    http://www.pythonware.com/products/xml
  17. #
  18. # also note that this version is designed to work with Python 1.5.2
  19. # and newer.  it does not work under 1.5.1 or earlier.
  20. #
  21. # Contact:
  22. # fredrik@pythonware.com
  23. # http://www.pythonware.com
  24. #
  25. # History:
  26. # 2000-01-05 fl  First experimental version (based on xmlrpclib.py)
  27. # 2000-02-15 fl  Updated to SOAP 1.0
  28. # 2000-04-30 fl  Major overhaul, updated for SOAP 1.1
  29. # 2000-05-27 fl  Fixed xmllib support
  30. # 2000-06-20 fl  Frontier interoperability testing
  31. #
  32. # Copyright (c) 1999-2000 by Secret Labs AB.
  33. # Copyright (c) 1999-2000 by Fredrik Lundh.
  34. #
  35. # Portions of this engine have been developed in cooperation with
  36. # Loudcloud, Inc.
  37. #
  38. # --------------------------------------------------------------------
  39. # The soaplib.py library is
  40. # Copyright (c) 1999-2000 by Secret Labs AB
  41. # Copyright (c) 1999-2000 by Fredrik Lundh
  42. #
  43. # By obtaining, using, and/or copying this software and/or its
  44. # associated documentation, you agree that you have read, understood,
  45. # and will comply with the following terms and conditions:
  46. #
  47. # Permission to use, copy, modify, and distribute this software and
  48. # its associated documentation for any purpose and without fee is
  49. # hereby granted, provided that the above copyright notice appears in
  50. # all copies, and that both that copyright notice and this permission
  51. # notice appear in supporting documentation, and that the name of
  52. # Secret Labs AB or the author not be used in advertising or publicity
  53. # pertaining to distribution of the software without specific, written
  54. # prior permission.
  55. #
  56. # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  57. # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
  58. # ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
  59. # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  60. # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  61. # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
  62. # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  63. # OF THIS SOFTWARE.
  64. # --------------------------------------------------------------------
  65.  
  66. # FIXME: handle Fault messages correctly
  67. # FIXME: handle homogenous arrays
  68. # FIXME: handle more time types
  69. # FIXME: add support for more character sets (under 1.6)
  70. # FIXME: incorporate id/href patches (from John Lehmann)
  71. #
  72. # 2001-02-20 fixed the bugs in the XML namespaces and also added __repr__
  73. #            method to the Binary class.
  74. #            Houman Gharmi <houmangh@activestate.com>
  75. #
  76.  
  77. import string, time, re
  78. import urllib, xmllib
  79. from types import *
  80. from cgi import escape
  81.  
  82. try:
  83.     import sgmlop
  84. except ImportError:
  85.     sgmlop = None # accelerator not available
  86.  
  87. # how far we're from a real 1.0 :-)
  88. __version__ = "0.8-hacked"
  89.  
  90. # schema namespaces
  91. NS_XSD = "http://www.w3.org/1999/XMLSchema"
  92. NS_XSI = "http://www.w3.org/1999/XMLSchema-instance"
  93.  
  94. # soap namespaces
  95. NS_ENV = "http://schemas.xmlsoap.org/soap/envelope/"
  96. NS_ENC = "http://schemas.xmlsoap.org/soap/encoding/"
  97.  
  98. # experimental extensions
  99. NS_LAB = "http://www.pythonware.com/soap/"
  100.  
  101. # envelope tag.  declare all namespaces used by the payload
  102. ENVELOPE = (
  103.     "<env:Envelope "
  104.     "xmlns:env='" + NS_ENV + "' "
  105.     "xmlns:enc='" + NS_ENC + "' "
  106.     "xmlns:lab='" + NS_LAB + "' "
  107.     "xmlns:xsd='" + NS_XSD + "' "
  108.     "xmlns:xsi='" + NS_XSI + "' "
  109.     "env:encodingStyle='" + NS_ENC + "'"
  110.     ">\n"
  111.     )
  112.  
  113. SOAPTAGS = NS_ENV + "Envelope", NS_ENV + "Body"
  114.  
  115. # check if a string is valid XML tag
  116. _is_valid_tag = re.compile("^[a-zA-Z_][-a-zA-Z0-9._]*$").match
  117. # FIXME: this should reject strings that start with [Xx][Mm][Ll]
  118.  
  119. # --------------------------------------------------------------------
  120. # Exceptions
  121.  
  122. class Error:
  123.     # base class for client errors
  124.     pass
  125.  
  126. class ProtocolError(Error):
  127.     # indicates an HTTP protocol error
  128.     def __init__(self, url, errcode, errmsg, headers):
  129.     self.url = url
  130.     self.errcode = errcode
  131.     self.errmsg = errmsg
  132.     self.headers = headers
  133.     def __repr__(self):
  134.     return (
  135.         "<ProtocolError for %s: %s %s>" %
  136.         (self.url, self.errcode, repr(self.errmsg))
  137.         )
  138.  
  139. class ResponseError(Error):
  140.     # indicates a broken response package
  141.     pass
  142.  
  143. class Fault(Error):
  144.     # indicates a SOAP fault package
  145.     def __init__(self, faultcode, faultstring, detail=None):
  146.     self.faultcode = faultcode
  147.     self.faultstring = faultstring
  148.     self.detail = None
  149.     def encode(self, out):
  150.     write = out.write
  151.     write("<env:Fault>\n")
  152.     write("<faultcode>%s</faultcode>" % self.faultcode)
  153.     write("<faultstring>%s</faultstring>" % self.faultstring)
  154.     if self.detail:
  155.         out._dump("detail", self.detail)
  156.     write("</env:Fault>\n")
  157.     def __repr__(self):
  158.     return (
  159.         "<Fault %s: %s>" %
  160.         (self.faultcode, repr(self.faultstring))
  161.         )
  162.  
  163. class MethodCall:
  164.     # (experimental) represents a set of named parameters
  165.     def __init__(self, method, namespace, pargs=(), kwargs={}):
  166.     self.method = method
  167.     self.namespace = namespace
  168.     self.pargs = pargs
  169.     self.kwargs = kwargs
  170.     def encode(self, out):
  171.     if self.namespace:
  172.         method = "rpc:" + self.method
  173.         out.write("<%s xmlns:rpc=%s>\n" % (method, repr(self.namespace)))
  174.     else:
  175.         method = self.method
  176.         out.write("<%s>\n" % method)
  177.     i = 1
  178.     for v in self.pargs:
  179.         out._dump("v.%d" % i, v)
  180.         i = i + 1
  181.     for k, v in self.kwargs.items():
  182.         out._dump(k, v)
  183.     out.write("</%s>\n" % method)
  184.  
  185. class Response:
  186.     # (experimental) represents a response tuple
  187.     def __init__(self, method, namespace, value=()):
  188.     self.method = method
  189.     self.namespace = namespace
  190.     self.value = value
  191.     def encode(self, out):
  192.     method = self.method + "Response"
  193.     if self.namespace:
  194.         method = "rpc:" + method
  195.         out.write("<%s xmlns:rpc=%s>\n" % (method, repr(self.namespace)))
  196.     else:
  197.         out.write("<%s>\n" % method)
  198.     i = 1
  199.     for v in self.value:
  200.         out._dump("v.%d" % i, v)
  201.         i = i + 1
  202.     out.write("</%s>\n" % method)
  203.  
  204.  
  205. # --------------------------------------------------------------------
  206. # Special values
  207.  
  208. # boolean wrapper
  209. # (you must use True or False to generate a "boolean" SOAP value)
  210.  
  211. class Boolean:
  212.  
  213.     def __init__(self, value = 0):
  214.     self.value = not not value
  215.  
  216.     def encode(self, tag, out):
  217.     out.write(
  218.         "<%s xsi:type='xsd:boolean'>%d</%s>\n" % (tag, self.value, tag)
  219.         )
  220.  
  221.     def __repr__(self):
  222.     if self.value:
  223.         return "<Boolean True at %x>" % id(self)
  224.     else:
  225.         return "<Boolean False at %x>" % id(self)
  226.  
  227.     def __int__(self):
  228.     return self.value
  229.  
  230.     def __nonzero__(self):
  231.     return self.value
  232.  
  233. True, False = Boolean(1), Boolean(0)
  234.  
  235. #
  236. # timePeriod wrapper
  237. # (wrap your iso8601 string or time tuple or localtime time value in
  238. # this class to generate a "timePeriod" SOAP value)
  239.  
  240. class DateTime:
  241.  
  242.     def __init__(self, value = 0):
  243.     t = type(value)
  244.     if t is not StringType:
  245.         if t is not TupleType:
  246.         value = time.localtime(value)
  247.         value = time.strftime("%Y%m%dT%H:%M:%S", value)
  248.     self.value = value
  249.  
  250.     def __repr__(self):
  251.     return "<DateTime %s at %x>" % (self.value, id(self))
  252.  
  253.     def decode(self, data):
  254.     self.value = string.strip(data)
  255.  
  256.     def encode(self, tag, out):
  257.     out.write(
  258.         "<%s xsi:type='xsd:timePeriod'>%s</%s>\n", (tag, self.value, tag)
  259.         )
  260.  
  261. #
  262. # binary data wrapper.  you must use this for strings that are not
  263. # valid XML strings (in other words, strings that cannot be stored
  264. # as a plain XML element).
  265.  
  266. class Binary:
  267.  
  268.     def __init__(self, data=None):
  269.     self.data = data
  270.  
  271.     def decode(self, data):
  272.     import base64
  273.     self.data = base64.decodestring(data)
  274.  
  275.     def encode(self, tag, out):
  276.     import base64, StringIO
  277.     out.write("<%s xsi:type='enc:base64'>\n" % tag)
  278.     base64.encode(StringIO.StringIO(self.data), out)
  279.     out.write("</%s>\n" % tag)
  280.  
  281.     def __repr__(self):
  282.         return self.data
  283.  
  284.     def __len__(self):
  285.         return len(self.data)
  286.  
  287.     __str__ = __repr__
  288.  
  289. WRAPPERS = DateTime, Binary, Boolean
  290.  
  291. # --------------------------------------------------------------------
  292. # XML parsers
  293.  
  294. if sgmlop:
  295.  
  296.     class FastParser:
  297.     # sgmlop based XML parser.  this is typically 15x faster
  298.     # than SlowParser...
  299.  
  300.     def __init__(self, target):
  301.  
  302.         # setup callbacks
  303.         self.finish_endtag = target.end
  304.         self.handle_data = target.data
  305.  
  306.         self.start = target.start
  307.  
  308.         self.namespace = {"xmlns": "xmlns"}
  309.  
  310.         # activate parser
  311.         self.parser = sgmlop.XMLParser()
  312.         self.parser.register(self)
  313.         self.feed = self.parser.feed
  314.         self.entity = {
  315.         "amp": "&", "gt": ">", "lt": "<",
  316.         "apos": "'", "quot": '"'
  317.         }
  318.  
  319.     def close(self):
  320.         try:
  321.         self.parser.close()
  322.         finally:
  323.         self.parser = self.feed = None # nuke circular reference
  324.  
  325.     def finish_starttag(self, tag, attrs):
  326.         # FIXME: this doesn't handle nested namespaces
  327.         items = attrs.items()
  328.         for k, v in items:
  329.         if k[:6] == "xmlns:":
  330.             self.namespace[k[6:]] = v
  331.         # fixup names
  332.         if ":" in tag:
  333.         ns, tag = string.split(tag, ":")
  334.         tag = self.namespace[ns] + tag
  335.         for k, v in items:
  336.         if ":" in k:
  337.             ns, k = string.split(k, ":")
  338.             k = self.namespace[ns] + k
  339.         attrs[k] = v
  340.         self.start(tag, attrs, self.namespace.get)
  341.  
  342.     def handle_entityref(self, entity):
  343.         # <string> entity
  344.         try:
  345.         self.handle_data(self.entity[entity])
  346.         except KeyError:
  347.         self.handle_data("&%s;" % entity)
  348.  
  349. else:
  350.  
  351.     FastParser = None
  352.  
  353. class SlowParser(xmllib.XMLParser):
  354.     # slow but safe standard parser, based on the XML parser in
  355.     # Python's standard library
  356.  
  357.     def __init__(self, target):
  358.     self.__start = target.start
  359.     self.handle_data = target.data
  360.     self.unknown_endtag = target.end
  361.     xmllib.XMLParser.__init__(self)
  362.  
  363.     def __namespace(self, prefix):
  364.     # map namespace prefix to full namespace
  365.     for tag, namespace, tag in self.stack:
  366.         if namespace.has_key(prefix):
  367.         return namespace[prefix]
  368.     return None
  369.  
  370.     def unknown_starttag(self, tag, attrs):
  371.     # fixup tags and attribute names (ouch!)
  372.     tag = string.replace(tag, " ", "")
  373.     for k, v in attrs.items():
  374.         k = string.replace(k, " ", "")
  375.         attrs[k] = v
  376.     self.__start(tag, attrs, self.__namespace)
  377.  
  378. # --------------------------------------------------------------------
  379. # SOAP marshalling and unmarshalling code
  380.  
  381. class Marshaller:
  382.     """Generate an SOAP body from a Python data structure"""
  383.  
  384.     # USAGE: create a marshaller instance for each set of parameters,
  385.     # and use "dumps" to convert your data (represented as a tuple) to
  386.     # a SOAP body chunk.  to write a fault response, pass a Fault
  387.     # instance instead.  you may prefer to use the "dumps" convenience
  388.     # function for this purpose (see below).
  389.  
  390.     # by the way, if you don't understand what's going on in here,
  391.     # that's perfectly ok.
  392.  
  393.     def __init__(self):
  394.     self.memo = {}
  395.     self.data = None
  396.  
  397.     dispatch = {}
  398.  
  399.     def dumps(self, value, envelope=0):
  400.     self.__out = []
  401.     self.write = write = self.__out.append
  402.     if envelope:
  403.         write(ENVELOPE)
  404.     write("<env:Body>\n")
  405.     if (isinstance(value, MethodCall) or
  406.         isinstance(value, Response) or
  407.         isinstance(value, Fault)):
  408.         value.encode(self)
  409.     else:
  410.         for item in value:
  411.         self._dump("v", item)
  412.     write("</env:Body>\n")
  413.     if envelope:
  414.         write("</env:Envelope>\n")
  415.     result = string.join(self.__out, "")
  416.     del self.__out, self.write # don't need this any more
  417.     return result
  418.  
  419.     def _dump(self, tag, value):
  420.     try:
  421.         f = self.dispatch[type(value)]
  422.     except KeyError:
  423.         raise TypeError, "cannot marshal %s objects" % type(value)
  424.     else:
  425.         f(self, tag, value)
  426.  
  427.     def dump_none(self, tag, value):
  428.     self.write(
  429.         "<%s xsi:null='1'></%s>\n" % (tag, tag)
  430.         )
  431.     dispatch[NoneType] = dump_none
  432.  
  433.     def dump_int(self, tag, value):
  434.     self.write(
  435.         "<%s xsi:type='xsd:int'>%s</%s>\n" % (tag, value, tag)
  436.         )
  437.     dispatch[IntType] = dump_int
  438.  
  439.     def dump_long(self, tag, value):
  440.     self.write(
  441.         "<%s xsi:type='xsd:integer'>%s</%s>\n" % (tag, value, tag)
  442.         )
  443.  
  444.     def dump_long_old(self, tag, value):
  445.     value = str(value)[:-1] # 1.5.2 and earlier
  446.     self.write(
  447.         "<%s xsi:type='xsd:integer'>%s</%s>\n" % (tag, value, tag)
  448.         )
  449.  
  450.     if ("%s" % 1L) == "1":
  451.     dispatch[LongType] = dump_long
  452.     else:
  453.     dispatch[LongType] = dump_long_old
  454.  
  455.     def dump_double(self, tag, value):
  456.     self.write(
  457.         "<%s xsi:type='xsd:double'>%s</%s>\n" % (tag, value, tag)
  458.         )
  459.     dispatch[FloatType] = dump_double
  460.  
  461.     def dump_string(self, tag, value):
  462.     self.write(
  463.         "<%s xsi:type='xsd:string'>%s</%s>\n" % (tag, escape(value), tag)
  464.         )
  465.     dispatch[StringType] = dump_string
  466.  
  467.     def container(self, value):
  468.     if value:
  469.         i = id(value)
  470.         if self.memo.has_key(i):
  471.         raise TypeError, "cannot marshal recursive data structures"
  472.         self.memo[i] = None
  473.  
  474.     def dump_array(self, tag, value):
  475.     self.container(value)
  476.     write = self.write
  477.     write("<%s enc:arrayType='xsd:ur-type[%d]'>\n" % (tag, len(value)))
  478.     for v in value:
  479.         self._dump("v", v)
  480.     write("</%s>\n" % tag)
  481.     dispatch[TupleType] = dump_array
  482.     dispatch[ListType] = dump_array
  483.  
  484.     def dump_dict(self, tag, value):
  485.     self.container(value)
  486.     write = self.write
  487.     write("<%s xsi:type='lab:PythonDict'>\n" % tag)
  488.     for k, v in value.items():
  489.         self._dump("k", k)
  490.         self._dump("v", v)
  491.     write("</%s>\n" % tag)
  492.     dispatch[DictType] = dump_dict
  493.  
  494.     def dump_instance(self, tag, value):
  495.     # check for special wrappers
  496.     if value.__class__ in WRAPPERS:
  497.         value.encode(tag, self)
  498.     else:
  499.         write = self.write
  500.         write("<%s>\n" % tag)
  501.         for k, v in vars(value).items():
  502.         self._dump(k, v)
  503.         write("</%s>\n" % tag)
  504.     dispatch[InstanceType] = dump_instance
  505.  
  506.  
  507. class Unmarshaller:
  508.  
  509.     # unmarshal an SOAP response, based on incoming XML events (start,
  510.     # data, end).  call close to get the resulting data structure
  511.  
  512.     # note that this reader is fairly tolerant, and gladly accepts
  513.     # bogus SOAP data without complaining (but not bogus XML).
  514.  
  515.     # and again, if you don't understand what's going on in here,
  516.     # that's perfectly ok.
  517.  
  518.     def __init__(self):
  519.     self._stack = []
  520.         self._marks = []
  521.     self._data = []
  522.     self.append = self._stack.append
  523.  
  524.     def close(self):
  525.     # return response code and the actual response
  526.     if self._marks or len(self._stack) != 1:
  527.         raise ResponseError()
  528.     # FIXME: should this really be done here?
  529.     method, params = self._stack[0]
  530.     args = []
  531.     for k, v in params:
  532.         args.append(v)
  533.     return method, tuple(args)
  534.  
  535.     #
  536.     # event handlers
  537.  
  538.     def start(self, tag, attrs, fixup):
  539.     # handle start tag
  540.     type = attrs.get(NS_XSI + "type")
  541.     if type is None:
  542.         type = attrs.get(NS_ENC + "arrayType")
  543.     if type and ":" in type:
  544.         prefix, type = string.split(type, ":")
  545.         ns = fixup(prefix)
  546.         if ns is None:
  547.         raise SyntaxError,\
  548.               "undefined namespace prefix %s" % repr(prefix)
  549.         type = ns + type
  550.     self._marks.append((tag, type, len(self._stack)))
  551.     self._data = []
  552.  
  553.     def data(self, text):
  554.     self._data.append(text)
  555.  
  556.     dispatch = {}
  557.  
  558.     def end(self, tag):
  559.     # call the appropriate type handler
  560.     tag, type, mark = self._marks.pop()
  561.     if type is None and tag in SOAPTAGS:
  562.         pass
  563.     else:
  564.         try:
  565.         f = self.dispatch[type]
  566.         except KeyError:
  567.         self.end_unknown(type)
  568.         else:
  569.         self._mark = mark
  570.                 self._stack.append((tag, f(self)))
  571.  
  572.     #
  573.     # element decoders
  574.  
  575.     def end_unknown(self, type, join=string.join):
  576.     print "***", type
  577.     raise SyntaxError, ("unknown type %s (for now)" % repr(type))
  578.  
  579.     def end_boolean(self, join=string.join):
  580.     value = join(self._data, "")
  581.     if value in ("0", "false"):
  582.         return False
  583.     elif value in ("1", "true"):
  584.         return True
  585.     else:
  586.         raise TypeError, "bad boolean value"
  587.     dispatch[NS_XSD + "boolean"] = end_boolean
  588.  
  589.     def end_int(self, join=string.join):
  590.     return int(join(self._data, ""))
  591.     dispatch[NS_XSD + "int"] = end_int
  592.     dispatch[NS_XSD + "byte"] = end_int
  593.     dispatch[NS_XSD + "unsignedByte"] = end_int
  594.     dispatch[NS_XSD + "short"] = end_int
  595.     dispatch[NS_XSD + "unsignedShort"] = end_int
  596.     dispatch[NS_XSD + "long"] = end_int
  597.  
  598.     def end_long(self, join=string.join):
  599.     return long(join(self._data, ""))
  600.     dispatch[NS_XSD + "integer"] = end_long
  601.     dispatch[NS_XSD + "unsignedInt"] = end_long
  602.     dispatch[NS_XSD + "unsignedLong"] = end_long
  603.  
  604.     def end_double(self, join=string.join):
  605.     return float(join(self._data, ""))
  606.     dispatch[NS_XSD + "double"] = end_double
  607.  
  608.     def end_string(self, join=string.join):
  609.     return join(self._data, "")
  610.     dispatch[NS_XSD + "string"] = end_string
  611.  
  612.     def end_array(self):
  613.     # map arrays to Python lists
  614.     list = []
  615.     data = self._stack[self._mark:]
  616.     del self._stack[self._mark:]
  617.     for tag, item in data:
  618.         list.append(item)
  619.     return list
  620.     dispatch[NS_XSD + "ur-type[]"] = end_array
  621.  
  622.     def end_dict(self):
  623.         dict = {}
  624.     data = self._stack[self._mark:]
  625.     del self._stack[self._mark:]
  626.         for i in range(0, len(data), 2):
  627.             dict[data[i][1]] = data[i+1][1]
  628.     return dict
  629.     dispatch[NS_LAB + "PythonDict"] = end_dict
  630.  
  631.     def end_struct(self):
  632.     # typeless elements are assumed to be structs.  map
  633.     # them to a (name, value) list
  634.     data = self._stack[self._mark:]
  635.     del self._stack[self._mark:]
  636.     return data
  637.     dispatch[None] = end_struct
  638.  
  639.     def end_base64(self, join=string.join):
  640.     value = Binary()
  641.     value.decode(join(self._data, ""))
  642.     return str(value)
  643.     dispatch[NS_ENC + "base64"] = end_base64
  644.  
  645.     def end_dateTime(self, join=string.join):
  646.     value = DateTime()
  647.     value.decode(join(self._data, ""))
  648.     return value
  649.     dispatch[NS_XSD + "timePeriod"] = end_dateTime
  650.     dispatch[NS_XSD + "timeInstant"] = end_dateTime
  651.  
  652. # --------------------------------------------------------------------
  653. # convenience functions
  654.  
  655. def getparser():
  656.     # get the fastest available parser, and attach it to an
  657.     # unmarshalling object.  return both objects.
  658.     target = Unmarshaller()
  659.     if FastParser:
  660.     return FastParser(target), target
  661.     return SlowParser(target), target
  662.  
  663. def dumps(params, envelope=0):
  664.     # convert a tuple or a fault object to an SOAP packet
  665.  
  666.     assert (type(params) == TupleType or
  667.         isinstance(params, MethodCall) or
  668.         isinstance(params, Response) or
  669.         isinstance(params, Fault)
  670.         ),\
  671.        "argument must be tuple, method call/response, or fault instance"
  672.  
  673.     m = Marshaller()
  674.  
  675.     return m.dumps(params, envelope)
  676.  
  677. def loads(data):
  678.     # convert an SOAP packet to data plus a method name (None
  679.     # if not present).  if the SOAP packet represents a fault
  680.     # condition, this function raises a Fault exception.
  681.     p, u = getparser()
  682.     p.feed(data)
  683.     p.close()
  684.     return u.close()
  685.  
  686.  
  687. # --------------------------------------------------------------------
  688. # request dispatcher
  689.  
  690. class _Method:
  691.     # some magic to bind an SOAP method to an RPC server.
  692.     # supports "nested" methods (e.g. examples.getStateName)
  693.     def __init__(self, send, name):
  694.     self.__send = send
  695.     self.__name = name
  696.     def __getattr__(self, name):
  697.     return _Method(self.__send, "%s.%s" % (self.__name, name))
  698.     def __call__(self, *pargs, **kwargs):
  699.     return self.__send(self.__name, pargs, kwargs)
  700.  
  701. class Transport:
  702.     """Handles an HTTP transaction to an SOAP server"""
  703.  
  704.     # client identifier (may be overridden)
  705.     user_agent = "soaplib.py/%s (from www.pythonware.com)" % __version__
  706.  
  707.     def request(self, host, handler, request_body):
  708.     # issue SOAP request
  709.  
  710.     import ppmhttplib
  711.     h = ppmhttplib.PPMHTTP(host)
  712.     h.debuglevel = 0
  713.         h.set_debuglevel(0)
  714.         h.postrequest("POST", handler)
  715.     # required by HTTP/1.1
  716.     h.putheader("Host", host)
  717.  
  718.     if h.debuglevel > 0:
  719.         print "-- REQUEST --"
  720.         print request_body
  721.  
  722.     # required by SOAP
  723.     h.putheader("User-Agent", self.user_agent)
  724.     h.putheader("Content-Type", "text/xml")
  725.     h.putheader("Content-Length", str(len(request_body)))
  726.         # Removed by ActiveState -- incompatible with Perl SOAP server
  727.         # XXX we should investigate the real problem...the SOAPAction
  728.         # XXX header is required in SOAP 1.1
  729.     # h.putheader("SOAPAction", '""')
  730.  
  731.     h.endheaders()
  732.  
  733.     if request_body:
  734.         h.send(request_body)
  735.  
  736.     errcode, errmsg, headers = h.getreply()
  737.  
  738.     if h.debuglevel > 0:
  739.         print "-- RESPONSE --"
  740.         print errcode, errmsg, headers
  741.  
  742.     if errcode not in (200, 500):
  743.         raise ProtocolError(
  744.         host + handler,
  745.         errcode, errmsg,
  746.         headers
  747.         )
  748.  
  749.     response = self.parse_response(h.getfile())
  750.  
  751.     # should this really be done in here?
  752.     response = response[1]
  753.     if len(response) == 1:
  754.         response = response[0]
  755.  
  756.     return response
  757.  
  758.     def parse_response(self, f):
  759.     # read response from input file, and parse it
  760.  
  761.     p, u = getparser()
  762.  
  763.     while 1:
  764.         response = f.read(1024)
  765.         if not response:
  766.         break
  767.         p.feed(response)
  768.  
  769.     f.close()
  770.     p.close()
  771.  
  772.     return u.close()
  773.  
  774.  
  775. class ServerProxy:
  776.     """Represents a connection to an SOAP server"""
  777.  
  778.     def __init__(self, uri, transport=None):
  779.     # establish a "logical" server connection
  780.  
  781.     # get the url
  782.     type, uri = urllib.splittype(uri)
  783.     if type != "http":
  784.         raise IOError, "unsupported SOAP protocol"
  785.     self.__host, self.__handler = urllib.splithost(uri)
  786.     if not self.__handler:
  787.         self.__handler = "/"
  788.  
  789.     if transport is None:
  790.         transport = Transport()
  791.     self.__transport = transport
  792.  
  793.     self.__methods = {} # known methods
  794.  
  795.     def _defmethod(self, methodname, namespace):
  796.     # associate information with a remote methodname
  797.  
  798.     self.__methods[methodname] = namespace
  799.  
  800.     def __request(self, methodname, pargs, kwargs):
  801.     # call a method on the remote server
  802.  
  803.     # wrap the arguments up
  804.     params = MethodCall(
  805.         methodname, self.__methods.get(methodname), pargs, kwargs
  806.         )
  807.  
  808.     request = dumps(params, 1)
  809.  
  810.     response = self.__transport.request(
  811.         self.__host,
  812.         self.__handler,
  813.         request
  814.         )
  815.     return response
  816.  
  817.     def __repr__(self):
  818.     return (
  819.         "<Server proxy for %s%s>" %
  820.         (self.__host, self.__handler)
  821.         )
  822.  
  823.     __str__ = __repr__
  824.  
  825.     def __getattr__(self, name):
  826.     # magic method dispatcher
  827.     return _Method(self.__request, name)
  828.  
  829.     def __getitem__(self, name):
  830.     # alternate method dispatcher
  831.     return _Method(self.__request, name)
  832.  
  833.  
  834. # xmlrpclib compatibility
  835. Server = ServerProxy
  836.  
  837.  
  838. if __name__ == "__main__":
  839.  
  840.     # simple test, using soapserver.py
  841.     server = ServerProxy("http://localhost:8000")
  842.     print server.call("hello")
  843.  
  844.  
  845.  
  846.  
  847.  
  848.  
  849.  
  850.  
  851.  
  852.  
  853.  
  854.  
  855.  
  856.