annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/xmlrpc/client.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
jpayne@68 1 #
jpayne@68 2 # XML-RPC CLIENT LIBRARY
jpayne@68 3 # $Id$
jpayne@68 4 #
jpayne@68 5 # an XML-RPC client interface for Python.
jpayne@68 6 #
jpayne@68 7 # the marshalling and response parser code can also be used to
jpayne@68 8 # implement XML-RPC servers.
jpayne@68 9 #
jpayne@68 10 # Notes:
jpayne@68 11 # this version is designed to work with Python 2.1 or newer.
jpayne@68 12 #
jpayne@68 13 # History:
jpayne@68 14 # 1999-01-14 fl Created
jpayne@68 15 # 1999-01-15 fl Changed dateTime to use localtime
jpayne@68 16 # 1999-01-16 fl Added Binary/base64 element, default to RPC2 service
jpayne@68 17 # 1999-01-19 fl Fixed array data element (from Skip Montanaro)
jpayne@68 18 # 1999-01-21 fl Fixed dateTime constructor, etc.
jpayne@68 19 # 1999-02-02 fl Added fault handling, handle empty sequences, etc.
jpayne@68 20 # 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro)
jpayne@68 21 # 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8)
jpayne@68 22 # 2000-11-28 fl Changed boolean to check the truth value of its argument
jpayne@68 23 # 2001-02-24 fl Added encoding/Unicode/SafeTransport patches
jpayne@68 24 # 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1)
jpayne@68 25 # 2001-03-28 fl Make sure response tuple is a singleton
jpayne@68 26 # 2001-03-29 fl Don't require empty params element (from Nicholas Riley)
jpayne@68 27 # 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2)
jpayne@68 28 # 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod)
jpayne@68 29 # 2001-09-03 fl Allow Transport subclass to override getparser
jpayne@68 30 # 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup)
jpayne@68 31 # 2001-10-01 fl Remove containers from memo cache when done with them
jpayne@68 32 # 2001-10-01 fl Use faster escape method (80% dumps speedup)
jpayne@68 33 # 2001-10-02 fl More dumps microtuning
jpayne@68 34 # 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum)
jpayne@68 35 # 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow
jpayne@68 36 # 2001-10-17 sm Test for int and long overflow (allows use on 64-bit systems)
jpayne@68 37 # 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix)
jpayne@68 38 # 2002-03-17 fl Avoid buffered read when possible (from James Rucker)
jpayne@68 39 # 2002-04-07 fl Added pythondoc comments
jpayne@68 40 # 2002-04-16 fl Added __str__ methods to datetime/binary wrappers
jpayne@68 41 # 2002-05-15 fl Added error constants (from Andrew Kuchling)
jpayne@68 42 # 2002-06-27 fl Merged with Python CVS version
jpayne@68 43 # 2002-10-22 fl Added basic authentication (based on code from Phillip Eby)
jpayne@68 44 # 2003-01-22 sm Add support for the bool type
jpayne@68 45 # 2003-02-27 gvr Remove apply calls
jpayne@68 46 # 2003-04-24 sm Use cStringIO if available
jpayne@68 47 # 2003-04-25 ak Add support for nil
jpayne@68 48 # 2003-06-15 gn Add support for time.struct_time
jpayne@68 49 # 2003-07-12 gp Correct marshalling of Faults
jpayne@68 50 # 2003-10-31 mvl Add multicall support
jpayne@68 51 # 2004-08-20 mvl Bump minimum supported Python version to 2.1
jpayne@68 52 # 2014-12-02 ch/doko Add workaround for gzip bomb vulnerability
jpayne@68 53 #
jpayne@68 54 # Copyright (c) 1999-2002 by Secret Labs AB.
jpayne@68 55 # Copyright (c) 1999-2002 by Fredrik Lundh.
jpayne@68 56 #
jpayne@68 57 # info@pythonware.com
jpayne@68 58 # http://www.pythonware.com
jpayne@68 59 #
jpayne@68 60 # --------------------------------------------------------------------
jpayne@68 61 # The XML-RPC client interface is
jpayne@68 62 #
jpayne@68 63 # Copyright (c) 1999-2002 by Secret Labs AB
jpayne@68 64 # Copyright (c) 1999-2002 by Fredrik Lundh
jpayne@68 65 #
jpayne@68 66 # By obtaining, using, and/or copying this software and/or its
jpayne@68 67 # associated documentation, you agree that you have read, understood,
jpayne@68 68 # and will comply with the following terms and conditions:
jpayne@68 69 #
jpayne@68 70 # Permission to use, copy, modify, and distribute this software and
jpayne@68 71 # its associated documentation for any purpose and without fee is
jpayne@68 72 # hereby granted, provided that the above copyright notice appears in
jpayne@68 73 # all copies, and that both that copyright notice and this permission
jpayne@68 74 # notice appear in supporting documentation, and that the name of
jpayne@68 75 # Secret Labs AB or the author not be used in advertising or publicity
jpayne@68 76 # pertaining to distribution of the software without specific, written
jpayne@68 77 # prior permission.
jpayne@68 78 #
jpayne@68 79 # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
jpayne@68 80 # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
jpayne@68 81 # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
jpayne@68 82 # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
jpayne@68 83 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
jpayne@68 84 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
jpayne@68 85 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
jpayne@68 86 # OF THIS SOFTWARE.
jpayne@68 87 # --------------------------------------------------------------------
jpayne@68 88
jpayne@68 89 """
jpayne@68 90 An XML-RPC client interface for Python.
jpayne@68 91
jpayne@68 92 The marshalling and response parser code can also be used to
jpayne@68 93 implement XML-RPC servers.
jpayne@68 94
jpayne@68 95 Exported exceptions:
jpayne@68 96
jpayne@68 97 Error Base class for client errors
jpayne@68 98 ProtocolError Indicates an HTTP protocol error
jpayne@68 99 ResponseError Indicates a broken response package
jpayne@68 100 Fault Indicates an XML-RPC fault package
jpayne@68 101
jpayne@68 102 Exported classes:
jpayne@68 103
jpayne@68 104 ServerProxy Represents a logical connection to an XML-RPC server
jpayne@68 105
jpayne@68 106 MultiCall Executor of boxcared xmlrpc requests
jpayne@68 107 DateTime dateTime wrapper for an ISO 8601 string or time tuple or
jpayne@68 108 localtime integer value to generate a "dateTime.iso8601"
jpayne@68 109 XML-RPC value
jpayne@68 110 Binary binary data wrapper
jpayne@68 111
jpayne@68 112 Marshaller Generate an XML-RPC params chunk from a Python data structure
jpayne@68 113 Unmarshaller Unmarshal an XML-RPC response from incoming XML event message
jpayne@68 114 Transport Handles an HTTP transaction to an XML-RPC server
jpayne@68 115 SafeTransport Handles an HTTPS transaction to an XML-RPC server
jpayne@68 116
jpayne@68 117 Exported constants:
jpayne@68 118
jpayne@68 119 (none)
jpayne@68 120
jpayne@68 121 Exported functions:
jpayne@68 122
jpayne@68 123 getparser Create instance of the fastest available parser & attach
jpayne@68 124 to an unmarshalling object
jpayne@68 125 dumps Convert an argument tuple or a Fault instance to an XML-RPC
jpayne@68 126 request (or response, if the methodresponse option is used).
jpayne@68 127 loads Convert an XML-RPC packet to unmarshalled data plus a method
jpayne@68 128 name (None if not present).
jpayne@68 129 """
jpayne@68 130
jpayne@68 131 import base64
jpayne@68 132 import sys
jpayne@68 133 import time
jpayne@68 134 from datetime import datetime
jpayne@68 135 from decimal import Decimal
jpayne@68 136 import http.client
jpayne@68 137 import urllib.parse
jpayne@68 138 from xml.parsers import expat
jpayne@68 139 import errno
jpayne@68 140 from io import BytesIO
jpayne@68 141 try:
jpayne@68 142 import gzip
jpayne@68 143 except ImportError:
jpayne@68 144 gzip = None #python can be built without zlib/gzip support
jpayne@68 145
jpayne@68 146 # --------------------------------------------------------------------
jpayne@68 147 # Internal stuff
jpayne@68 148
jpayne@68 149 def escape(s):
jpayne@68 150 s = s.replace("&", "&")
jpayne@68 151 s = s.replace("<", "&lt;")
jpayne@68 152 return s.replace(">", "&gt;",)
jpayne@68 153
jpayne@68 154 # used in User-Agent header sent
jpayne@68 155 __version__ = '%d.%d' % sys.version_info[:2]
jpayne@68 156
jpayne@68 157 # xmlrpc integer limits
jpayne@68 158 MAXINT = 2**31-1
jpayne@68 159 MININT = -2**31
jpayne@68 160
jpayne@68 161 # --------------------------------------------------------------------
jpayne@68 162 # Error constants (from Dan Libby's specification at
jpayne@68 163 # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php)
jpayne@68 164
jpayne@68 165 # Ranges of errors
jpayne@68 166 PARSE_ERROR = -32700
jpayne@68 167 SERVER_ERROR = -32600
jpayne@68 168 APPLICATION_ERROR = -32500
jpayne@68 169 SYSTEM_ERROR = -32400
jpayne@68 170 TRANSPORT_ERROR = -32300
jpayne@68 171
jpayne@68 172 # Specific errors
jpayne@68 173 NOT_WELLFORMED_ERROR = -32700
jpayne@68 174 UNSUPPORTED_ENCODING = -32701
jpayne@68 175 INVALID_ENCODING_CHAR = -32702
jpayne@68 176 INVALID_XMLRPC = -32600
jpayne@68 177 METHOD_NOT_FOUND = -32601
jpayne@68 178 INVALID_METHOD_PARAMS = -32602
jpayne@68 179 INTERNAL_ERROR = -32603
jpayne@68 180
jpayne@68 181 # --------------------------------------------------------------------
jpayne@68 182 # Exceptions
jpayne@68 183
jpayne@68 184 ##
jpayne@68 185 # Base class for all kinds of client-side errors.
jpayne@68 186
jpayne@68 187 class Error(Exception):
jpayne@68 188 """Base class for client errors."""
jpayne@68 189 __str__ = object.__str__
jpayne@68 190
jpayne@68 191 ##
jpayne@68 192 # Indicates an HTTP-level protocol error. This is raised by the HTTP
jpayne@68 193 # transport layer, if the server returns an error code other than 200
jpayne@68 194 # (OK).
jpayne@68 195 #
jpayne@68 196 # @param url The target URL.
jpayne@68 197 # @param errcode The HTTP error code.
jpayne@68 198 # @param errmsg The HTTP error message.
jpayne@68 199 # @param headers The HTTP header dictionary.
jpayne@68 200
jpayne@68 201 class ProtocolError(Error):
jpayne@68 202 """Indicates an HTTP protocol error."""
jpayne@68 203 def __init__(self, url, errcode, errmsg, headers):
jpayne@68 204 Error.__init__(self)
jpayne@68 205 self.url = url
jpayne@68 206 self.errcode = errcode
jpayne@68 207 self.errmsg = errmsg
jpayne@68 208 self.headers = headers
jpayne@68 209 def __repr__(self):
jpayne@68 210 return (
jpayne@68 211 "<%s for %s: %s %s>" %
jpayne@68 212 (self.__class__.__name__, self.url, self.errcode, self.errmsg)
jpayne@68 213 )
jpayne@68 214
jpayne@68 215 ##
jpayne@68 216 # Indicates a broken XML-RPC response package. This exception is
jpayne@68 217 # raised by the unmarshalling layer, if the XML-RPC response is
jpayne@68 218 # malformed.
jpayne@68 219
jpayne@68 220 class ResponseError(Error):
jpayne@68 221 """Indicates a broken response package."""
jpayne@68 222 pass
jpayne@68 223
jpayne@68 224 ##
jpayne@68 225 # Indicates an XML-RPC fault response package. This exception is
jpayne@68 226 # raised by the unmarshalling layer, if the XML-RPC response contains
jpayne@68 227 # a fault string. This exception can also be used as a class, to
jpayne@68 228 # generate a fault XML-RPC message.
jpayne@68 229 #
jpayne@68 230 # @param faultCode The XML-RPC fault code.
jpayne@68 231 # @param faultString The XML-RPC fault string.
jpayne@68 232
jpayne@68 233 class Fault(Error):
jpayne@68 234 """Indicates an XML-RPC fault package."""
jpayne@68 235 def __init__(self, faultCode, faultString, **extra):
jpayne@68 236 Error.__init__(self)
jpayne@68 237 self.faultCode = faultCode
jpayne@68 238 self.faultString = faultString
jpayne@68 239 def __repr__(self):
jpayne@68 240 return "<%s %s: %r>" % (self.__class__.__name__,
jpayne@68 241 self.faultCode, self.faultString)
jpayne@68 242
jpayne@68 243 # --------------------------------------------------------------------
jpayne@68 244 # Special values
jpayne@68 245
jpayne@68 246 ##
jpayne@68 247 # Backwards compatibility
jpayne@68 248
jpayne@68 249 boolean = Boolean = bool
jpayne@68 250
jpayne@68 251 ##
jpayne@68 252 # Wrapper for XML-RPC DateTime values. This converts a time value to
jpayne@68 253 # the format used by XML-RPC.
jpayne@68 254 # <p>
jpayne@68 255 # The value can be given as a datetime object, as a string in the
jpayne@68 256 # format "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by
jpayne@68 257 # time.localtime()), or an integer value (as returned by time.time()).
jpayne@68 258 # The wrapper uses time.localtime() to convert an integer to a time
jpayne@68 259 # tuple.
jpayne@68 260 #
jpayne@68 261 # @param value The time, given as a datetime object, an ISO 8601 string,
jpayne@68 262 # a time tuple, or an integer time value.
jpayne@68 263
jpayne@68 264
jpayne@68 265 # Issue #13305: different format codes across platforms
jpayne@68 266 _day0 = datetime(1, 1, 1)
jpayne@68 267 if _day0.strftime('%Y') == '0001': # Mac OS X
jpayne@68 268 def _iso8601_format(value):
jpayne@68 269 return value.strftime("%Y%m%dT%H:%M:%S")
jpayne@68 270 elif _day0.strftime('%4Y') == '0001': # Linux
jpayne@68 271 def _iso8601_format(value):
jpayne@68 272 return value.strftime("%4Y%m%dT%H:%M:%S")
jpayne@68 273 else:
jpayne@68 274 def _iso8601_format(value):
jpayne@68 275 return value.strftime("%Y%m%dT%H:%M:%S").zfill(17)
jpayne@68 276 del _day0
jpayne@68 277
jpayne@68 278
jpayne@68 279 def _strftime(value):
jpayne@68 280 if isinstance(value, datetime):
jpayne@68 281 return _iso8601_format(value)
jpayne@68 282
jpayne@68 283 if not isinstance(value, (tuple, time.struct_time)):
jpayne@68 284 if value == 0:
jpayne@68 285 value = time.time()
jpayne@68 286 value = time.localtime(value)
jpayne@68 287
jpayne@68 288 return "%04d%02d%02dT%02d:%02d:%02d" % value[:6]
jpayne@68 289
jpayne@68 290 class DateTime:
jpayne@68 291 """DateTime wrapper for an ISO 8601 string or time tuple or
jpayne@68 292 localtime integer value to generate 'dateTime.iso8601' XML-RPC
jpayne@68 293 value.
jpayne@68 294 """
jpayne@68 295
jpayne@68 296 def __init__(self, value=0):
jpayne@68 297 if isinstance(value, str):
jpayne@68 298 self.value = value
jpayne@68 299 else:
jpayne@68 300 self.value = _strftime(value)
jpayne@68 301
jpayne@68 302 def make_comparable(self, other):
jpayne@68 303 if isinstance(other, DateTime):
jpayne@68 304 s = self.value
jpayne@68 305 o = other.value
jpayne@68 306 elif isinstance(other, datetime):
jpayne@68 307 s = self.value
jpayne@68 308 o = _iso8601_format(other)
jpayne@68 309 elif isinstance(other, str):
jpayne@68 310 s = self.value
jpayne@68 311 o = other
jpayne@68 312 elif hasattr(other, "timetuple"):
jpayne@68 313 s = self.timetuple()
jpayne@68 314 o = other.timetuple()
jpayne@68 315 else:
jpayne@68 316 otype = (hasattr(other, "__class__")
jpayne@68 317 and other.__class__.__name__
jpayne@68 318 or type(other))
jpayne@68 319 raise TypeError("Can't compare %s and %s" %
jpayne@68 320 (self.__class__.__name__, otype))
jpayne@68 321 return s, o
jpayne@68 322
jpayne@68 323 def __lt__(self, other):
jpayne@68 324 s, o = self.make_comparable(other)
jpayne@68 325 return s < o
jpayne@68 326
jpayne@68 327 def __le__(self, other):
jpayne@68 328 s, o = self.make_comparable(other)
jpayne@68 329 return s <= o
jpayne@68 330
jpayne@68 331 def __gt__(self, other):
jpayne@68 332 s, o = self.make_comparable(other)
jpayne@68 333 return s > o
jpayne@68 334
jpayne@68 335 def __ge__(self, other):
jpayne@68 336 s, o = self.make_comparable(other)
jpayne@68 337 return s >= o
jpayne@68 338
jpayne@68 339 def __eq__(self, other):
jpayne@68 340 s, o = self.make_comparable(other)
jpayne@68 341 return s == o
jpayne@68 342
jpayne@68 343 def timetuple(self):
jpayne@68 344 return time.strptime(self.value, "%Y%m%dT%H:%M:%S")
jpayne@68 345
jpayne@68 346 ##
jpayne@68 347 # Get date/time value.
jpayne@68 348 #
jpayne@68 349 # @return Date/time value, as an ISO 8601 string.
jpayne@68 350
jpayne@68 351 def __str__(self):
jpayne@68 352 return self.value
jpayne@68 353
jpayne@68 354 def __repr__(self):
jpayne@68 355 return "<%s %r at %#x>" % (self.__class__.__name__, self.value, id(self))
jpayne@68 356
jpayne@68 357 def decode(self, data):
jpayne@68 358 self.value = str(data).strip()
jpayne@68 359
jpayne@68 360 def encode(self, out):
jpayne@68 361 out.write("<value><dateTime.iso8601>")
jpayne@68 362 out.write(self.value)
jpayne@68 363 out.write("</dateTime.iso8601></value>\n")
jpayne@68 364
jpayne@68 365 def _datetime(data):
jpayne@68 366 # decode xml element contents into a DateTime structure.
jpayne@68 367 value = DateTime()
jpayne@68 368 value.decode(data)
jpayne@68 369 return value
jpayne@68 370
jpayne@68 371 def _datetime_type(data):
jpayne@68 372 return datetime.strptime(data, "%Y%m%dT%H:%M:%S")
jpayne@68 373
jpayne@68 374 ##
jpayne@68 375 # Wrapper for binary data. This can be used to transport any kind
jpayne@68 376 # of binary data over XML-RPC, using BASE64 encoding.
jpayne@68 377 #
jpayne@68 378 # @param data An 8-bit string containing arbitrary data.
jpayne@68 379
jpayne@68 380 class Binary:
jpayne@68 381 """Wrapper for binary data."""
jpayne@68 382
jpayne@68 383 def __init__(self, data=None):
jpayne@68 384 if data is None:
jpayne@68 385 data = b""
jpayne@68 386 else:
jpayne@68 387 if not isinstance(data, (bytes, bytearray)):
jpayne@68 388 raise TypeError("expected bytes or bytearray, not %s" %
jpayne@68 389 data.__class__.__name__)
jpayne@68 390 data = bytes(data) # Make a copy of the bytes!
jpayne@68 391 self.data = data
jpayne@68 392
jpayne@68 393 ##
jpayne@68 394 # Get buffer contents.
jpayne@68 395 #
jpayne@68 396 # @return Buffer contents, as an 8-bit string.
jpayne@68 397
jpayne@68 398 def __str__(self):
jpayne@68 399 return str(self.data, "latin-1") # XXX encoding?!
jpayne@68 400
jpayne@68 401 def __eq__(self, other):
jpayne@68 402 if isinstance(other, Binary):
jpayne@68 403 other = other.data
jpayne@68 404 return self.data == other
jpayne@68 405
jpayne@68 406 def decode(self, data):
jpayne@68 407 self.data = base64.decodebytes(data)
jpayne@68 408
jpayne@68 409 def encode(self, out):
jpayne@68 410 out.write("<value><base64>\n")
jpayne@68 411 encoded = base64.encodebytes(self.data)
jpayne@68 412 out.write(encoded.decode('ascii'))
jpayne@68 413 out.write("</base64></value>\n")
jpayne@68 414
jpayne@68 415 def _binary(data):
jpayne@68 416 # decode xml element contents into a Binary structure
jpayne@68 417 value = Binary()
jpayne@68 418 value.decode(data)
jpayne@68 419 return value
jpayne@68 420
jpayne@68 421 WRAPPERS = (DateTime, Binary)
jpayne@68 422
jpayne@68 423 # --------------------------------------------------------------------
jpayne@68 424 # XML parsers
jpayne@68 425
jpayne@68 426 class ExpatParser:
jpayne@68 427 # fast expat parser for Python 2.0 and later.
jpayne@68 428 def __init__(self, target):
jpayne@68 429 self._parser = parser = expat.ParserCreate(None, None)
jpayne@68 430 self._target = target
jpayne@68 431 parser.StartElementHandler = target.start
jpayne@68 432 parser.EndElementHandler = target.end
jpayne@68 433 parser.CharacterDataHandler = target.data
jpayne@68 434 encoding = None
jpayne@68 435 target.xml(encoding, None)
jpayne@68 436
jpayne@68 437 def feed(self, data):
jpayne@68 438 self._parser.Parse(data, 0)
jpayne@68 439
jpayne@68 440 def close(self):
jpayne@68 441 try:
jpayne@68 442 parser = self._parser
jpayne@68 443 except AttributeError:
jpayne@68 444 pass
jpayne@68 445 else:
jpayne@68 446 del self._target, self._parser # get rid of circular references
jpayne@68 447 parser.Parse(b"", True) # end of data
jpayne@68 448
jpayne@68 449 # --------------------------------------------------------------------
jpayne@68 450 # XML-RPC marshalling and unmarshalling code
jpayne@68 451
jpayne@68 452 ##
jpayne@68 453 # XML-RPC marshaller.
jpayne@68 454 #
jpayne@68 455 # @param encoding Default encoding for 8-bit strings. The default
jpayne@68 456 # value is None (interpreted as UTF-8).
jpayne@68 457 # @see dumps
jpayne@68 458
jpayne@68 459 class Marshaller:
jpayne@68 460 """Generate an XML-RPC params chunk from a Python data structure.
jpayne@68 461
jpayne@68 462 Create a Marshaller instance for each set of parameters, and use
jpayne@68 463 the "dumps" method to convert your data (represented as a tuple)
jpayne@68 464 to an XML-RPC params chunk. To write a fault response, pass a
jpayne@68 465 Fault instance instead. You may prefer to use the "dumps" module
jpayne@68 466 function for this purpose.
jpayne@68 467 """
jpayne@68 468
jpayne@68 469 # by the way, if you don't understand what's going on in here,
jpayne@68 470 # that's perfectly ok.
jpayne@68 471
jpayne@68 472 def __init__(self, encoding=None, allow_none=False):
jpayne@68 473 self.memo = {}
jpayne@68 474 self.data = None
jpayne@68 475 self.encoding = encoding
jpayne@68 476 self.allow_none = allow_none
jpayne@68 477
jpayne@68 478 dispatch = {}
jpayne@68 479
jpayne@68 480 def dumps(self, values):
jpayne@68 481 out = []
jpayne@68 482 write = out.append
jpayne@68 483 dump = self.__dump
jpayne@68 484 if isinstance(values, Fault):
jpayne@68 485 # fault instance
jpayne@68 486 write("<fault>\n")
jpayne@68 487 dump({'faultCode': values.faultCode,
jpayne@68 488 'faultString': values.faultString},
jpayne@68 489 write)
jpayne@68 490 write("</fault>\n")
jpayne@68 491 else:
jpayne@68 492 # parameter block
jpayne@68 493 # FIXME: the xml-rpc specification allows us to leave out
jpayne@68 494 # the entire <params> block if there are no parameters.
jpayne@68 495 # however, changing this may break older code (including
jpayne@68 496 # old versions of xmlrpclib.py), so this is better left as
jpayne@68 497 # is for now. See @XMLRPC3 for more information. /F
jpayne@68 498 write("<params>\n")
jpayne@68 499 for v in values:
jpayne@68 500 write("<param>\n")
jpayne@68 501 dump(v, write)
jpayne@68 502 write("</param>\n")
jpayne@68 503 write("</params>\n")
jpayne@68 504 result = "".join(out)
jpayne@68 505 return result
jpayne@68 506
jpayne@68 507 def __dump(self, value, write):
jpayne@68 508 try:
jpayne@68 509 f = self.dispatch[type(value)]
jpayne@68 510 except KeyError:
jpayne@68 511 # check if this object can be marshalled as a structure
jpayne@68 512 if not hasattr(value, '__dict__'):
jpayne@68 513 raise TypeError("cannot marshal %s objects" % type(value))
jpayne@68 514 # check if this class is a sub-class of a basic type,
jpayne@68 515 # because we don't know how to marshal these types
jpayne@68 516 # (e.g. a string sub-class)
jpayne@68 517 for type_ in type(value).__mro__:
jpayne@68 518 if type_ in self.dispatch.keys():
jpayne@68 519 raise TypeError("cannot marshal %s objects" % type(value))
jpayne@68 520 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix
jpayne@68 521 # for the p3yk merge, this should probably be fixed more neatly.
jpayne@68 522 f = self.dispatch["_arbitrary_instance"]
jpayne@68 523 f(self, value, write)
jpayne@68 524
jpayne@68 525 def dump_nil (self, value, write):
jpayne@68 526 if not self.allow_none:
jpayne@68 527 raise TypeError("cannot marshal None unless allow_none is enabled")
jpayne@68 528 write("<value><nil/></value>")
jpayne@68 529 dispatch[type(None)] = dump_nil
jpayne@68 530
jpayne@68 531 def dump_bool(self, value, write):
jpayne@68 532 write("<value><boolean>")
jpayne@68 533 write(value and "1" or "0")
jpayne@68 534 write("</boolean></value>\n")
jpayne@68 535 dispatch[bool] = dump_bool
jpayne@68 536
jpayne@68 537 def dump_long(self, value, write):
jpayne@68 538 if value > MAXINT or value < MININT:
jpayne@68 539 raise OverflowError("int exceeds XML-RPC limits")
jpayne@68 540 write("<value><int>")
jpayne@68 541 write(str(int(value)))
jpayne@68 542 write("</int></value>\n")
jpayne@68 543 dispatch[int] = dump_long
jpayne@68 544
jpayne@68 545 # backward compatible
jpayne@68 546 dump_int = dump_long
jpayne@68 547
jpayne@68 548 def dump_double(self, value, write):
jpayne@68 549 write("<value><double>")
jpayne@68 550 write(repr(value))
jpayne@68 551 write("</double></value>\n")
jpayne@68 552 dispatch[float] = dump_double
jpayne@68 553
jpayne@68 554 def dump_unicode(self, value, write, escape=escape):
jpayne@68 555 write("<value><string>")
jpayne@68 556 write(escape(value))
jpayne@68 557 write("</string></value>\n")
jpayne@68 558 dispatch[str] = dump_unicode
jpayne@68 559
jpayne@68 560 def dump_bytes(self, value, write):
jpayne@68 561 write("<value><base64>\n")
jpayne@68 562 encoded = base64.encodebytes(value)
jpayne@68 563 write(encoded.decode('ascii'))
jpayne@68 564 write("</base64></value>\n")
jpayne@68 565 dispatch[bytes] = dump_bytes
jpayne@68 566 dispatch[bytearray] = dump_bytes
jpayne@68 567
jpayne@68 568 def dump_array(self, value, write):
jpayne@68 569 i = id(value)
jpayne@68 570 if i in self.memo:
jpayne@68 571 raise TypeError("cannot marshal recursive sequences")
jpayne@68 572 self.memo[i] = None
jpayne@68 573 dump = self.__dump
jpayne@68 574 write("<value><array><data>\n")
jpayne@68 575 for v in value:
jpayne@68 576 dump(v, write)
jpayne@68 577 write("</data></array></value>\n")
jpayne@68 578 del self.memo[i]
jpayne@68 579 dispatch[tuple] = dump_array
jpayne@68 580 dispatch[list] = dump_array
jpayne@68 581
jpayne@68 582 def dump_struct(self, value, write, escape=escape):
jpayne@68 583 i = id(value)
jpayne@68 584 if i in self.memo:
jpayne@68 585 raise TypeError("cannot marshal recursive dictionaries")
jpayne@68 586 self.memo[i] = None
jpayne@68 587 dump = self.__dump
jpayne@68 588 write("<value><struct>\n")
jpayne@68 589 for k, v in value.items():
jpayne@68 590 write("<member>\n")
jpayne@68 591 if not isinstance(k, str):
jpayne@68 592 raise TypeError("dictionary key must be string")
jpayne@68 593 write("<name>%s</name>\n" % escape(k))
jpayne@68 594 dump(v, write)
jpayne@68 595 write("</member>\n")
jpayne@68 596 write("</struct></value>\n")
jpayne@68 597 del self.memo[i]
jpayne@68 598 dispatch[dict] = dump_struct
jpayne@68 599
jpayne@68 600 def dump_datetime(self, value, write):
jpayne@68 601 write("<value><dateTime.iso8601>")
jpayne@68 602 write(_strftime(value))
jpayne@68 603 write("</dateTime.iso8601></value>\n")
jpayne@68 604 dispatch[datetime] = dump_datetime
jpayne@68 605
jpayne@68 606 def dump_instance(self, value, write):
jpayne@68 607 # check for special wrappers
jpayne@68 608 if value.__class__ in WRAPPERS:
jpayne@68 609 self.write = write
jpayne@68 610 value.encode(self)
jpayne@68 611 del self.write
jpayne@68 612 else:
jpayne@68 613 # store instance attributes as a struct (really?)
jpayne@68 614 self.dump_struct(value.__dict__, write)
jpayne@68 615 dispatch[DateTime] = dump_instance
jpayne@68 616 dispatch[Binary] = dump_instance
jpayne@68 617 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix
jpayne@68 618 # for the p3yk merge, this should probably be fixed more neatly.
jpayne@68 619 dispatch["_arbitrary_instance"] = dump_instance
jpayne@68 620
jpayne@68 621 ##
jpayne@68 622 # XML-RPC unmarshaller.
jpayne@68 623 #
jpayne@68 624 # @see loads
jpayne@68 625
jpayne@68 626 class Unmarshaller:
jpayne@68 627 """Unmarshal an XML-RPC response, based on incoming XML event
jpayne@68 628 messages (start, data, end). Call close() to get the resulting
jpayne@68 629 data structure.
jpayne@68 630
jpayne@68 631 Note that this reader is fairly tolerant, and gladly accepts bogus
jpayne@68 632 XML-RPC data without complaining (but not bogus XML).
jpayne@68 633 """
jpayne@68 634
jpayne@68 635 # and again, if you don't understand what's going on in here,
jpayne@68 636 # that's perfectly ok.
jpayne@68 637
jpayne@68 638 def __init__(self, use_datetime=False, use_builtin_types=False):
jpayne@68 639 self._type = None
jpayne@68 640 self._stack = []
jpayne@68 641 self._marks = []
jpayne@68 642 self._data = []
jpayne@68 643 self._value = False
jpayne@68 644 self._methodname = None
jpayne@68 645 self._encoding = "utf-8"
jpayne@68 646 self.append = self._stack.append
jpayne@68 647 self._use_datetime = use_builtin_types or use_datetime
jpayne@68 648 self._use_bytes = use_builtin_types
jpayne@68 649
jpayne@68 650 def close(self):
jpayne@68 651 # return response tuple and target method
jpayne@68 652 if self._type is None or self._marks:
jpayne@68 653 raise ResponseError()
jpayne@68 654 if self._type == "fault":
jpayne@68 655 raise Fault(**self._stack[0])
jpayne@68 656 return tuple(self._stack)
jpayne@68 657
jpayne@68 658 def getmethodname(self):
jpayne@68 659 return self._methodname
jpayne@68 660
jpayne@68 661 #
jpayne@68 662 # event handlers
jpayne@68 663
jpayne@68 664 def xml(self, encoding, standalone):
jpayne@68 665 self._encoding = encoding
jpayne@68 666 # FIXME: assert standalone == 1 ???
jpayne@68 667
jpayne@68 668 def start(self, tag, attrs):
jpayne@68 669 # prepare to handle this element
jpayne@68 670 if ':' in tag:
jpayne@68 671 tag = tag.split(':')[-1]
jpayne@68 672 if tag == "array" or tag == "struct":
jpayne@68 673 self._marks.append(len(self._stack))
jpayne@68 674 self._data = []
jpayne@68 675 if self._value and tag not in self.dispatch:
jpayne@68 676 raise ResponseError("unknown tag %r" % tag)
jpayne@68 677 self._value = (tag == "value")
jpayne@68 678
jpayne@68 679 def data(self, text):
jpayne@68 680 self._data.append(text)
jpayne@68 681
jpayne@68 682 def end(self, tag):
jpayne@68 683 # call the appropriate end tag handler
jpayne@68 684 try:
jpayne@68 685 f = self.dispatch[tag]
jpayne@68 686 except KeyError:
jpayne@68 687 if ':' not in tag:
jpayne@68 688 return # unknown tag ?
jpayne@68 689 try:
jpayne@68 690 f = self.dispatch[tag.split(':')[-1]]
jpayne@68 691 except KeyError:
jpayne@68 692 return # unknown tag ?
jpayne@68 693 return f(self, "".join(self._data))
jpayne@68 694
jpayne@68 695 #
jpayne@68 696 # accelerator support
jpayne@68 697
jpayne@68 698 def end_dispatch(self, tag, data):
jpayne@68 699 # dispatch data
jpayne@68 700 try:
jpayne@68 701 f = self.dispatch[tag]
jpayne@68 702 except KeyError:
jpayne@68 703 if ':' not in tag:
jpayne@68 704 return # unknown tag ?
jpayne@68 705 try:
jpayne@68 706 f = self.dispatch[tag.split(':')[-1]]
jpayne@68 707 except KeyError:
jpayne@68 708 return # unknown tag ?
jpayne@68 709 return f(self, data)
jpayne@68 710
jpayne@68 711 #
jpayne@68 712 # element decoders
jpayne@68 713
jpayne@68 714 dispatch = {}
jpayne@68 715
jpayne@68 716 def end_nil (self, data):
jpayne@68 717 self.append(None)
jpayne@68 718 self._value = 0
jpayne@68 719 dispatch["nil"] = end_nil
jpayne@68 720
jpayne@68 721 def end_boolean(self, data):
jpayne@68 722 if data == "0":
jpayne@68 723 self.append(False)
jpayne@68 724 elif data == "1":
jpayne@68 725 self.append(True)
jpayne@68 726 else:
jpayne@68 727 raise TypeError("bad boolean value")
jpayne@68 728 self._value = 0
jpayne@68 729 dispatch["boolean"] = end_boolean
jpayne@68 730
jpayne@68 731 def end_int(self, data):
jpayne@68 732 self.append(int(data))
jpayne@68 733 self._value = 0
jpayne@68 734 dispatch["i1"] = end_int
jpayne@68 735 dispatch["i2"] = end_int
jpayne@68 736 dispatch["i4"] = end_int
jpayne@68 737 dispatch["i8"] = end_int
jpayne@68 738 dispatch["int"] = end_int
jpayne@68 739 dispatch["biginteger"] = end_int
jpayne@68 740
jpayne@68 741 def end_double(self, data):
jpayne@68 742 self.append(float(data))
jpayne@68 743 self._value = 0
jpayne@68 744 dispatch["double"] = end_double
jpayne@68 745 dispatch["float"] = end_double
jpayne@68 746
jpayne@68 747 def end_bigdecimal(self, data):
jpayne@68 748 self.append(Decimal(data))
jpayne@68 749 self._value = 0
jpayne@68 750 dispatch["bigdecimal"] = end_bigdecimal
jpayne@68 751
jpayne@68 752 def end_string(self, data):
jpayne@68 753 if self._encoding:
jpayne@68 754 data = data.decode(self._encoding)
jpayne@68 755 self.append(data)
jpayne@68 756 self._value = 0
jpayne@68 757 dispatch["string"] = end_string
jpayne@68 758 dispatch["name"] = end_string # struct keys are always strings
jpayne@68 759
jpayne@68 760 def end_array(self, data):
jpayne@68 761 mark = self._marks.pop()
jpayne@68 762 # map arrays to Python lists
jpayne@68 763 self._stack[mark:] = [self._stack[mark:]]
jpayne@68 764 self._value = 0
jpayne@68 765 dispatch["array"] = end_array
jpayne@68 766
jpayne@68 767 def end_struct(self, data):
jpayne@68 768 mark = self._marks.pop()
jpayne@68 769 # map structs to Python dictionaries
jpayne@68 770 dict = {}
jpayne@68 771 items = self._stack[mark:]
jpayne@68 772 for i in range(0, len(items), 2):
jpayne@68 773 dict[items[i]] = items[i+1]
jpayne@68 774 self._stack[mark:] = [dict]
jpayne@68 775 self._value = 0
jpayne@68 776 dispatch["struct"] = end_struct
jpayne@68 777
jpayne@68 778 def end_base64(self, data):
jpayne@68 779 value = Binary()
jpayne@68 780 value.decode(data.encode("ascii"))
jpayne@68 781 if self._use_bytes:
jpayne@68 782 value = value.data
jpayne@68 783 self.append(value)
jpayne@68 784 self._value = 0
jpayne@68 785 dispatch["base64"] = end_base64
jpayne@68 786
jpayne@68 787 def end_dateTime(self, data):
jpayne@68 788 value = DateTime()
jpayne@68 789 value.decode(data)
jpayne@68 790 if self._use_datetime:
jpayne@68 791 value = _datetime_type(data)
jpayne@68 792 self.append(value)
jpayne@68 793 dispatch["dateTime.iso8601"] = end_dateTime
jpayne@68 794
jpayne@68 795 def end_value(self, data):
jpayne@68 796 # if we stumble upon a value element with no internal
jpayne@68 797 # elements, treat it as a string element
jpayne@68 798 if self._value:
jpayne@68 799 self.end_string(data)
jpayne@68 800 dispatch["value"] = end_value
jpayne@68 801
jpayne@68 802 def end_params(self, data):
jpayne@68 803 self._type = "params"
jpayne@68 804 dispatch["params"] = end_params
jpayne@68 805
jpayne@68 806 def end_fault(self, data):
jpayne@68 807 self._type = "fault"
jpayne@68 808 dispatch["fault"] = end_fault
jpayne@68 809
jpayne@68 810 def end_methodName(self, data):
jpayne@68 811 if self._encoding:
jpayne@68 812 data = data.decode(self._encoding)
jpayne@68 813 self._methodname = data
jpayne@68 814 self._type = "methodName" # no params
jpayne@68 815 dispatch["methodName"] = end_methodName
jpayne@68 816
jpayne@68 817 ## Multicall support
jpayne@68 818 #
jpayne@68 819
jpayne@68 820 class _MultiCallMethod:
jpayne@68 821 # some lesser magic to store calls made to a MultiCall object
jpayne@68 822 # for batch execution
jpayne@68 823 def __init__(self, call_list, name):
jpayne@68 824 self.__call_list = call_list
jpayne@68 825 self.__name = name
jpayne@68 826 def __getattr__(self, name):
jpayne@68 827 return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name))
jpayne@68 828 def __call__(self, *args):
jpayne@68 829 self.__call_list.append((self.__name, args))
jpayne@68 830
jpayne@68 831 class MultiCallIterator:
jpayne@68 832 """Iterates over the results of a multicall. Exceptions are
jpayne@68 833 raised in response to xmlrpc faults."""
jpayne@68 834
jpayne@68 835 def __init__(self, results):
jpayne@68 836 self.results = results
jpayne@68 837
jpayne@68 838 def __getitem__(self, i):
jpayne@68 839 item = self.results[i]
jpayne@68 840 if type(item) == type({}):
jpayne@68 841 raise Fault(item['faultCode'], item['faultString'])
jpayne@68 842 elif type(item) == type([]):
jpayne@68 843 return item[0]
jpayne@68 844 else:
jpayne@68 845 raise ValueError("unexpected type in multicall result")
jpayne@68 846
jpayne@68 847 class MultiCall:
jpayne@68 848 """server -> an object used to boxcar method calls
jpayne@68 849
jpayne@68 850 server should be a ServerProxy object.
jpayne@68 851
jpayne@68 852 Methods can be added to the MultiCall using normal
jpayne@68 853 method call syntax e.g.:
jpayne@68 854
jpayne@68 855 multicall = MultiCall(server_proxy)
jpayne@68 856 multicall.add(2,3)
jpayne@68 857 multicall.get_address("Guido")
jpayne@68 858
jpayne@68 859 To execute the multicall, call the MultiCall object e.g.:
jpayne@68 860
jpayne@68 861 add_result, address = multicall()
jpayne@68 862 """
jpayne@68 863
jpayne@68 864 def __init__(self, server):
jpayne@68 865 self.__server = server
jpayne@68 866 self.__call_list = []
jpayne@68 867
jpayne@68 868 def __repr__(self):
jpayne@68 869 return "<%s at %#x>" % (self.__class__.__name__, id(self))
jpayne@68 870
jpayne@68 871 def __getattr__(self, name):
jpayne@68 872 return _MultiCallMethod(self.__call_list, name)
jpayne@68 873
jpayne@68 874 def __call__(self):
jpayne@68 875 marshalled_list = []
jpayne@68 876 for name, args in self.__call_list:
jpayne@68 877 marshalled_list.append({'methodName' : name, 'params' : args})
jpayne@68 878
jpayne@68 879 return MultiCallIterator(self.__server.system.multicall(marshalled_list))
jpayne@68 880
jpayne@68 881 # --------------------------------------------------------------------
jpayne@68 882 # convenience functions
jpayne@68 883
jpayne@68 884 FastMarshaller = FastParser = FastUnmarshaller = None
jpayne@68 885
jpayne@68 886 ##
jpayne@68 887 # Create a parser object, and connect it to an unmarshalling instance.
jpayne@68 888 # This function picks the fastest available XML parser.
jpayne@68 889 #
jpayne@68 890 # return A (parser, unmarshaller) tuple.
jpayne@68 891
jpayne@68 892 def getparser(use_datetime=False, use_builtin_types=False):
jpayne@68 893 """getparser() -> parser, unmarshaller
jpayne@68 894
jpayne@68 895 Create an instance of the fastest available parser, and attach it
jpayne@68 896 to an unmarshalling object. Return both objects.
jpayne@68 897 """
jpayne@68 898 if FastParser and FastUnmarshaller:
jpayne@68 899 if use_builtin_types:
jpayne@68 900 mkdatetime = _datetime_type
jpayne@68 901 mkbytes = base64.decodebytes
jpayne@68 902 elif use_datetime:
jpayne@68 903 mkdatetime = _datetime_type
jpayne@68 904 mkbytes = _binary
jpayne@68 905 else:
jpayne@68 906 mkdatetime = _datetime
jpayne@68 907 mkbytes = _binary
jpayne@68 908 target = FastUnmarshaller(True, False, mkbytes, mkdatetime, Fault)
jpayne@68 909 parser = FastParser(target)
jpayne@68 910 else:
jpayne@68 911 target = Unmarshaller(use_datetime=use_datetime, use_builtin_types=use_builtin_types)
jpayne@68 912 if FastParser:
jpayne@68 913 parser = FastParser(target)
jpayne@68 914 else:
jpayne@68 915 parser = ExpatParser(target)
jpayne@68 916 return parser, target
jpayne@68 917
jpayne@68 918 ##
jpayne@68 919 # Convert a Python tuple or a Fault instance to an XML-RPC packet.
jpayne@68 920 #
jpayne@68 921 # @def dumps(params, **options)
jpayne@68 922 # @param params A tuple or Fault instance.
jpayne@68 923 # @keyparam methodname If given, create a methodCall request for
jpayne@68 924 # this method name.
jpayne@68 925 # @keyparam methodresponse If given, create a methodResponse packet.
jpayne@68 926 # If used with a tuple, the tuple must be a singleton (that is,
jpayne@68 927 # it must contain exactly one element).
jpayne@68 928 # @keyparam encoding The packet encoding.
jpayne@68 929 # @return A string containing marshalled data.
jpayne@68 930
jpayne@68 931 def dumps(params, methodname=None, methodresponse=None, encoding=None,
jpayne@68 932 allow_none=False):
jpayne@68 933 """data [,options] -> marshalled data
jpayne@68 934
jpayne@68 935 Convert an argument tuple or a Fault instance to an XML-RPC
jpayne@68 936 request (or response, if the methodresponse option is used).
jpayne@68 937
jpayne@68 938 In addition to the data object, the following options can be given
jpayne@68 939 as keyword arguments:
jpayne@68 940
jpayne@68 941 methodname: the method name for a methodCall packet
jpayne@68 942
jpayne@68 943 methodresponse: true to create a methodResponse packet.
jpayne@68 944 If this option is used with a tuple, the tuple must be
jpayne@68 945 a singleton (i.e. it can contain only one element).
jpayne@68 946
jpayne@68 947 encoding: the packet encoding (default is UTF-8)
jpayne@68 948
jpayne@68 949 All byte strings in the data structure are assumed to use the
jpayne@68 950 packet encoding. Unicode strings are automatically converted,
jpayne@68 951 where necessary.
jpayne@68 952 """
jpayne@68 953
jpayne@68 954 assert isinstance(params, (tuple, Fault)), "argument must be tuple or Fault instance"
jpayne@68 955 if isinstance(params, Fault):
jpayne@68 956 methodresponse = 1
jpayne@68 957 elif methodresponse and isinstance(params, tuple):
jpayne@68 958 assert len(params) == 1, "response tuple must be a singleton"
jpayne@68 959
jpayne@68 960 if not encoding:
jpayne@68 961 encoding = "utf-8"
jpayne@68 962
jpayne@68 963 if FastMarshaller:
jpayne@68 964 m = FastMarshaller(encoding)
jpayne@68 965 else:
jpayne@68 966 m = Marshaller(encoding, allow_none)
jpayne@68 967
jpayne@68 968 data = m.dumps(params)
jpayne@68 969
jpayne@68 970 if encoding != "utf-8":
jpayne@68 971 xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding)
jpayne@68 972 else:
jpayne@68 973 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
jpayne@68 974
jpayne@68 975 # standard XML-RPC wrappings
jpayne@68 976 if methodname:
jpayne@68 977 # a method call
jpayne@68 978 data = (
jpayne@68 979 xmlheader,
jpayne@68 980 "<methodCall>\n"
jpayne@68 981 "<methodName>", methodname, "</methodName>\n",
jpayne@68 982 data,
jpayne@68 983 "</methodCall>\n"
jpayne@68 984 )
jpayne@68 985 elif methodresponse:
jpayne@68 986 # a method response, or a fault structure
jpayne@68 987 data = (
jpayne@68 988 xmlheader,
jpayne@68 989 "<methodResponse>\n",
jpayne@68 990 data,
jpayne@68 991 "</methodResponse>\n"
jpayne@68 992 )
jpayne@68 993 else:
jpayne@68 994 return data # return as is
jpayne@68 995 return "".join(data)
jpayne@68 996
jpayne@68 997 ##
jpayne@68 998 # Convert an XML-RPC packet to a Python object. If the XML-RPC packet
jpayne@68 999 # represents a fault condition, this function raises a Fault exception.
jpayne@68 1000 #
jpayne@68 1001 # @param data An XML-RPC packet, given as an 8-bit string.
jpayne@68 1002 # @return A tuple containing the unpacked data, and the method name
jpayne@68 1003 # (None if not present).
jpayne@68 1004 # @see Fault
jpayne@68 1005
jpayne@68 1006 def loads(data, use_datetime=False, use_builtin_types=False):
jpayne@68 1007 """data -> unmarshalled data, method name
jpayne@68 1008
jpayne@68 1009 Convert an XML-RPC packet to unmarshalled data plus a method
jpayne@68 1010 name (None if not present).
jpayne@68 1011
jpayne@68 1012 If the XML-RPC packet represents a fault condition, this function
jpayne@68 1013 raises a Fault exception.
jpayne@68 1014 """
jpayne@68 1015 p, u = getparser(use_datetime=use_datetime, use_builtin_types=use_builtin_types)
jpayne@68 1016 p.feed(data)
jpayne@68 1017 p.close()
jpayne@68 1018 return u.close(), u.getmethodname()
jpayne@68 1019
jpayne@68 1020 ##
jpayne@68 1021 # Encode a string using the gzip content encoding such as specified by the
jpayne@68 1022 # Content-Encoding: gzip
jpayne@68 1023 # in the HTTP header, as described in RFC 1952
jpayne@68 1024 #
jpayne@68 1025 # @param data the unencoded data
jpayne@68 1026 # @return the encoded data
jpayne@68 1027
jpayne@68 1028 def gzip_encode(data):
jpayne@68 1029 """data -> gzip encoded data
jpayne@68 1030
jpayne@68 1031 Encode data using the gzip content encoding as described in RFC 1952
jpayne@68 1032 """
jpayne@68 1033 if not gzip:
jpayne@68 1034 raise NotImplementedError
jpayne@68 1035 f = BytesIO()
jpayne@68 1036 with gzip.GzipFile(mode="wb", fileobj=f, compresslevel=1) as gzf:
jpayne@68 1037 gzf.write(data)
jpayne@68 1038 return f.getvalue()
jpayne@68 1039
jpayne@68 1040 ##
jpayne@68 1041 # Decode a string using the gzip content encoding such as specified by the
jpayne@68 1042 # Content-Encoding: gzip
jpayne@68 1043 # in the HTTP header, as described in RFC 1952
jpayne@68 1044 #
jpayne@68 1045 # @param data The encoded data
jpayne@68 1046 # @keyparam max_decode Maximum bytes to decode (20 MiB default), use negative
jpayne@68 1047 # values for unlimited decoding
jpayne@68 1048 # @return the unencoded data
jpayne@68 1049 # @raises ValueError if data is not correctly coded.
jpayne@68 1050 # @raises ValueError if max gzipped payload length exceeded
jpayne@68 1051
jpayne@68 1052 def gzip_decode(data, max_decode=20971520):
jpayne@68 1053 """gzip encoded data -> unencoded data
jpayne@68 1054
jpayne@68 1055 Decode data using the gzip content encoding as described in RFC 1952
jpayne@68 1056 """
jpayne@68 1057 if not gzip:
jpayne@68 1058 raise NotImplementedError
jpayne@68 1059 with gzip.GzipFile(mode="rb", fileobj=BytesIO(data)) as gzf:
jpayne@68 1060 try:
jpayne@68 1061 if max_decode < 0: # no limit
jpayne@68 1062 decoded = gzf.read()
jpayne@68 1063 else:
jpayne@68 1064 decoded = gzf.read(max_decode + 1)
jpayne@68 1065 except OSError:
jpayne@68 1066 raise ValueError("invalid data")
jpayne@68 1067 if max_decode >= 0 and len(decoded) > max_decode:
jpayne@68 1068 raise ValueError("max gzipped payload length exceeded")
jpayne@68 1069 return decoded
jpayne@68 1070
jpayne@68 1071 ##
jpayne@68 1072 # Return a decoded file-like object for the gzip encoding
jpayne@68 1073 # as described in RFC 1952.
jpayne@68 1074 #
jpayne@68 1075 # @param response A stream supporting a read() method
jpayne@68 1076 # @return a file-like object that the decoded data can be read() from
jpayne@68 1077
jpayne@68 1078 class GzipDecodedResponse(gzip.GzipFile if gzip else object):
jpayne@68 1079 """a file-like object to decode a response encoded with the gzip
jpayne@68 1080 method, as described in RFC 1952.
jpayne@68 1081 """
jpayne@68 1082 def __init__(self, response):
jpayne@68 1083 #response doesn't support tell() and read(), required by
jpayne@68 1084 #GzipFile
jpayne@68 1085 if not gzip:
jpayne@68 1086 raise NotImplementedError
jpayne@68 1087 self.io = BytesIO(response.read())
jpayne@68 1088 gzip.GzipFile.__init__(self, mode="rb", fileobj=self.io)
jpayne@68 1089
jpayne@68 1090 def close(self):
jpayne@68 1091 try:
jpayne@68 1092 gzip.GzipFile.close(self)
jpayne@68 1093 finally:
jpayne@68 1094 self.io.close()
jpayne@68 1095
jpayne@68 1096
jpayne@68 1097 # --------------------------------------------------------------------
jpayne@68 1098 # request dispatcher
jpayne@68 1099
jpayne@68 1100 class _Method:
jpayne@68 1101 # some magic to bind an XML-RPC method to an RPC server.
jpayne@68 1102 # supports "nested" methods (e.g. examples.getStateName)
jpayne@68 1103 def __init__(self, send, name):
jpayne@68 1104 self.__send = send
jpayne@68 1105 self.__name = name
jpayne@68 1106 def __getattr__(self, name):
jpayne@68 1107 return _Method(self.__send, "%s.%s" % (self.__name, name))
jpayne@68 1108 def __call__(self, *args):
jpayne@68 1109 return self.__send(self.__name, args)
jpayne@68 1110
jpayne@68 1111 ##
jpayne@68 1112 # Standard transport class for XML-RPC over HTTP.
jpayne@68 1113 # <p>
jpayne@68 1114 # You can create custom transports by subclassing this method, and
jpayne@68 1115 # overriding selected methods.
jpayne@68 1116
jpayne@68 1117 class Transport:
jpayne@68 1118 """Handles an HTTP transaction to an XML-RPC server."""
jpayne@68 1119
jpayne@68 1120 # client identifier (may be overridden)
jpayne@68 1121 user_agent = "Python-xmlrpc/%s" % __version__
jpayne@68 1122
jpayne@68 1123 #if true, we'll request gzip encoding
jpayne@68 1124 accept_gzip_encoding = True
jpayne@68 1125
jpayne@68 1126 # if positive, encode request using gzip if it exceeds this threshold
jpayne@68 1127 # note that many servers will get confused, so only use it if you know
jpayne@68 1128 # that they can decode such a request
jpayne@68 1129 encode_threshold = None #None = don't encode
jpayne@68 1130
jpayne@68 1131 def __init__(self, use_datetime=False, use_builtin_types=False,
jpayne@68 1132 *, headers=()):
jpayne@68 1133 self._use_datetime = use_datetime
jpayne@68 1134 self._use_builtin_types = use_builtin_types
jpayne@68 1135 self._connection = (None, None)
jpayne@68 1136 self._headers = list(headers)
jpayne@68 1137 self._extra_headers = []
jpayne@68 1138
jpayne@68 1139 ##
jpayne@68 1140 # Send a complete request, and parse the response.
jpayne@68 1141 # Retry request if a cached connection has disconnected.
jpayne@68 1142 #
jpayne@68 1143 # @param host Target host.
jpayne@68 1144 # @param handler Target PRC handler.
jpayne@68 1145 # @param request_body XML-RPC request body.
jpayne@68 1146 # @param verbose Debugging flag.
jpayne@68 1147 # @return Parsed response.
jpayne@68 1148
jpayne@68 1149 def request(self, host, handler, request_body, verbose=False):
jpayne@68 1150 #retry request once if cached connection has gone cold
jpayne@68 1151 for i in (0, 1):
jpayne@68 1152 try:
jpayne@68 1153 return self.single_request(host, handler, request_body, verbose)
jpayne@68 1154 except http.client.RemoteDisconnected:
jpayne@68 1155 if i:
jpayne@68 1156 raise
jpayne@68 1157 except OSError as e:
jpayne@68 1158 if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED,
jpayne@68 1159 errno.EPIPE):
jpayne@68 1160 raise
jpayne@68 1161
jpayne@68 1162 def single_request(self, host, handler, request_body, verbose=False):
jpayne@68 1163 # issue XML-RPC request
jpayne@68 1164 try:
jpayne@68 1165 http_conn = self.send_request(host, handler, request_body, verbose)
jpayne@68 1166 resp = http_conn.getresponse()
jpayne@68 1167 if resp.status == 200:
jpayne@68 1168 self.verbose = verbose
jpayne@68 1169 return self.parse_response(resp)
jpayne@68 1170
jpayne@68 1171 except Fault:
jpayne@68 1172 raise
jpayne@68 1173 except Exception:
jpayne@68 1174 #All unexpected errors leave connection in
jpayne@68 1175 # a strange state, so we clear it.
jpayne@68 1176 self.close()
jpayne@68 1177 raise
jpayne@68 1178
jpayne@68 1179 #We got an error response.
jpayne@68 1180 #Discard any response data and raise exception
jpayne@68 1181 if resp.getheader("content-length", ""):
jpayne@68 1182 resp.read()
jpayne@68 1183 raise ProtocolError(
jpayne@68 1184 host + handler,
jpayne@68 1185 resp.status, resp.reason,
jpayne@68 1186 dict(resp.getheaders())
jpayne@68 1187 )
jpayne@68 1188
jpayne@68 1189
jpayne@68 1190 ##
jpayne@68 1191 # Create parser.
jpayne@68 1192 #
jpayne@68 1193 # @return A 2-tuple containing a parser and an unmarshaller.
jpayne@68 1194
jpayne@68 1195 def getparser(self):
jpayne@68 1196 # get parser and unmarshaller
jpayne@68 1197 return getparser(use_datetime=self._use_datetime,
jpayne@68 1198 use_builtin_types=self._use_builtin_types)
jpayne@68 1199
jpayne@68 1200 ##
jpayne@68 1201 # Get authorization info from host parameter
jpayne@68 1202 # Host may be a string, or a (host, x509-dict) tuple; if a string,
jpayne@68 1203 # it is checked for a "user:pw@host" format, and a "Basic
jpayne@68 1204 # Authentication" header is added if appropriate.
jpayne@68 1205 #
jpayne@68 1206 # @param host Host descriptor (URL or (URL, x509 info) tuple).
jpayne@68 1207 # @return A 3-tuple containing (actual host, extra headers,
jpayne@68 1208 # x509 info). The header and x509 fields may be None.
jpayne@68 1209
jpayne@68 1210 def get_host_info(self, host):
jpayne@68 1211
jpayne@68 1212 x509 = {}
jpayne@68 1213 if isinstance(host, tuple):
jpayne@68 1214 host, x509 = host
jpayne@68 1215
jpayne@68 1216 auth, host = urllib.parse._splituser(host)
jpayne@68 1217
jpayne@68 1218 if auth:
jpayne@68 1219 auth = urllib.parse.unquote_to_bytes(auth)
jpayne@68 1220 auth = base64.encodebytes(auth).decode("utf-8")
jpayne@68 1221 auth = "".join(auth.split()) # get rid of whitespace
jpayne@68 1222 extra_headers = [
jpayne@68 1223 ("Authorization", "Basic " + auth)
jpayne@68 1224 ]
jpayne@68 1225 else:
jpayne@68 1226 extra_headers = []
jpayne@68 1227
jpayne@68 1228 return host, extra_headers, x509
jpayne@68 1229
jpayne@68 1230 ##
jpayne@68 1231 # Connect to server.
jpayne@68 1232 #
jpayne@68 1233 # @param host Target host.
jpayne@68 1234 # @return An HTTPConnection object
jpayne@68 1235
jpayne@68 1236 def make_connection(self, host):
jpayne@68 1237 #return an existing connection if possible. This allows
jpayne@68 1238 #HTTP/1.1 keep-alive.
jpayne@68 1239 if self._connection and host == self._connection[0]:
jpayne@68 1240 return self._connection[1]
jpayne@68 1241 # create a HTTP connection object from a host descriptor
jpayne@68 1242 chost, self._extra_headers, x509 = self.get_host_info(host)
jpayne@68 1243 self._connection = host, http.client.HTTPConnection(chost)
jpayne@68 1244 return self._connection[1]
jpayne@68 1245
jpayne@68 1246 ##
jpayne@68 1247 # Clear any cached connection object.
jpayne@68 1248 # Used in the event of socket errors.
jpayne@68 1249 #
jpayne@68 1250 def close(self):
jpayne@68 1251 host, connection = self._connection
jpayne@68 1252 if connection:
jpayne@68 1253 self._connection = (None, None)
jpayne@68 1254 connection.close()
jpayne@68 1255
jpayne@68 1256 ##
jpayne@68 1257 # Send HTTP request.
jpayne@68 1258 #
jpayne@68 1259 # @param host Host descriptor (URL or (URL, x509 info) tuple).
jpayne@68 1260 # @param handler Target RPC handler (a path relative to host)
jpayne@68 1261 # @param request_body The XML-RPC request body
jpayne@68 1262 # @param debug Enable debugging if debug is true.
jpayne@68 1263 # @return An HTTPConnection.
jpayne@68 1264
jpayne@68 1265 def send_request(self, host, handler, request_body, debug):
jpayne@68 1266 connection = self.make_connection(host)
jpayne@68 1267 headers = self._headers + self._extra_headers
jpayne@68 1268 if debug:
jpayne@68 1269 connection.set_debuglevel(1)
jpayne@68 1270 if self.accept_gzip_encoding and gzip:
jpayne@68 1271 connection.putrequest("POST", handler, skip_accept_encoding=True)
jpayne@68 1272 headers.append(("Accept-Encoding", "gzip"))
jpayne@68 1273 else:
jpayne@68 1274 connection.putrequest("POST", handler)
jpayne@68 1275 headers.append(("Content-Type", "text/xml"))
jpayne@68 1276 headers.append(("User-Agent", self.user_agent))
jpayne@68 1277 self.send_headers(connection, headers)
jpayne@68 1278 self.send_content(connection, request_body)
jpayne@68 1279 return connection
jpayne@68 1280
jpayne@68 1281 ##
jpayne@68 1282 # Send request headers.
jpayne@68 1283 # This function provides a useful hook for subclassing
jpayne@68 1284 #
jpayne@68 1285 # @param connection httpConnection.
jpayne@68 1286 # @param headers list of key,value pairs for HTTP headers
jpayne@68 1287
jpayne@68 1288 def send_headers(self, connection, headers):
jpayne@68 1289 for key, val in headers:
jpayne@68 1290 connection.putheader(key, val)
jpayne@68 1291
jpayne@68 1292 ##
jpayne@68 1293 # Send request body.
jpayne@68 1294 # This function provides a useful hook for subclassing
jpayne@68 1295 #
jpayne@68 1296 # @param connection httpConnection.
jpayne@68 1297 # @param request_body XML-RPC request body.
jpayne@68 1298
jpayne@68 1299 def send_content(self, connection, request_body):
jpayne@68 1300 #optionally encode the request
jpayne@68 1301 if (self.encode_threshold is not None and
jpayne@68 1302 self.encode_threshold < len(request_body) and
jpayne@68 1303 gzip):
jpayne@68 1304 connection.putheader("Content-Encoding", "gzip")
jpayne@68 1305 request_body = gzip_encode(request_body)
jpayne@68 1306
jpayne@68 1307 connection.putheader("Content-Length", str(len(request_body)))
jpayne@68 1308 connection.endheaders(request_body)
jpayne@68 1309
jpayne@68 1310 ##
jpayne@68 1311 # Parse response.
jpayne@68 1312 #
jpayne@68 1313 # @param file Stream.
jpayne@68 1314 # @return Response tuple and target method.
jpayne@68 1315
jpayne@68 1316 def parse_response(self, response):
jpayne@68 1317 # read response data from httpresponse, and parse it
jpayne@68 1318 # Check for new http response object, otherwise it is a file object.
jpayne@68 1319 if hasattr(response, 'getheader'):
jpayne@68 1320 if response.getheader("Content-Encoding", "") == "gzip":
jpayne@68 1321 stream = GzipDecodedResponse(response)
jpayne@68 1322 else:
jpayne@68 1323 stream = response
jpayne@68 1324 else:
jpayne@68 1325 stream = response
jpayne@68 1326
jpayne@68 1327 p, u = self.getparser()
jpayne@68 1328
jpayne@68 1329 while 1:
jpayne@68 1330 data = stream.read(1024)
jpayne@68 1331 if not data:
jpayne@68 1332 break
jpayne@68 1333 if self.verbose:
jpayne@68 1334 print("body:", repr(data))
jpayne@68 1335 p.feed(data)
jpayne@68 1336
jpayne@68 1337 if stream is not response:
jpayne@68 1338 stream.close()
jpayne@68 1339 p.close()
jpayne@68 1340
jpayne@68 1341 return u.close()
jpayne@68 1342
jpayne@68 1343 ##
jpayne@68 1344 # Standard transport class for XML-RPC over HTTPS.
jpayne@68 1345
jpayne@68 1346 class SafeTransport(Transport):
jpayne@68 1347 """Handles an HTTPS transaction to an XML-RPC server."""
jpayne@68 1348
jpayne@68 1349 def __init__(self, use_datetime=False, use_builtin_types=False,
jpayne@68 1350 *, headers=(), context=None):
jpayne@68 1351 super().__init__(use_datetime=use_datetime,
jpayne@68 1352 use_builtin_types=use_builtin_types,
jpayne@68 1353 headers=headers)
jpayne@68 1354 self.context = context
jpayne@68 1355
jpayne@68 1356 # FIXME: mostly untested
jpayne@68 1357
jpayne@68 1358 def make_connection(self, host):
jpayne@68 1359 if self._connection and host == self._connection[0]:
jpayne@68 1360 return self._connection[1]
jpayne@68 1361
jpayne@68 1362 if not hasattr(http.client, "HTTPSConnection"):
jpayne@68 1363 raise NotImplementedError(
jpayne@68 1364 "your version of http.client doesn't support HTTPS")
jpayne@68 1365 # create a HTTPS connection object from a host descriptor
jpayne@68 1366 # host may be a string, or a (host, x509-dict) tuple
jpayne@68 1367 chost, self._extra_headers, x509 = self.get_host_info(host)
jpayne@68 1368 self._connection = host, http.client.HTTPSConnection(chost,
jpayne@68 1369 None, context=self.context, **(x509 or {}))
jpayne@68 1370 return self._connection[1]
jpayne@68 1371
jpayne@68 1372 ##
jpayne@68 1373 # Standard server proxy. This class establishes a virtual connection
jpayne@68 1374 # to an XML-RPC server.
jpayne@68 1375 # <p>
jpayne@68 1376 # This class is available as ServerProxy and Server. New code should
jpayne@68 1377 # use ServerProxy, to avoid confusion.
jpayne@68 1378 #
jpayne@68 1379 # @def ServerProxy(uri, **options)
jpayne@68 1380 # @param uri The connection point on the server.
jpayne@68 1381 # @keyparam transport A transport factory, compatible with the
jpayne@68 1382 # standard transport class.
jpayne@68 1383 # @keyparam encoding The default encoding used for 8-bit strings
jpayne@68 1384 # (default is UTF-8).
jpayne@68 1385 # @keyparam verbose Use a true value to enable debugging output.
jpayne@68 1386 # (printed to standard output).
jpayne@68 1387 # @see Transport
jpayne@68 1388
jpayne@68 1389 class ServerProxy:
jpayne@68 1390 """uri [,options] -> a logical connection to an XML-RPC server
jpayne@68 1391
jpayne@68 1392 uri is the connection point on the server, given as
jpayne@68 1393 scheme://host/target.
jpayne@68 1394
jpayne@68 1395 The standard implementation always supports the "http" scheme. If
jpayne@68 1396 SSL socket support is available (Python 2.0), it also supports
jpayne@68 1397 "https".
jpayne@68 1398
jpayne@68 1399 If the target part and the slash preceding it are both omitted,
jpayne@68 1400 "/RPC2" is assumed.
jpayne@68 1401
jpayne@68 1402 The following options can be given as keyword arguments:
jpayne@68 1403
jpayne@68 1404 transport: a transport factory
jpayne@68 1405 encoding: the request encoding (default is UTF-8)
jpayne@68 1406
jpayne@68 1407 All 8-bit strings passed to the server proxy are assumed to use
jpayne@68 1408 the given encoding.
jpayne@68 1409 """
jpayne@68 1410
jpayne@68 1411 def __init__(self, uri, transport=None, encoding=None, verbose=False,
jpayne@68 1412 allow_none=False, use_datetime=False, use_builtin_types=False,
jpayne@68 1413 *, headers=(), context=None):
jpayne@68 1414 # establish a "logical" server connection
jpayne@68 1415
jpayne@68 1416 # get the url
jpayne@68 1417 type, uri = urllib.parse._splittype(uri)
jpayne@68 1418 if type not in ("http", "https"):
jpayne@68 1419 raise OSError("unsupported XML-RPC protocol")
jpayne@68 1420 self.__host, self.__handler = urllib.parse._splithost(uri)
jpayne@68 1421 if not self.__handler:
jpayne@68 1422 self.__handler = "/RPC2"
jpayne@68 1423
jpayne@68 1424 if transport is None:
jpayne@68 1425 if type == "https":
jpayne@68 1426 handler = SafeTransport
jpayne@68 1427 extra_kwargs = {"context": context}
jpayne@68 1428 else:
jpayne@68 1429 handler = Transport
jpayne@68 1430 extra_kwargs = {}
jpayne@68 1431 transport = handler(use_datetime=use_datetime,
jpayne@68 1432 use_builtin_types=use_builtin_types,
jpayne@68 1433 headers=headers,
jpayne@68 1434 **extra_kwargs)
jpayne@68 1435 self.__transport = transport
jpayne@68 1436
jpayne@68 1437 self.__encoding = encoding or 'utf-8'
jpayne@68 1438 self.__verbose = verbose
jpayne@68 1439 self.__allow_none = allow_none
jpayne@68 1440
jpayne@68 1441 def __close(self):
jpayne@68 1442 self.__transport.close()
jpayne@68 1443
jpayne@68 1444 def __request(self, methodname, params):
jpayne@68 1445 # call a method on the remote server
jpayne@68 1446
jpayne@68 1447 request = dumps(params, methodname, encoding=self.__encoding,
jpayne@68 1448 allow_none=self.__allow_none).encode(self.__encoding, 'xmlcharrefreplace')
jpayne@68 1449
jpayne@68 1450 response = self.__transport.request(
jpayne@68 1451 self.__host,
jpayne@68 1452 self.__handler,
jpayne@68 1453 request,
jpayne@68 1454 verbose=self.__verbose
jpayne@68 1455 )
jpayne@68 1456
jpayne@68 1457 if len(response) == 1:
jpayne@68 1458 response = response[0]
jpayne@68 1459
jpayne@68 1460 return response
jpayne@68 1461
jpayne@68 1462 def __repr__(self):
jpayne@68 1463 return (
jpayne@68 1464 "<%s for %s%s>" %
jpayne@68 1465 (self.__class__.__name__, self.__host, self.__handler)
jpayne@68 1466 )
jpayne@68 1467
jpayne@68 1468 def __getattr__(self, name):
jpayne@68 1469 # magic method dispatcher
jpayne@68 1470 return _Method(self.__request, name)
jpayne@68 1471
jpayne@68 1472 # note: to call a remote object with a non-standard name, use
jpayne@68 1473 # result getattr(server, "strange-python-name")(args)
jpayne@68 1474
jpayne@68 1475 def __call__(self, attr):
jpayne@68 1476 """A workaround to get special attributes on the ServerProxy
jpayne@68 1477 without interfering with the magic __getattr__
jpayne@68 1478 """
jpayne@68 1479 if attr == "close":
jpayne@68 1480 return self.__close
jpayne@68 1481 elif attr == "transport":
jpayne@68 1482 return self.__transport
jpayne@68 1483 raise AttributeError("Attribute %r not found" % (attr,))
jpayne@68 1484
jpayne@68 1485 def __enter__(self):
jpayne@68 1486 return self
jpayne@68 1487
jpayne@68 1488 def __exit__(self, *args):
jpayne@68 1489 self.__close()
jpayne@68 1490
jpayne@68 1491 # compatibility
jpayne@68 1492
jpayne@68 1493 Server = ServerProxy
jpayne@68 1494
jpayne@68 1495 # --------------------------------------------------------------------
jpayne@68 1496 # test code
jpayne@68 1497
jpayne@68 1498 if __name__ == "__main__":
jpayne@68 1499
jpayne@68 1500 # simple test program (from the XML-RPC specification)
jpayne@68 1501
jpayne@68 1502 # local server, available from Lib/xmlrpc/server.py
jpayne@68 1503 server = ServerProxy("http://localhost:8000")
jpayne@68 1504
jpayne@68 1505 try:
jpayne@68 1506 print(server.currentTime.getCurrentTime())
jpayne@68 1507 except Error as v:
jpayne@68 1508 print("ERROR", v)
jpayne@68 1509
jpayne@68 1510 multi = MultiCall(server)
jpayne@68 1511 multi.getData()
jpayne@68 1512 multi.pow(2,9)
jpayne@68 1513 multi.add(1,2)
jpayne@68 1514 try:
jpayne@68 1515 for response in multi():
jpayne@68 1516 print(response)
jpayne@68 1517 except Error as v:
jpayne@68 1518 print("ERROR", v)