These files are a subset of the python-2.7.2.tgz distribution from python.org. Changed files from PyMod-2.7.2 have been copied into the corresponding directories of this tree, replacing the original files in the distribution. Signed-off-by: daryl.mcdaniel@intel.com git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@13197 6f19259b-4bc3-4df7-8a09-765794883524
		
			
				
	
	
		
			1637 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1637 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| # 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
 |