| # | |
| # 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, errno.EPIPE): | |
| 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 | |
| # Check for new http response object, else it is a file object | |
| if hasattr(response,'getheader'): | |
| if response.getheader("Content-Encoding", "") == "gzip": | |
| stream = GzipDecodedResponse(response) | |
| else: | |
| stream = 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 |