| # | 
 | # XML-RPC CLIENT LIBRARY | 
 | # $Id$ | 
 | # | 
 | # an XML-RPC client interface for Python. | 
 | # | 
 | # the marshalling and response parser code can also be used to | 
 | # implement XML-RPC servers. | 
 | # | 
 | # Notes: | 
 | # this version is designed to work with Python 2.1 or newer. | 
 | # | 
 | # History: | 
 | # 1999-01-14 fl  Created | 
 | # 1999-01-15 fl  Changed dateTime to use localtime | 
 | # 1999-01-16 fl  Added Binary/base64 element, default to RPC2 service | 
 | # 1999-01-19 fl  Fixed array data element (from Skip Montanaro) | 
 | # 1999-01-21 fl  Fixed dateTime constructor, etc. | 
 | # 1999-02-02 fl  Added fault handling, handle empty sequences, etc. | 
 | # 1999-02-10 fl  Fixed problem with empty responses (from Skip Montanaro) | 
 | # 1999-06-20 fl  Speed improvements, pluggable parsers/transports (0.9.8) | 
 | # 2000-11-28 fl  Changed boolean to check the truth value of its argument | 
 | # 2001-02-24 fl  Added encoding/Unicode/SafeTransport patches | 
 | # 2001-02-26 fl  Added compare support to wrappers (0.9.9/1.0b1) | 
 | # 2001-03-28 fl  Make sure response tuple is a singleton | 
 | # 2001-03-29 fl  Don't require empty params element (from Nicholas Riley) | 
 | # 2001-06-10 fl  Folded in _xmlrpclib accelerator support (1.0b2) | 
 | # 2001-08-20 fl  Base xmlrpclib.Error on built-in Exception (from Paul Prescod) | 
 | # 2001-09-03 fl  Allow Transport subclass to override getparser | 
 | # 2001-09-10 fl  Lazy import of urllib, cgi, xmllib (20x import speedup) | 
 | # 2001-10-01 fl  Remove containers from memo cache when done with them | 
 | # 2001-10-01 fl  Use faster escape method (80% dumps speedup) | 
 | # 2001-10-02 fl  More dumps microtuning | 
 | # 2001-10-04 fl  Make sure import expat gets a parser (from Guido van Rossum) | 
 | # 2001-10-10 sm  Allow long ints to be passed as ints if they don't overflow | 
 | # 2001-10-17 sm  Test for int and long overflow (allows use on 64-bit systems) | 
 | # 2001-11-12 fl  Use repr() to marshal doubles (from Paul Felix) | 
 | # 2002-03-17 fl  Avoid buffered read when possible (from James Rucker) | 
 | # 2002-04-07 fl  Added pythondoc comments | 
 | # 2002-04-16 fl  Added __str__ methods to datetime/binary wrappers | 
 | # 2002-05-15 fl  Added error constants (from Andrew Kuchling) | 
 | # 2002-06-27 fl  Merged with Python CVS version | 
 | # 2002-10-22 fl  Added basic authentication (based on code from Phillip Eby) | 
 | # 2003-01-22 sm  Add support for the bool type | 
 | # 2003-02-27 gvr Remove apply calls | 
 | # 2003-04-24 sm  Use cStringIO if available | 
 | # 2003-04-25 ak  Add support for nil | 
 | # 2003-06-15 gn  Add support for time.struct_time | 
 | # 2003-07-12 gp  Correct marshalling of Faults | 
 | # 2003-10-31 mvl Add multicall support | 
 | # 2004-08-20 mvl Bump minimum supported Python version to 2.1 | 
 | # | 
 | # Copyright (c) 1999-2002 by Secret Labs AB. | 
 | # Copyright (c) 1999-2002 by Fredrik Lundh. | 
 | # | 
 | # info@pythonware.com | 
 | # http://www.pythonware.com | 
 | # | 
 | # -------------------------------------------------------------------- | 
 | # The XML-RPC client interface is | 
 | # | 
 | # Copyright (c) 1999-2002 by Secret Labs AB | 
 | # Copyright (c) 1999-2002 by Fredrik Lundh | 
 | # | 
 | # By obtaining, using, and/or copying this software and/or its | 
 | # associated documentation, you agree that you have read, understood, | 
 | # and will comply with the following terms and conditions: | 
 | # | 
 | # Permission to use, copy, modify, and distribute this software and | 
 | # its associated documentation for any purpose and without fee is | 
 | # hereby granted, provided that the above copyright notice appears in | 
 | # all copies, and that both that copyright notice and this permission | 
 | # notice appear in supporting documentation, and that the name of | 
 | # Secret Labs AB or the author not be used in advertising or publicity | 
 | # pertaining to distribution of the software without specific, written | 
 | # prior permission. | 
 | # | 
 | # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | 
 | # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- | 
 | # ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR | 
 | # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY | 
 | # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | 
 | # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS | 
 | # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | 
 | # OF THIS SOFTWARE. | 
 | # -------------------------------------------------------------------- | 
 |  | 
 | # | 
 | # things to look into some day: | 
 |  | 
 | # TODO: sort out True/False/boolean issues for Python 2.3 | 
 |  | 
 | """ | 
 | An XML-RPC client interface for Python. | 
 |  | 
 | The marshalling and response parser code can also be used to | 
 | implement XML-RPC servers. | 
 |  | 
 | Exported exceptions: | 
 |  | 
 |   Error          Base class for client errors | 
 |   ProtocolError  Indicates an HTTP protocol error | 
 |   ResponseError  Indicates a broken response package | 
 |   Fault          Indicates an XML-RPC fault package | 
 |  | 
 | Exported classes: | 
 |  | 
 |   ServerProxy    Represents a logical connection to an XML-RPC server | 
 |  | 
 |   MultiCall      Executor of boxcared xmlrpc requests | 
 |   Boolean        boolean wrapper to generate a "boolean" XML-RPC value | 
 |   DateTime       dateTime wrapper for an ISO 8601 string or time tuple or | 
 |                  localtime integer value to generate a "dateTime.iso8601" | 
 |                  XML-RPC value | 
 |   Binary         binary data wrapper | 
 |  | 
 |   SlowParser     Slow but safe standard parser (based on xmllib) | 
 |   Marshaller     Generate an XML-RPC params chunk from a Python data structure | 
 |   Unmarshaller   Unmarshal an XML-RPC response from incoming XML event message | 
 |   Transport      Handles an HTTP transaction to an XML-RPC server | 
 |   SafeTransport  Handles an HTTPS transaction to an XML-RPC server | 
 |  | 
 | Exported constants: | 
 |  | 
 |   True | 
 |   False | 
 |  | 
 | Exported functions: | 
 |  | 
 |   boolean        Convert any Python value to an XML-RPC boolean | 
 |   getparser      Create instance of the fastest available parser & attach | 
 |                  to an unmarshalling object | 
 |   dumps          Convert an argument tuple or a Fault instance to an XML-RPC | 
 |                  request (or response, if the methodresponse option is used). | 
 |   loads          Convert an XML-RPC packet to unmarshalled data plus a method | 
 |                  name (None if not present). | 
 | """ | 
 |  | 
 | import re, string, time, operator | 
 |  | 
 | from types import * | 
 | import socket | 
 | import errno | 
 | import httplib | 
 | try: | 
 |     import gzip | 
 | except ImportError: | 
 |     gzip = None #python can be built without zlib/gzip support | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # Internal stuff | 
 |  | 
 | try: | 
 |     unicode | 
 | except NameError: | 
 |     unicode = None # unicode support not available | 
 |  | 
 | try: | 
 |     import datetime | 
 | except ImportError: | 
 |     datetime = None | 
 |  | 
 | try: | 
 |     _bool_is_builtin = False.__class__.__name__ == "bool" | 
 | except NameError: | 
 |     _bool_is_builtin = 0 | 
 |  | 
 | def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): | 
 |     # decode non-ascii string (if possible) | 
 |     if unicode and encoding and is8bit(data): | 
 |         data = unicode(data, encoding) | 
 |     return data | 
 |  | 
 | def escape(s, replace=string.replace): | 
 |     s = replace(s, "&", "&") | 
 |     s = replace(s, "<", "<") | 
 |     return replace(s, ">", ">",) | 
 |  | 
 | if unicode: | 
 |     def _stringify(string): | 
 |         # convert to 7-bit ascii if possible | 
 |         try: | 
 |             return string.encode("ascii") | 
 |         except UnicodeError: | 
 |             return string | 
 | else: | 
 |     def _stringify(string): | 
 |         return string | 
 |  | 
 | __version__ = "1.0.1" | 
 |  | 
 | # xmlrpc integer limits | 
 | MAXINT =  2L**31-1 | 
 | MININT = -2L**31 | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # Error constants (from Dan Libby's specification at | 
 | # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) | 
 |  | 
 | # Ranges of errors | 
 | PARSE_ERROR       = -32700 | 
 | SERVER_ERROR      = -32600 | 
 | APPLICATION_ERROR = -32500 | 
 | SYSTEM_ERROR      = -32400 | 
 | TRANSPORT_ERROR   = -32300 | 
 |  | 
 | # Specific errors | 
 | NOT_WELLFORMED_ERROR  = -32700 | 
 | UNSUPPORTED_ENCODING  = -32701 | 
 | INVALID_ENCODING_CHAR = -32702 | 
 | INVALID_XMLRPC        = -32600 | 
 | METHOD_NOT_FOUND      = -32601 | 
 | INVALID_METHOD_PARAMS = -32602 | 
 | INTERNAL_ERROR        = -32603 | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # Exceptions | 
 |  | 
 | ## | 
 | # Base class for all kinds of client-side errors. | 
 |  | 
 | class Error(Exception): | 
 |     """Base class for client errors.""" | 
 |     def __str__(self): | 
 |         return repr(self) | 
 |  | 
 | ## | 
 | # Indicates an HTTP-level protocol error.  This is raised by the HTTP | 
 | # transport layer, if the server returns an error code other than 200 | 
 | # (OK). | 
 | # | 
 | # @param url The target URL. | 
 | # @param errcode The HTTP error code. | 
 | # @param errmsg The HTTP error message. | 
 | # @param headers The HTTP header dictionary. | 
 |  | 
 | class ProtocolError(Error): | 
 |     """Indicates an HTTP protocol error.""" | 
 |     def __init__(self, url, errcode, errmsg, headers): | 
 |         Error.__init__(self) | 
 |         self.url = url | 
 |         self.errcode = errcode | 
 |         self.errmsg = errmsg | 
 |         self.headers = headers | 
 |     def __repr__(self): | 
 |         return ( | 
 |             "<ProtocolError for %s: %s %s>" % | 
 |             (self.url, self.errcode, self.errmsg) | 
 |             ) | 
 |  | 
 | ## | 
 | # Indicates a broken XML-RPC response package.  This exception is | 
 | # raised by the unmarshalling layer, if the XML-RPC response is | 
 | # malformed. | 
 |  | 
 | class ResponseError(Error): | 
 |     """Indicates a broken response package.""" | 
 |     pass | 
 |  | 
 | ## | 
 | # Indicates an XML-RPC fault response package.  This exception is | 
 | # raised by the unmarshalling layer, if the XML-RPC response contains | 
 | # a fault string.  This exception can also used as a class, to | 
 | # generate a fault XML-RPC message. | 
 | # | 
 | # @param faultCode The XML-RPC fault code. | 
 | # @param faultString The XML-RPC fault string. | 
 |  | 
 | class Fault(Error): | 
 |     """Indicates an XML-RPC fault package.""" | 
 |     def __init__(self, faultCode, faultString, **extra): | 
 |         Error.__init__(self) | 
 |         self.faultCode = faultCode | 
 |         self.faultString = faultString | 
 |     def __repr__(self): | 
 |         return ( | 
 |             "<Fault %s: %s>" % | 
 |             (self.faultCode, repr(self.faultString)) | 
 |             ) | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # Special values | 
 |  | 
 | ## | 
 | # Wrapper for XML-RPC boolean values.  Use the xmlrpclib.True and | 
 | # xmlrpclib.False constants, or the xmlrpclib.boolean() function, to | 
 | # generate boolean XML-RPC values. | 
 | # | 
 | # @param value A boolean value.  Any true value is interpreted as True, | 
 | #              all other values are interpreted as False. | 
 |  | 
 | from sys import modules | 
 | mod_dict = modules[__name__].__dict__ | 
 | if _bool_is_builtin: | 
 |     boolean = Boolean = bool | 
 |     # to avoid breaking code which references xmlrpclib.{True,False} | 
 |     mod_dict['True'] = True | 
 |     mod_dict['False'] = False | 
 | else: | 
 |     class Boolean: | 
 |         """Boolean-value wrapper. | 
 |  | 
 |         Use True or False to generate a "boolean" XML-RPC value. | 
 |         """ | 
 |  | 
 |         def __init__(self, value = 0): | 
 |             self.value = operator.truth(value) | 
 |  | 
 |         def encode(self, out): | 
 |             out.write("<value><boolean>%d</boolean></value>\n" % self.value) | 
 |  | 
 |         def __cmp__(self, other): | 
 |             if isinstance(other, Boolean): | 
 |                 other = other.value | 
 |             return cmp(self.value, other) | 
 |  | 
 |         def __repr__(self): | 
 |             if self.value: | 
 |                 return "<Boolean True at %x>" % id(self) | 
 |             else: | 
 |                 return "<Boolean False at %x>" % id(self) | 
 |  | 
 |         def __int__(self): | 
 |             return self.value | 
 |  | 
 |         def __nonzero__(self): | 
 |             return self.value | 
 |  | 
 |     mod_dict['True'] = Boolean(1) | 
 |     mod_dict['False'] = Boolean(0) | 
 |  | 
 |     ## | 
 |     # Map true or false value to XML-RPC boolean values. | 
 |     # | 
 |     # @def boolean(value) | 
 |     # @param value A boolean value.  Any true value is mapped to True, | 
 |     #              all other values are mapped to False. | 
 |     # @return xmlrpclib.True or xmlrpclib.False. | 
 |     # @see Boolean | 
 |     # @see True | 
 |     # @see False | 
 |  | 
 |     def boolean(value, _truefalse=(False, True)): | 
 |         """Convert any Python value to XML-RPC 'boolean'.""" | 
 |         return _truefalse[operator.truth(value)] | 
 |  | 
 | del modules, mod_dict | 
 |  | 
 | ## | 
 | # Wrapper for XML-RPC DateTime values.  This converts a time value to | 
 | # the format used by XML-RPC. | 
 | # <p> | 
 | # The value can be given as a string in the format | 
 | # "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by | 
 | # time.localtime()), or an integer value (as returned by time.time()). | 
 | # The wrapper uses time.localtime() to convert an integer to a time | 
 | # tuple. | 
 | # | 
 | # @param value The time, given as an ISO 8601 string, a time | 
 | #              tuple, or a integer time value. | 
 |  | 
 | def _strftime(value): | 
 |     if datetime: | 
 |         if isinstance(value, datetime.datetime): | 
 |             return "%04d%02d%02dT%02d:%02d:%02d" % ( | 
 |                 value.year, value.month, value.day, | 
 |                 value.hour, value.minute, value.second) | 
 |  | 
 |     if not isinstance(value, (TupleType, time.struct_time)): | 
 |         if value == 0: | 
 |             value = time.time() | 
 |         value = time.localtime(value) | 
 |  | 
 |     return "%04d%02d%02dT%02d:%02d:%02d" % value[:6] | 
 |  | 
 | class DateTime: | 
 |     """DateTime wrapper for an ISO 8601 string or time tuple or | 
 |     localtime integer value to generate 'dateTime.iso8601' XML-RPC | 
 |     value. | 
 |     """ | 
 |  | 
 |     def __init__(self, value=0): | 
 |         if isinstance(value, StringType): | 
 |             self.value = value | 
 |         else: | 
 |             self.value = _strftime(value) | 
 |  | 
 |     def make_comparable(self, other): | 
 |         if isinstance(other, DateTime): | 
 |             s = self.value | 
 |             o = other.value | 
 |         elif datetime and isinstance(other, datetime.datetime): | 
 |             s = self.value | 
 |             o = other.strftime("%Y%m%dT%H:%M:%S") | 
 |         elif isinstance(other, (str, unicode)): | 
 |             s = self.value | 
 |             o = other | 
 |         elif hasattr(other, "timetuple"): | 
 |             s = self.timetuple() | 
 |             o = other.timetuple() | 
 |         else: | 
 |             otype = (hasattr(other, "__class__") | 
 |                      and other.__class__.__name__ | 
 |                      or type(other)) | 
 |             raise TypeError("Can't compare %s and %s" % | 
 |                             (self.__class__.__name__, otype)) | 
 |         return s, o | 
 |  | 
 |     def __lt__(self, other): | 
 |         s, o = self.make_comparable(other) | 
 |         return s < o | 
 |  | 
 |     def __le__(self, other): | 
 |         s, o = self.make_comparable(other) | 
 |         return s <= o | 
 |  | 
 |     def __gt__(self, other): | 
 |         s, o = self.make_comparable(other) | 
 |         return s > o | 
 |  | 
 |     def __ge__(self, other): | 
 |         s, o = self.make_comparable(other) | 
 |         return s >= o | 
 |  | 
 |     def __eq__(self, other): | 
 |         s, o = self.make_comparable(other) | 
 |         return s == o | 
 |  | 
 |     def __ne__(self, other): | 
 |         s, o = self.make_comparable(other) | 
 |         return s != o | 
 |  | 
 |     def timetuple(self): | 
 |         return time.strptime(self.value, "%Y%m%dT%H:%M:%S") | 
 |  | 
 |     def __cmp__(self, other): | 
 |         s, o = self.make_comparable(other) | 
 |         return cmp(s, o) | 
 |  | 
 |     ## | 
 |     # Get date/time value. | 
 |     # | 
 |     # @return Date/time value, as an ISO 8601 string. | 
 |  | 
 |     def __str__(self): | 
 |         return self.value | 
 |  | 
 |     def __repr__(self): | 
 |         return "<DateTime %s at %x>" % (repr(self.value), id(self)) | 
 |  | 
 |     def decode(self, data): | 
 |         data = str(data) | 
 |         self.value = string.strip(data) | 
 |  | 
 |     def encode(self, out): | 
 |         out.write("<value><dateTime.iso8601>") | 
 |         out.write(self.value) | 
 |         out.write("</dateTime.iso8601></value>\n") | 
 |  | 
 | def _datetime(data): | 
 |     # decode xml element contents into a DateTime structure. | 
 |     value = DateTime() | 
 |     value.decode(data) | 
 |     return value | 
 |  | 
 | def _datetime_type(data): | 
 |     t = time.strptime(data, "%Y%m%dT%H:%M:%S") | 
 |     return datetime.datetime(*tuple(t)[:6]) | 
 |  | 
 | ## | 
 | # Wrapper for binary data.  This can be used to transport any kind | 
 | # of binary data over XML-RPC, using BASE64 encoding. | 
 | # | 
 | # @param data An 8-bit string containing arbitrary data. | 
 |  | 
 | import base64 | 
 | try: | 
 |     import cStringIO as StringIO | 
 | except ImportError: | 
 |     import StringIO | 
 |  | 
 | class Binary: | 
 |     """Wrapper for binary data.""" | 
 |  | 
 |     def __init__(self, data=None): | 
 |         self.data = data | 
 |  | 
 |     ## | 
 |     # Get buffer contents. | 
 |     # | 
 |     # @return Buffer contents, as an 8-bit string. | 
 |  | 
 |     def __str__(self): | 
 |         return self.data or "" | 
 |  | 
 |     def __cmp__(self, other): | 
 |         if isinstance(other, Binary): | 
 |             other = other.data | 
 |         return cmp(self.data, other) | 
 |  | 
 |     def decode(self, data): | 
 |         self.data = base64.decodestring(data) | 
 |  | 
 |     def encode(self, out): | 
 |         out.write("<value><base64>\n") | 
 |         base64.encode(StringIO.StringIO(self.data), out) | 
 |         out.write("</base64></value>\n") | 
 |  | 
 | def _binary(data): | 
 |     # decode xml element contents into a Binary structure | 
 |     value = Binary() | 
 |     value.decode(data) | 
 |     return value | 
 |  | 
 | WRAPPERS = (DateTime, Binary) | 
 | if not _bool_is_builtin: | 
 |     WRAPPERS = WRAPPERS + (Boolean,) | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # XML parsers | 
 |  | 
 | try: | 
 |     # optional xmlrpclib accelerator | 
 |     import _xmlrpclib | 
 |     FastParser = _xmlrpclib.Parser | 
 |     FastUnmarshaller = _xmlrpclib.Unmarshaller | 
 | except (AttributeError, ImportError): | 
 |     FastParser = FastUnmarshaller = None | 
 |  | 
 | try: | 
 |     import _xmlrpclib | 
 |     FastMarshaller = _xmlrpclib.Marshaller | 
 | except (AttributeError, ImportError): | 
 |     FastMarshaller = None | 
 |  | 
 | try: | 
 |     from xml.parsers import expat | 
 |     if not hasattr(expat, "ParserCreate"): | 
 |         raise ImportError | 
 | except ImportError: | 
 |     ExpatParser = None # expat not available | 
 | else: | 
 |     class ExpatParser: | 
 |         # fast expat parser for Python 2.0 and later. | 
 |         def __init__(self, target): | 
 |             self._parser = parser = expat.ParserCreate(None, None) | 
 |             self._target = target | 
 |             parser.StartElementHandler = target.start | 
 |             parser.EndElementHandler = target.end | 
 |             parser.CharacterDataHandler = target.data | 
 |             encoding = None | 
 |             if not parser.returns_unicode: | 
 |                 encoding = "utf-8" | 
 |             target.xml(encoding, None) | 
 |  | 
 |         def feed(self, data): | 
 |             self._parser.Parse(data, 0) | 
 |  | 
 |         def close(self): | 
 |             self._parser.Parse("", 1) # end of data | 
 |             del self._target, self._parser # get rid of circular references | 
 |  | 
 | class SlowParser: | 
 |     """Default XML parser (based on xmllib.XMLParser).""" | 
 |     # this is the slowest parser. | 
 |     def __init__(self, target): | 
 |         import xmllib # lazy subclassing (!) | 
 |         if xmllib.XMLParser not in SlowParser.__bases__: | 
 |             SlowParser.__bases__ = (xmllib.XMLParser,) | 
 |         self.handle_xml = target.xml | 
 |         self.unknown_starttag = target.start | 
 |         self.handle_data = target.data | 
 |         self.handle_cdata = target.data | 
 |         self.unknown_endtag = target.end | 
 |         try: | 
 |             xmllib.XMLParser.__init__(self, accept_utf8=1) | 
 |         except TypeError: | 
 |             xmllib.XMLParser.__init__(self) # pre-2.0 | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # XML-RPC marshalling and unmarshalling code | 
 |  | 
 | ## | 
 | # XML-RPC marshaller. | 
 | # | 
 | # @param encoding Default encoding for 8-bit strings.  The default | 
 | #     value is None (interpreted as UTF-8). | 
 | # @see dumps | 
 |  | 
 | class Marshaller: | 
 |     """Generate an XML-RPC params chunk from a Python data structure. | 
 |  | 
 |     Create a Marshaller instance for each set of parameters, and use | 
 |     the "dumps" method to convert your data (represented as a tuple) | 
 |     to an XML-RPC params chunk.  To write a fault response, pass a | 
 |     Fault instance instead.  You may prefer to use the "dumps" module | 
 |     function for this purpose. | 
 |     """ | 
 |  | 
 |     # by the way, if you don't understand what's going on in here, | 
 |     # that's perfectly ok. | 
 |  | 
 |     def __init__(self, encoding=None, allow_none=0): | 
 |         self.memo = {} | 
 |         self.data = None | 
 |         self.encoding = encoding | 
 |         self.allow_none = allow_none | 
 |  | 
 |     dispatch = {} | 
 |  | 
 |     def dumps(self, values): | 
 |         out = [] | 
 |         write = out.append | 
 |         dump = self.__dump | 
 |         if isinstance(values, Fault): | 
 |             # fault instance | 
 |             write("<fault>\n") | 
 |             dump({'faultCode': values.faultCode, | 
 |                   'faultString': values.faultString}, | 
 |                  write) | 
 |             write("</fault>\n") | 
 |         else: | 
 |             # parameter block | 
 |             # FIXME: the xml-rpc specification allows us to leave out | 
 |             # the entire <params> block if there are no parameters. | 
 |             # however, changing this may break older code (including | 
 |             # old versions of xmlrpclib.py), so this is better left as | 
 |             # is for now.  See @XMLRPC3 for more information. /F | 
 |             write("<params>\n") | 
 |             for v in values: | 
 |                 write("<param>\n") | 
 |                 dump(v, write) | 
 |                 write("</param>\n") | 
 |             write("</params>\n") | 
 |         result = string.join(out, "") | 
 |         return result | 
 |  | 
 |     def __dump(self, value, write): | 
 |         try: | 
 |             f = self.dispatch[type(value)] | 
 |         except KeyError: | 
 |             # check if this object can be marshalled as a structure | 
 |             try: | 
 |                 value.__dict__ | 
 |             except: | 
 |                 raise TypeError, "cannot marshal %s objects" % type(value) | 
 |             # check if this class is a sub-class of a basic type, | 
 |             # because we don't know how to marshal these types | 
 |             # (e.g. a string sub-class) | 
 |             for type_ in type(value).__mro__: | 
 |                 if type_ in self.dispatch.keys(): | 
 |                     raise TypeError, "cannot marshal %s objects" % type(value) | 
 |             f = self.dispatch[InstanceType] | 
 |         f(self, value, write) | 
 |  | 
 |     def dump_nil (self, value, write): | 
 |         if not self.allow_none: | 
 |             raise TypeError, "cannot marshal None unless allow_none is enabled" | 
 |         write("<value><nil/></value>") | 
 |     dispatch[NoneType] = dump_nil | 
 |  | 
 |     def dump_int(self, value, write): | 
 |         # in case ints are > 32 bits | 
 |         if value > MAXINT or value < MININT: | 
 |             raise OverflowError, "int exceeds XML-RPC limits" | 
 |         write("<value><int>") | 
 |         write(str(value)) | 
 |         write("</int></value>\n") | 
 |     dispatch[IntType] = dump_int | 
 |  | 
 |     if _bool_is_builtin: | 
 |         def dump_bool(self, value, write): | 
 |             write("<value><boolean>") | 
 |             write(value and "1" or "0") | 
 |             write("</boolean></value>\n") | 
 |         dispatch[bool] = dump_bool | 
 |  | 
 |     def dump_long(self, value, write): | 
 |         if value > MAXINT or value < MININT: | 
 |             raise OverflowError, "long int exceeds XML-RPC limits" | 
 |         write("<value><int>") | 
 |         write(str(int(value))) | 
 |         write("</int></value>\n") | 
 |     dispatch[LongType] = dump_long | 
 |  | 
 |     def dump_double(self, value, write): | 
 |         write("<value><double>") | 
 |         write(repr(value)) | 
 |         write("</double></value>\n") | 
 |     dispatch[FloatType] = dump_double | 
 |  | 
 |     def dump_string(self, value, write, escape=escape): | 
 |         write("<value><string>") | 
 |         write(escape(value)) | 
 |         write("</string></value>\n") | 
 |     dispatch[StringType] = dump_string | 
 |  | 
 |     if unicode: | 
 |         def dump_unicode(self, value, write, escape=escape): | 
 |             value = value.encode(self.encoding) | 
 |             write("<value><string>") | 
 |             write(escape(value)) | 
 |             write("</string></value>\n") | 
 |         dispatch[UnicodeType] = dump_unicode | 
 |  | 
 |     def dump_array(self, value, write): | 
 |         i = id(value) | 
 |         if i in self.memo: | 
 |             raise TypeError, "cannot marshal recursive sequences" | 
 |         self.memo[i] = None | 
 |         dump = self.__dump | 
 |         write("<value><array><data>\n") | 
 |         for v in value: | 
 |             dump(v, write) | 
 |         write("</data></array></value>\n") | 
 |         del self.memo[i] | 
 |     dispatch[TupleType] = dump_array | 
 |     dispatch[ListType] = dump_array | 
 |  | 
 |     def dump_struct(self, value, write, escape=escape): | 
 |         i = id(value) | 
 |         if i in self.memo: | 
 |             raise TypeError, "cannot marshal recursive dictionaries" | 
 |         self.memo[i] = None | 
 |         dump = self.__dump | 
 |         write("<value><struct>\n") | 
 |         for k, v in value.items(): | 
 |             write("<member>\n") | 
 |             if type(k) is not StringType: | 
 |                 if unicode and type(k) is UnicodeType: | 
 |                     k = k.encode(self.encoding) | 
 |                 else: | 
 |                     raise TypeError, "dictionary key must be string" | 
 |             write("<name>%s</name>\n" % escape(k)) | 
 |             dump(v, write) | 
 |             write("</member>\n") | 
 |         write("</struct></value>\n") | 
 |         del self.memo[i] | 
 |     dispatch[DictType] = dump_struct | 
 |  | 
 |     if datetime: | 
 |         def dump_datetime(self, value, write): | 
 |             write("<value><dateTime.iso8601>") | 
 |             write(_strftime(value)) | 
 |             write("</dateTime.iso8601></value>\n") | 
 |         dispatch[datetime.datetime] = dump_datetime | 
 |  | 
 |     def dump_instance(self, value, write): | 
 |         # check for special wrappers | 
 |         if value.__class__ in WRAPPERS: | 
 |             self.write = write | 
 |             value.encode(self) | 
 |             del self.write | 
 |         else: | 
 |             # store instance attributes as a struct (really?) | 
 |             self.dump_struct(value.__dict__, write) | 
 |     dispatch[InstanceType] = dump_instance | 
 |  | 
 | ## | 
 | # XML-RPC unmarshaller. | 
 | # | 
 | # @see loads | 
 |  | 
 | class Unmarshaller: | 
 |     """Unmarshal an XML-RPC response, based on incoming XML event | 
 |     messages (start, data, end).  Call close() to get the resulting | 
 |     data structure. | 
 |  | 
 |     Note that this reader is fairly tolerant, and gladly accepts bogus | 
 |     XML-RPC data without complaining (but not bogus XML). | 
 |     """ | 
 |  | 
 |     # and again, if you don't understand what's going on in here, | 
 |     # that's perfectly ok. | 
 |  | 
 |     def __init__(self, use_datetime=0): | 
 |         self._type = None | 
 |         self._stack = [] | 
 |         self._marks = [] | 
 |         self._data = [] | 
 |         self._methodname = None | 
 |         self._encoding = "utf-8" | 
 |         self.append = self._stack.append | 
 |         self._use_datetime = use_datetime | 
 |         if use_datetime and not datetime: | 
 |             raise ValueError, "the datetime module is not available" | 
 |  | 
 |     def close(self): | 
 |         # return response tuple and target method | 
 |         if self._type is None or self._marks: | 
 |             raise ResponseError() | 
 |         if self._type == "fault": | 
 |             raise Fault(**self._stack[0]) | 
 |         return tuple(self._stack) | 
 |  | 
 |     def getmethodname(self): | 
 |         return self._methodname | 
 |  | 
 |     # | 
 |     # event handlers | 
 |  | 
 |     def xml(self, encoding, standalone): | 
 |         self._encoding = encoding | 
 |         # FIXME: assert standalone == 1 ??? | 
 |  | 
 |     def start(self, tag, attrs): | 
 |         # prepare to handle this element | 
 |         if tag == "array" or tag == "struct": | 
 |             self._marks.append(len(self._stack)) | 
 |         self._data = [] | 
 |         self._value = (tag == "value") | 
 |  | 
 |     def data(self, text): | 
 |         self._data.append(text) | 
 |  | 
 |     def end(self, tag, join=string.join): | 
 |         # call the appropriate end tag handler | 
 |         try: | 
 |             f = self.dispatch[tag] | 
 |         except KeyError: | 
 |             pass # unknown tag ? | 
 |         else: | 
 |             return f(self, join(self._data, "")) | 
 |  | 
 |     # | 
 |     # accelerator support | 
 |  | 
 |     def end_dispatch(self, tag, data): | 
 |         # dispatch data | 
 |         try: | 
 |             f = self.dispatch[tag] | 
 |         except KeyError: | 
 |             pass # unknown tag ? | 
 |         else: | 
 |             return f(self, data) | 
 |  | 
 |     # | 
 |     # element decoders | 
 |  | 
 |     dispatch = {} | 
 |  | 
 |     def end_nil (self, data): | 
 |         self.append(None) | 
 |         self._value = 0 | 
 |     dispatch["nil"] = end_nil | 
 |  | 
 |     def end_boolean(self, data): | 
 |         if data == "0": | 
 |             self.append(False) | 
 |         elif data == "1": | 
 |             self.append(True) | 
 |         else: | 
 |             raise TypeError, "bad boolean value" | 
 |         self._value = 0 | 
 |     dispatch["boolean"] = end_boolean | 
 |  | 
 |     def end_int(self, data): | 
 |         self.append(int(data)) | 
 |         self._value = 0 | 
 |     dispatch["i4"] = end_int | 
 |     dispatch["i8"] = end_int | 
 |     dispatch["int"] = end_int | 
 |  | 
 |     def end_double(self, data): | 
 |         self.append(float(data)) | 
 |         self._value = 0 | 
 |     dispatch["double"] = end_double | 
 |  | 
 |     def end_string(self, data): | 
 |         if self._encoding: | 
 |             data = _decode(data, self._encoding) | 
 |         self.append(_stringify(data)) | 
 |         self._value = 0 | 
 |     dispatch["string"] = end_string | 
 |     dispatch["name"] = end_string # struct keys are always strings | 
 |  | 
 |     def end_array(self, data): | 
 |         mark = self._marks.pop() | 
 |         # map arrays to Python lists | 
 |         self._stack[mark:] = [self._stack[mark:]] | 
 |         self._value = 0 | 
 |     dispatch["array"] = end_array | 
 |  | 
 |     def end_struct(self, data): | 
 |         mark = self._marks.pop() | 
 |         # map structs to Python dictionaries | 
 |         dict = {} | 
 |         items = self._stack[mark:] | 
 |         for i in range(0, len(items), 2): | 
 |             dict[_stringify(items[i])] = items[i+1] | 
 |         self._stack[mark:] = [dict] | 
 |         self._value = 0 | 
 |     dispatch["struct"] = end_struct | 
 |  | 
 |     def end_base64(self, data): | 
 |         value = Binary() | 
 |         value.decode(data) | 
 |         self.append(value) | 
 |         self._value = 0 | 
 |     dispatch["base64"] = end_base64 | 
 |  | 
 |     def end_dateTime(self, data): | 
 |         value = DateTime() | 
 |         value.decode(data) | 
 |         if self._use_datetime: | 
 |             value = _datetime_type(data) | 
 |         self.append(value) | 
 |     dispatch["dateTime.iso8601"] = end_dateTime | 
 |  | 
 |     def end_value(self, data): | 
 |         # if we stumble upon a value element with no internal | 
 |         # elements, treat it as a string element | 
 |         if self._value: | 
 |             self.end_string(data) | 
 |     dispatch["value"] = end_value | 
 |  | 
 |     def end_params(self, data): | 
 |         self._type = "params" | 
 |     dispatch["params"] = end_params | 
 |  | 
 |     def end_fault(self, data): | 
 |         self._type = "fault" | 
 |     dispatch["fault"] = end_fault | 
 |  | 
 |     def end_methodName(self, data): | 
 |         if self._encoding: | 
 |             data = _decode(data, self._encoding) | 
 |         self._methodname = data | 
 |         self._type = "methodName" # no params | 
 |     dispatch["methodName"] = end_methodName | 
 |  | 
 | ## Multicall support | 
 | # | 
 |  | 
 | class _MultiCallMethod: | 
 |     # some lesser magic to store calls made to a MultiCall object | 
 |     # for batch execution | 
 |     def __init__(self, call_list, name): | 
 |         self.__call_list = call_list | 
 |         self.__name = name | 
 |     def __getattr__(self, name): | 
 |         return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name)) | 
 |     def __call__(self, *args): | 
 |         self.__call_list.append((self.__name, args)) | 
 |  | 
 | class MultiCallIterator: | 
 |     """Iterates over the results of a multicall. Exceptions are | 
 |     thrown in response to xmlrpc faults.""" | 
 |  | 
 |     def __init__(self, results): | 
 |         self.results = results | 
 |  | 
 |     def __getitem__(self, i): | 
 |         item = self.results[i] | 
 |         if type(item) == type({}): | 
 |             raise Fault(item['faultCode'], item['faultString']) | 
 |         elif type(item) == type([]): | 
 |             return item[0] | 
 |         else: | 
 |             raise ValueError,\ | 
 |                   "unexpected type in multicall result" | 
 |  | 
 | class MultiCall: | 
 |     """server -> a object used to boxcar method calls | 
 |  | 
 |     server should be a ServerProxy object. | 
 |  | 
 |     Methods can be added to the MultiCall using normal | 
 |     method call syntax e.g.: | 
 |  | 
 |     multicall = MultiCall(server_proxy) | 
 |     multicall.add(2,3) | 
 |     multicall.get_address("Guido") | 
 |  | 
 |     To execute the multicall, call the MultiCall object e.g.: | 
 |  | 
 |     add_result, address = multicall() | 
 |     """ | 
 |  | 
 |     def __init__(self, server): | 
 |         self.__server = server | 
 |         self.__call_list = [] | 
 |  | 
 |     def __repr__(self): | 
 |         return "<MultiCall at %x>" % id(self) | 
 |  | 
 |     __str__ = __repr__ | 
 |  | 
 |     def __getattr__(self, name): | 
 |         return _MultiCallMethod(self.__call_list, name) | 
 |  | 
 |     def __call__(self): | 
 |         marshalled_list = [] | 
 |         for name, args in self.__call_list: | 
 |             marshalled_list.append({'methodName' : name, 'params' : args}) | 
 |  | 
 |         return MultiCallIterator(self.__server.system.multicall(marshalled_list)) | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # convenience functions | 
 |  | 
 | ## | 
 | # Create a parser object, and connect it to an unmarshalling instance. | 
 | # This function picks the fastest available XML parser. | 
 | # | 
 | # return A (parser, unmarshaller) tuple. | 
 |  | 
 | def getparser(use_datetime=0): | 
 |     """getparser() -> parser, unmarshaller | 
 |  | 
 |     Create an instance of the fastest available parser, and attach it | 
 |     to an unmarshalling object.  Return both objects. | 
 |     """ | 
 |     if use_datetime and not datetime: | 
 |         raise ValueError, "the datetime module is not available" | 
 |     if FastParser and FastUnmarshaller: | 
 |         if use_datetime: | 
 |             mkdatetime = _datetime_type | 
 |         else: | 
 |             mkdatetime = _datetime | 
 |         target = FastUnmarshaller(True, False, _binary, mkdatetime, Fault) | 
 |         parser = FastParser(target) | 
 |     else: | 
 |         target = Unmarshaller(use_datetime=use_datetime) | 
 |         if FastParser: | 
 |             parser = FastParser(target) | 
 |         elif ExpatParser: | 
 |             parser = ExpatParser(target) | 
 |         else: | 
 |             parser = SlowParser(target) | 
 |     return parser, target | 
 |  | 
 | ## | 
 | # Convert a Python tuple or a Fault instance to an XML-RPC packet. | 
 | # | 
 | # @def dumps(params, **options) | 
 | # @param params A tuple or Fault instance. | 
 | # @keyparam methodname If given, create a methodCall request for | 
 | #     this method name. | 
 | # @keyparam methodresponse If given, create a methodResponse packet. | 
 | #     If used with a tuple, the tuple must be a singleton (that is, | 
 | #     it must contain exactly one element). | 
 | # @keyparam encoding The packet encoding. | 
 | # @return A string containing marshalled data. | 
 |  | 
 | def dumps(params, methodname=None, methodresponse=None, encoding=None, | 
 |           allow_none=0): | 
 |     """data [,options] -> marshalled data | 
 |  | 
 |     Convert an argument tuple or a Fault instance to an XML-RPC | 
 |     request (or response, if the methodresponse option is used). | 
 |  | 
 |     In addition to the data object, the following options can be given | 
 |     as keyword arguments: | 
 |  | 
 |         methodname: the method name for a methodCall packet | 
 |  | 
 |         methodresponse: true to create a methodResponse packet. | 
 |         If this option is used with a tuple, the tuple must be | 
 |         a singleton (i.e. it can contain only one element). | 
 |  | 
 |         encoding: the packet encoding (default is UTF-8) | 
 |  | 
 |     All 8-bit strings in the data structure are assumed to use the | 
 |     packet encoding.  Unicode strings are automatically converted, | 
 |     where necessary. | 
 |     """ | 
 |  | 
 |     assert isinstance(params, TupleType) or isinstance(params, Fault),\ | 
 |            "argument must be tuple or Fault instance" | 
 |  | 
 |     if isinstance(params, Fault): | 
 |         methodresponse = 1 | 
 |     elif methodresponse and isinstance(params, TupleType): | 
 |         assert len(params) == 1, "response tuple must be a singleton" | 
 |  | 
 |     if not encoding: | 
 |         encoding = "utf-8" | 
 |  | 
 |     if FastMarshaller: | 
 |         m = FastMarshaller(encoding) | 
 |     else: | 
 |         m = Marshaller(encoding, allow_none) | 
 |  | 
 |     data = m.dumps(params) | 
 |  | 
 |     if encoding != "utf-8": | 
 |         xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding) | 
 |     else: | 
 |         xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default | 
 |  | 
 |     # standard XML-RPC wrappings | 
 |     if methodname: | 
 |         # a method call | 
 |         if not isinstance(methodname, StringType): | 
 |             methodname = methodname.encode(encoding) | 
 |         data = ( | 
 |             xmlheader, | 
 |             "<methodCall>\n" | 
 |             "<methodName>", methodname, "</methodName>\n", | 
 |             data, | 
 |             "</methodCall>\n" | 
 |             ) | 
 |     elif methodresponse: | 
 |         # a method response, or a fault structure | 
 |         data = ( | 
 |             xmlheader, | 
 |             "<methodResponse>\n", | 
 |             data, | 
 |             "</methodResponse>\n" | 
 |             ) | 
 |     else: | 
 |         return data # return as is | 
 |     return string.join(data, "") | 
 |  | 
 | ## | 
 | # Convert an XML-RPC packet to a Python object.  If the XML-RPC packet | 
 | # represents a fault condition, this function raises a Fault exception. | 
 | # | 
 | # @param data An XML-RPC packet, given as an 8-bit string. | 
 | # @return A tuple containing the unpacked data, and the method name | 
 | #     (None if not present). | 
 | # @see Fault | 
 |  | 
 | def loads(data, use_datetime=0): | 
 |     """data -> unmarshalled data, method name | 
 |  | 
 |     Convert an XML-RPC packet to unmarshalled data plus a method | 
 |     name (None if not present). | 
 |  | 
 |     If the XML-RPC packet represents a fault condition, this function | 
 |     raises a Fault exception. | 
 |     """ | 
 |     p, u = getparser(use_datetime=use_datetime) | 
 |     p.feed(data) | 
 |     p.close() | 
 |     return u.close(), u.getmethodname() | 
 |  | 
 | ## | 
 | # Encode a string using the gzip content encoding such as specified by the | 
 | # Content-Encoding: gzip | 
 | # in the HTTP header, as described in RFC 1952 | 
 | # | 
 | # @param data the unencoded data | 
 | # @return the encoded data | 
 |  | 
 | def gzip_encode(data): | 
 |     """data -> gzip encoded data | 
 |  | 
 |     Encode data using the gzip content encoding as described in RFC 1952 | 
 |     """ | 
 |     if not gzip: | 
 |         raise NotImplementedError | 
 |     f = StringIO.StringIO() | 
 |     gzf = gzip.GzipFile(mode="wb", fileobj=f, compresslevel=1) | 
 |     gzf.write(data) | 
 |     gzf.close() | 
 |     encoded = f.getvalue() | 
 |     f.close() | 
 |     return encoded | 
 |  | 
 | ## | 
 | # Decode a string using the gzip content encoding such as specified by the | 
 | # Content-Encoding: gzip | 
 | # in the HTTP header, as described in RFC 1952 | 
 | # | 
 | # @param data The encoded data | 
 | # @return the unencoded data | 
 | # @raises ValueError if data is not correctly coded. | 
 |  | 
 | def gzip_decode(data): | 
 |     """gzip encoded data -> unencoded data | 
 |  | 
 |     Decode data using the gzip content encoding as described in RFC 1952 | 
 |     """ | 
 |     if not gzip: | 
 |         raise NotImplementedError | 
 |     f = StringIO.StringIO(data) | 
 |     gzf = gzip.GzipFile(mode="rb", fileobj=f) | 
 |     try: | 
 |         decoded = gzf.read() | 
 |     except IOError: | 
 |         raise ValueError("invalid data") | 
 |     f.close() | 
 |     gzf.close() | 
 |     return decoded | 
 |  | 
 | ## | 
 | # Return a decoded file-like object for the gzip encoding | 
 | # as described in RFC 1952. | 
 | # | 
 | # @param response A stream supporting a read() method | 
 | # @return a file-like object that the decoded data can be read() from | 
 |  | 
 | class GzipDecodedResponse(gzip.GzipFile if gzip else object): | 
 |     """a file-like object to decode a response encoded with the gzip | 
 |     method, as described in RFC 1952. | 
 |     """ | 
 |     def __init__(self, response): | 
 |         #response doesn't support tell() and read(), required by | 
 |         #GzipFile | 
 |         if not gzip: | 
 |             raise NotImplementedError | 
 |         self.stringio = StringIO.StringIO(response.read()) | 
 |         gzip.GzipFile.__init__(self, mode="rb", fileobj=self.stringio) | 
 |  | 
 |     def close(self): | 
 |         gzip.GzipFile.close(self) | 
 |         self.stringio.close() | 
 |  | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # request dispatcher | 
 |  | 
 | class _Method: | 
 |     # some magic to bind an XML-RPC method to an RPC server. | 
 |     # supports "nested" methods (e.g. examples.getStateName) | 
 |     def __init__(self, send, name): | 
 |         self.__send = send | 
 |         self.__name = name | 
 |     def __getattr__(self, name): | 
 |         return _Method(self.__send, "%s.%s" % (self.__name, name)) | 
 |     def __call__(self, *args): | 
 |         return self.__send(self.__name, args) | 
 |  | 
 | ## | 
 | # Standard transport class for XML-RPC over HTTP. | 
 | # <p> | 
 | # You can create custom transports by subclassing this method, and | 
 | # overriding selected methods. | 
 |  | 
 | class Transport: | 
 |     """Handles an HTTP transaction to an XML-RPC server.""" | 
 |  | 
 |     # client identifier (may be overridden) | 
 |     user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ | 
 |  | 
 |     #if true, we'll request gzip encoding | 
 |     accept_gzip_encoding = True | 
 |  | 
 |     # if positive, encode request using gzip if it exceeds this threshold | 
 |     # note that many server will get confused, so only use it if you know | 
 |     # that they can decode such a request | 
 |     encode_threshold = None #None = don't encode | 
 |  | 
 |     def __init__(self, use_datetime=0): | 
 |         self._use_datetime = use_datetime | 
 |         self._connection = (None, None) | 
 |         self._extra_headers = [] | 
 |     ## | 
 |     # Send a complete request, and parse the response. | 
 |     # Retry request if a cached connection has disconnected. | 
 |     # | 
 |     # @param host Target host. | 
 |     # @param handler Target PRC handler. | 
 |     # @param request_body XML-RPC request body. | 
 |     # @param verbose Debugging flag. | 
 |     # @return Parsed response. | 
 |  | 
 |     def request(self, host, handler, request_body, verbose=0): | 
 |         #retry request once if cached connection has gone cold | 
 |         for i in (0, 1): | 
 |             try: | 
 |                 return self.single_request(host, handler, request_body, verbose) | 
 |             except socket.error, e: | 
 |                 if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED): | 
 |                     raise | 
 |             except httplib.BadStatusLine: #close after we sent request | 
 |                 if i: | 
 |                     raise | 
 |  | 
 |     ## | 
 |     # Send a complete request, and parse the response. | 
 |     # | 
 |     # @param host Target host. | 
 |     # @param handler Target PRC handler. | 
 |     # @param request_body XML-RPC request body. | 
 |     # @param verbose Debugging flag. | 
 |     # @return Parsed response. | 
 |  | 
 |     def single_request(self, host, handler, request_body, verbose=0): | 
 |         # issue XML-RPC request | 
 |  | 
 |         h = self.make_connection(host) | 
 |         if verbose: | 
 |             h.set_debuglevel(1) | 
 |  | 
 |         try: | 
 |             self.send_request(h, handler, request_body) | 
 |             self.send_host(h, host) | 
 |             self.send_user_agent(h) | 
 |             self.send_content(h, request_body) | 
 |  | 
 |             response = h.getresponse(buffering=True) | 
 |             if response.status == 200: | 
 |                 self.verbose = verbose | 
 |                 return self.parse_response(response) | 
 |         except Fault: | 
 |             raise | 
 |         except Exception: | 
 |             # All unexpected errors leave connection in | 
 |             # a strange state, so we clear it. | 
 |             self.close() | 
 |             raise | 
 |  | 
 |         #discard any response data and raise exception | 
 |         if (response.getheader("content-length", 0)): | 
 |             response.read() | 
 |         raise ProtocolError( | 
 |             host + handler, | 
 |             response.status, response.reason, | 
 |             response.msg, | 
 |             ) | 
 |  | 
 |     ## | 
 |     # Create parser. | 
 |     # | 
 |     # @return A 2-tuple containing a parser and a unmarshaller. | 
 |  | 
 |     def getparser(self): | 
 |         # get parser and unmarshaller | 
 |         return getparser(use_datetime=self._use_datetime) | 
 |  | 
 |     ## | 
 |     # Get authorization info from host parameter | 
 |     # Host may be a string, or a (host, x509-dict) tuple; if a string, | 
 |     # it is checked for a "user:pw@host" format, and a "Basic | 
 |     # Authentication" header is added if appropriate. | 
 |     # | 
 |     # @param host Host descriptor (URL or (URL, x509 info) tuple). | 
 |     # @return A 3-tuple containing (actual host, extra headers, | 
 |     #     x509 info).  The header and x509 fields may be None. | 
 |  | 
 |     def get_host_info(self, host): | 
 |  | 
 |         x509 = {} | 
 |         if isinstance(host, TupleType): | 
 |             host, x509 = host | 
 |  | 
 |         import urllib | 
 |         auth, host = urllib.splituser(host) | 
 |  | 
 |         if auth: | 
 |             import base64 | 
 |             auth = base64.encodestring(urllib.unquote(auth)) | 
 |             auth = string.join(string.split(auth), "") # get rid of whitespace | 
 |             extra_headers = [ | 
 |                 ("Authorization", "Basic " + auth) | 
 |                 ] | 
 |         else: | 
 |             extra_headers = None | 
 |  | 
 |         return host, extra_headers, x509 | 
 |  | 
 |     ## | 
 |     # Connect to server. | 
 |     # | 
 |     # @param host Target host. | 
 |     # @return A connection handle. | 
 |  | 
 |     def make_connection(self, host): | 
 |         #return an existing connection if possible.  This allows | 
 |         #HTTP/1.1 keep-alive. | 
 |         if self._connection and host == self._connection[0]: | 
 |             return self._connection[1] | 
 |  | 
 |         # create a HTTP connection object from a host descriptor | 
 |         chost, self._extra_headers, x509 = self.get_host_info(host) | 
 |         #store the host argument along with the connection object | 
 |         self._connection = host, httplib.HTTPConnection(chost) | 
 |         return self._connection[1] | 
 |  | 
 |     ## | 
 |     # Clear any cached connection object. | 
 |     # Used in the event of socket errors. | 
 |     # | 
 |     def close(self): | 
 |         if self._connection[1]: | 
 |             self._connection[1].close() | 
 |             self._connection = (None, None) | 
 |  | 
 |     ## | 
 |     # Send request header. | 
 |     # | 
 |     # @param connection Connection handle. | 
 |     # @param handler Target RPC handler. | 
 |     # @param request_body XML-RPC body. | 
 |  | 
 |     def send_request(self, connection, handler, request_body): | 
 |         if (self.accept_gzip_encoding and gzip): | 
 |             connection.putrequest("POST", handler, skip_accept_encoding=True) | 
 |             connection.putheader("Accept-Encoding", "gzip") | 
 |         else: | 
 |             connection.putrequest("POST", handler) | 
 |  | 
 |     ## | 
 |     # Send host name. | 
 |     # | 
 |     # @param connection Connection handle. | 
 |     # @param host Host name. | 
 |     # | 
 |     # Note: This function doesn't actually add the "Host" | 
 |     # header anymore, it is done as part of the connection.putrequest() in | 
 |     # send_request() above. | 
 |  | 
 |     def send_host(self, connection, host): | 
 |         extra_headers = self._extra_headers | 
 |         if extra_headers: | 
 |             if isinstance(extra_headers, DictType): | 
 |                 extra_headers = extra_headers.items() | 
 |             for key, value in extra_headers: | 
 |                 connection.putheader(key, value) | 
 |  | 
 |     ## | 
 |     # Send user-agent identifier. | 
 |     # | 
 |     # @param connection Connection handle. | 
 |  | 
 |     def send_user_agent(self, connection): | 
 |         connection.putheader("User-Agent", self.user_agent) | 
 |  | 
 |     ## | 
 |     # Send request body. | 
 |     # | 
 |     # @param connection Connection handle. | 
 |     # @param request_body XML-RPC request body. | 
 |  | 
 |     def send_content(self, connection, request_body): | 
 |         connection.putheader("Content-Type", "text/xml") | 
 |  | 
 |         #optionally encode the request | 
 |         if (self.encode_threshold is not None and | 
 |             self.encode_threshold < len(request_body) and | 
 |             gzip): | 
 |             connection.putheader("Content-Encoding", "gzip") | 
 |             request_body = gzip_encode(request_body) | 
 |  | 
 |         connection.putheader("Content-Length", str(len(request_body))) | 
 |         connection.endheaders(request_body) | 
 |  | 
 |     ## | 
 |     # Parse response. | 
 |     # | 
 |     # @param file Stream. | 
 |     # @return Response tuple and target method. | 
 |  | 
 |     def parse_response(self, response): | 
 |         # read response data from httpresponse, and parse it | 
 |         if response.getheader("Content-Encoding", "") == "gzip": | 
 |             stream = GzipDecodedResponse(response) | 
 |         else: | 
 |             stream = response | 
 |  | 
 |         p, u = self.getparser() | 
 |  | 
 |         while 1: | 
 |             data = stream.read(1024) | 
 |             if not data: | 
 |                 break | 
 |             if self.verbose: | 
 |                 print "body:", repr(data) | 
 |             p.feed(data) | 
 |  | 
 |         if stream is not response: | 
 |             stream.close() | 
 |         p.close() | 
 |  | 
 |         return u.close() | 
 |  | 
 | ## | 
 | # Standard transport class for XML-RPC over HTTPS. | 
 |  | 
 | class SafeTransport(Transport): | 
 |     """Handles an HTTPS transaction to an XML-RPC server.""" | 
 |  | 
 |     # FIXME: mostly untested | 
 |  | 
 |     def make_connection(self, host): | 
 |         if self._connection and host == self._connection[0]: | 
 |             return self._connection[1] | 
 |         # create a HTTPS connection object from a host descriptor | 
 |         # host may be a string, or a (host, x509-dict) tuple | 
 |         try: | 
 |             HTTPS = httplib.HTTPSConnection | 
 |         except AttributeError: | 
 |             raise NotImplementedError( | 
 |                 "your version of httplib doesn't support HTTPS" | 
 |                 ) | 
 |         else: | 
 |             chost, self._extra_headers, x509 = self.get_host_info(host) | 
 |             self._connection = host, HTTPS(chost, None, **(x509 or {})) | 
 |             return self._connection[1] | 
 |  | 
 | ## | 
 | # Standard server proxy.  This class establishes a virtual connection | 
 | # to an XML-RPC server. | 
 | # <p> | 
 | # This class is available as ServerProxy and Server.  New code should | 
 | # use ServerProxy, to avoid confusion. | 
 | # | 
 | # @def ServerProxy(uri, **options) | 
 | # @param uri The connection point on the server. | 
 | # @keyparam transport A transport factory, compatible with the | 
 | #    standard transport class. | 
 | # @keyparam encoding The default encoding used for 8-bit strings | 
 | #    (default is UTF-8). | 
 | # @keyparam verbose Use a true value to enable debugging output. | 
 | #    (printed to standard output). | 
 | # @see Transport | 
 |  | 
 | class ServerProxy: | 
 |     """uri [,options] -> a logical connection to an XML-RPC server | 
 |  | 
 |     uri is the connection point on the server, given as | 
 |     scheme://host/target. | 
 |  | 
 |     The standard implementation always supports the "http" scheme.  If | 
 |     SSL socket support is available (Python 2.0), it also supports | 
 |     "https". | 
 |  | 
 |     If the target part and the slash preceding it are both omitted, | 
 |     "/RPC2" is assumed. | 
 |  | 
 |     The following options can be given as keyword arguments: | 
 |  | 
 |         transport: a transport factory | 
 |         encoding: the request encoding (default is UTF-8) | 
 |  | 
 |     All 8-bit strings passed to the server proxy are assumed to use | 
 |     the given encoding. | 
 |     """ | 
 |  | 
 |     def __init__(self, uri, transport=None, encoding=None, verbose=0, | 
 |                  allow_none=0, use_datetime=0): | 
 |         # establish a "logical" server connection | 
 |  | 
 |         # get the url | 
 |         import urllib | 
 |         type, uri = urllib.splittype(uri) | 
 |         if type not in ("http", "https"): | 
 |             raise IOError, "unsupported XML-RPC protocol" | 
 |         self.__host, self.__handler = urllib.splithost(uri) | 
 |         if not self.__handler: | 
 |             self.__handler = "/RPC2" | 
 |  | 
 |         if transport is None: | 
 |             if type == "https": | 
 |                 transport = SafeTransport(use_datetime=use_datetime) | 
 |             else: | 
 |                 transport = Transport(use_datetime=use_datetime) | 
 |         self.__transport = transport | 
 |  | 
 |         self.__encoding = encoding | 
 |         self.__verbose = verbose | 
 |         self.__allow_none = allow_none | 
 |  | 
 |     def __close(self): | 
 |         self.__transport.close() | 
 |  | 
 |     def __request(self, methodname, params): | 
 |         # call a method on the remote server | 
 |  | 
 |         request = dumps(params, methodname, encoding=self.__encoding, | 
 |                         allow_none=self.__allow_none) | 
 |  | 
 |         response = self.__transport.request( | 
 |             self.__host, | 
 |             self.__handler, | 
 |             request, | 
 |             verbose=self.__verbose | 
 |             ) | 
 |  | 
 |         if len(response) == 1: | 
 |             response = response[0] | 
 |  | 
 |         return response | 
 |  | 
 |     def __repr__(self): | 
 |         return ( | 
 |             "<ServerProxy for %s%s>" % | 
 |             (self.__host, self.__handler) | 
 |             ) | 
 |  | 
 |     __str__ = __repr__ | 
 |  | 
 |     def __getattr__(self, name): | 
 |         # magic method dispatcher | 
 |         return _Method(self.__request, name) | 
 |  | 
 |     # note: to call a remote object with an non-standard name, use | 
 |     # result getattr(server, "strange-python-name")(args) | 
 |  | 
 |     def __call__(self, attr): | 
 |         """A workaround to get special attributes on the ServerProxy | 
 |            without interfering with the magic __getattr__ | 
 |         """ | 
 |         if attr == "close": | 
 |             return self.__close | 
 |         elif attr == "transport": | 
 |             return self.__transport | 
 |         raise AttributeError("Attribute %r not found" % (attr,)) | 
 |  | 
 | # compatibility | 
 |  | 
 | Server = ServerProxy | 
 |  | 
 | # -------------------------------------------------------------------- | 
 | # test code | 
 |  | 
 | if __name__ == "__main__": | 
 |  | 
 |     # simple test program (from the XML-RPC specification) | 
 |  | 
 |     # server = ServerProxy("http://localhost:8000") # local server | 
 |     server = ServerProxy("http://time.xmlrpc.com/RPC2") | 
 |  | 
 |     print server | 
 |  | 
 |     try: | 
 |         print server.currentTime.getCurrentTime() | 
 |     except Error, v: | 
 |         print "ERROR", v | 
 |  | 
 |     multi = MultiCall(server) | 
 |     multi.currentTime.getCurrentTime() | 
 |     multi.currentTime.getCurrentTime() | 
 |     try: | 
 |         for response in multi(): | 
 |             print response | 
 |     except Error, v: | 
 |         print "ERROR", v |