blob: 37c9429da41444784b9ba292fc33a2abf57d9477 [file] [log] [blame]
Fredrik Lundhb9056332001-07-11 17:42:21 +00001#
2# XML-RPC CLIENT LIBRARY
3# $Id$
4#
Fredrik Lundhc4c062f2001-09-10 19:45:02 +00005# an XML-RPC client interface for Python.
6#
7# the marshalling and response parser code can also be used to
8# implement XML-RPC servers.
9#
10# Notes:
11# this version is designed to work with Python 1.5.2 or newer.
12# unicode encoding support requires at least Python 1.6.
13# experimental HTTPS requires Python 2.0 built with SSL sockets.
14# expat parser support requires Python 2.0 with pyexpat support.
15#
Fredrik Lundhb9056332001-07-11 17:42:21 +000016# History:
17# 1999-01-14 fl Created
18# 1999-01-15 fl Changed dateTime to use localtime
19# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service
20# 1999-01-19 fl Fixed array data element (from Skip Montanaro)
21# 1999-01-21 fl Fixed dateTime constructor, etc.
22# 1999-02-02 fl Added fault handling, handle empty sequences, etc.
23# 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro)
24# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8)
25# 2000-11-28 fl Changed boolean to check the truth value of its argument
26# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches
27# 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1)
28# 2001-03-28 fl Make sure response tuple is a singleton
29# 2001-03-29 fl Don't require empty params element (from Nicholas Riley)
Fredrik Lundh78eedce2001-08-23 20:04:33 +000030# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2)
31# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod)
Fredrik Lundhc4c062f2001-09-10 19:45:02 +000032# 2001-09-03 fl Allow Transport subclass to override getparser
33# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup)
Fredrik Lundhb9056332001-07-11 17:42:21 +000034#
35# Copyright (c) 1999-2001 by Secret Labs AB.
36# Copyright (c) 1999-2001 by Fredrik Lundh.
37#
38# info@pythonware.com
39# http://www.pythonware.com
40#
41# --------------------------------------------------------------------
42# The XML-RPC client interface is
43#
44# Copyright (c) 1999-2001 by Secret Labs AB
45# Copyright (c) 1999-2001 by Fredrik Lundh
46#
47# By obtaining, using, and/or copying this software and/or its
48# associated documentation, you agree that you have read, understood,
49# and will comply with the following terms and conditions:
50#
51# Permission to use, copy, modify, and distribute this software and
52# its associated documentation for any purpose and without fee is
53# hereby granted, provided that the above copyright notice appears in
54# all copies, and that both that copyright notice and this permission
55# notice appear in supporting documentation, and that the name of
56# Secret Labs AB or the author not be used in advertising or publicity
57# pertaining to distribution of the software without specific, written
58# prior permission.
59#
60# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
61# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
62# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
63# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
64# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
65# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
66# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
67# OF THIS SOFTWARE.
68# --------------------------------------------------------------------
69
70#
71# things to look into before 1.0 final:
72
73# TODO: unicode marshalling -DONE
74# TODO: ascii-compatible encoding support -DONE
75# TODO: safe transport -DONE (but mostly untested)
76# TODO: sgmlop memory leak -DONE
77# TODO: sgmlop xml parsing -DONE
78# TODO: support unicode method names -DONE
79# TODO: update selftest -DONE
80# TODO: add docstrings -DONE
81# TODO: clean up parser encoding (trust the parser) -DONE
82# TODO: expat support -DONE
83# TODO: _xmlrpclib accelerator support -DONE
84# TODO: use smarter/faster escape from effdom
85# TODO: support basic authentication (see robin's patch)
86# TODO: fix host tuple handling in the server constructor
87# TODO: let transport verify schemes
88# TODO: update documentation
89# TODO: authentication plugins
90# TODO: memo problem (see HP's mail)
91
Fred Drake1b410792001-09-04 18:55:03 +000092"""
93An XML-RPC client interface for Python.
94
95The marshalling and response parser code can also be used to
96implement XML-RPC servers.
97
Fred Drake1b410792001-09-04 18:55:03 +000098Exported exceptions:
99
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000100 Error Base class for client errors
101 ProtocolError Indicates an HTTP protocol error
102 ResponseError Indicates a broken response package
103 Fault Indicates an XML-RPC fault package
Fred Drake1b410792001-09-04 18:55:03 +0000104
105Exported classes:
106
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000107 ServerProxy Represents a logical connection to an XML-RPC server
Fred Drake1b410792001-09-04 18:55:03 +0000108
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000109 Boolean boolean wrapper to generate a "boolean" XML-RPC value
110 DateTime dateTime wrapper for an ISO 8601 string or time tuple or
111 localtime integer value to generate a "dateTime.iso8601"
112 XML-RPC value
113 Binary binary data wrapper
Fred Drake1b410792001-09-04 18:55:03 +0000114
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000115 SlowParser Slow but safe standard parser (based on xmllib)
116 Marshaller Generate an XML-RPC params chunk from a Python data structure
117 Unmarshaller Unmarshal an XML-RPC response from incoming XML event message
118 Transport Handles an HTTP transaction to an XML-RPC server
119 SafeTransport Handles an HTTPS transaction to an XML-RPC server
Fred Drake1b410792001-09-04 18:55:03 +0000120
121Exported constants:
122
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000123 True
124 False
Fred Drake1b410792001-09-04 18:55:03 +0000125
126Exported functions:
127
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000128 boolean Convert any Python value to an XML-RPC boolean
129 getparser Create instance of the fastest available parser & attach
130 to an unmarshalling object
131 dumps Convert an argument tuple or a Fault instance to an XML-RPC
132 request (or response, if the methodresponse option is used).
133 loads Convert an XML-RPC packet to unmarshalled data plus a method
134 name (None if not present).
Fred Drake1b410792001-09-04 18:55:03 +0000135"""
136
Fredrik Lundhb9056332001-07-11 17:42:21 +0000137import re, string, time, operator
Fredrik Lundhb9056332001-07-11 17:42:21 +0000138from types import *
Skip Montanarofbacaf72001-10-01 17:50:29 +0000139from cgi import escape as _escape
Fredrik Lundhb9056332001-07-11 17:42:21 +0000140
141try:
142 unicode
143except NameError:
144 unicode = None # unicode support not available
145
146def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search):
147 # decode non-ascii string (if possible)
148 if unicode and encoding and is8bit(data):
149 data = unicode(data, encoding)
150 return data
151
152if unicode:
153 def _stringify(string):
154 # convert to 7-bit ascii if possible
155 try:
156 return str(string)
157 except UnicodeError:
158 return string
159else:
160 def _stringify(string):
161 return string
162
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000163__version__ = "1.0b3"
Fredrik Lundhb9056332001-07-11 17:42:21 +0000164
165# --------------------------------------------------------------------
166# Exceptions
167
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000168class Error(Exception):
Fred Drake1b410792001-09-04 18:55:03 +0000169 """Base class for client errors."""
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000170 def __str__(self):
171 return repr(self)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000172
173class ProtocolError(Error):
Fred Drake1b410792001-09-04 18:55:03 +0000174 """Indicates an HTTP protocol error."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000175 def __init__(self, url, errcode, errmsg, headers):
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000176 Error.__init__(self)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000177 self.url = url
178 self.errcode = errcode
179 self.errmsg = errmsg
180 self.headers = headers
181 def __repr__(self):
182 return (
183 "<ProtocolError for %s: %s %s>" %
184 (self.url, self.errcode, self.errmsg)
185 )
186
187class ResponseError(Error):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000188 """Indicates a broken response package."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000189 pass
190
191class Fault(Error):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000192 """Indicates an XML-RPC fault package."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000193 def __init__(self, faultCode, faultString, **extra):
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000194 Error.__init__(self)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000195 self.faultCode = faultCode
196 self.faultString = faultString
197 def __repr__(self):
198 return (
199 "<Fault %s: %s>" %
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000200 (self.faultCode, repr(self.faultString))
Fredrik Lundhb9056332001-07-11 17:42:21 +0000201 )
202
203# --------------------------------------------------------------------
204# Special values
205
Fredrik Lundhb9056332001-07-11 17:42:21 +0000206class Boolean:
Fred Drake1b410792001-09-04 18:55:03 +0000207 """Boolean-value wrapper.
208
209 Use True or False to generate a "boolean" XML-RPC value.
210 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000211
212 def __init__(self, value = 0):
213 self.value = operator.truth(value)
214
215 def encode(self, out):
216 out.write("<value><boolean>%d</boolean></value>\n" % self.value)
217
218 def __cmp__(self, other):
219 if isinstance(other, Boolean):
220 other = other.value
221 return cmp(self.value, other)
222
223 def __repr__(self):
224 if self.value:
225 return "<Boolean True at %x>" % id(self)
226 else:
227 return "<Boolean False at %x>" % id(self)
228
229 def __int__(self):
230 return self.value
231
232 def __nonzero__(self):
233 return self.value
234
235True, False = Boolean(1), Boolean(0)
236
237def boolean(value, truefalse=(False, True)):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000238 """Convert any Python value to XML-RPC 'boolean'."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000239 return truefalse[operator.truth(value)]
240
Fredrik Lundhb9056332001-07-11 17:42:21 +0000241class DateTime:
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000242 """DateTime wrapper for an ISO 8601 string or time tuple or
243 localtime integer value to generate 'dateTime.iso8601' XML-RPC
244 value.
245 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000246
247 def __init__(self, value=0):
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000248 if not isinstance(value, StringType):
249 if not isinstance(value, TupleType):
Fredrik Lundhb9056332001-07-11 17:42:21 +0000250 if value == 0:
251 value = time.time()
252 value = time.localtime(value)
253 value = time.strftime("%Y%m%dT%H:%M:%S", value)
254 self.value = value
255
256 def __cmp__(self, other):
257 if isinstance(other, DateTime):
258 other = other.value
259 return cmp(self.value, other)
260
261 def __repr__(self):
262 return "<DateTime %s at %x>" % (self.value, id(self))
263
264 def decode(self, data):
265 self.value = string.strip(data)
266
267 def encode(self, out):
268 out.write("<value><dateTime.iso8601>")
269 out.write(self.value)
270 out.write("</dateTime.iso8601></value>\n")
271
272def datetime(data):
273 value = DateTime()
274 value.decode(data)
275 return value
276
Fredrik Lundhb9056332001-07-11 17:42:21 +0000277class Binary:
Fred Drake1b410792001-09-04 18:55:03 +0000278 """Wrapper for binary data."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000279
280 def __init__(self, data=None):
281 self.data = data
282
283 def __cmp__(self, other):
284 if isinstance(other, Binary):
285 other = other.data
286 return cmp(self.data, other)
287
288 def decode(self, data):
289 import base64
290 self.data = base64.decodestring(data)
291
292 def encode(self, out):
293 import base64, StringIO
294 out.write("<value><base64>\n")
295 base64.encode(StringIO.StringIO(self.data), out)
296 out.write("</base64></value>\n")
297
298def binary(data):
299 value = Binary()
300 value.decode(data)
301 return value
302
303WRAPPERS = DateTime, Binary, Boolean
304
305# --------------------------------------------------------------------
306# XML parsers
307
308try:
309 # optional xmlrpclib accelerator. for more information on this
310 # component, contact info@pythonware.com
311 import _xmlrpclib
312 FastParser = _xmlrpclib.Parser
313 FastUnmarshaller = _xmlrpclib.Unmarshaller
314except (AttributeError, ImportError):
315 FastParser = FastUnmarshaller = None
316
317#
318# the SGMLOP parser is about 15x faster than Python's builtin
319# XML parser. SGMLOP sources can be downloaded from:
320#
321# http://www.pythonware.com/products/xml/sgmlop.htm
322#
323
324try:
325 import sgmlop
326 if not hasattr(sgmlop, "XMLParser"):
327 raise ImportError
328except ImportError:
329 SgmlopParser = None # sgmlop accelerator not available
330else:
331 class SgmlopParser:
332 def __init__(self, target):
333
334 # setup callbacks
335 self.finish_starttag = target.start
336 self.finish_endtag = target.end
337 self.handle_data = target.data
338 self.handle_xml = target.xml
339
340 # activate parser
341 self.parser = sgmlop.XMLParser()
342 self.parser.register(self)
343 self.feed = self.parser.feed
344 self.entity = {
345 "amp": "&", "gt": ">", "lt": "<",
346 "apos": "'", "quot": '"'
347 }
348
349 def close(self):
350 try:
351 self.parser.close()
352 finally:
353 self.parser = self.feed = None # nuke circular reference
354
355 def handle_proc(self, tag, attr):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000356 import re
Fredrik Lundhb9056332001-07-11 17:42:21 +0000357 m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr)
358 if m:
359 self.handle_xml(m.group(1), 1)
360
361 def handle_entityref(self, entity):
362 # <string> entity
363 try:
364 self.handle_data(self.entity[entity])
365 except KeyError:
366 self.handle_data("&%s;" % entity)
367
368try:
369 from xml.parsers import expat
370except ImportError:
371 ExpatParser = None
372else:
373 class ExpatParser:
374 # fast expat parser for Python 2.0. this is about 50%
375 # slower than sgmlop, on roundtrip testing
376 def __init__(self, target):
377 self._parser = parser = expat.ParserCreate(None, None)
378 self._target = target
379 parser.StartElementHandler = target.start
380 parser.EndElementHandler = target.end
381 parser.CharacterDataHandler = target.data
382 encoding = None
383 if not parser.returns_unicode:
384 encoding = "utf-8"
385 target.xml(encoding, None)
386
387 def feed(self, data):
388 self._parser.Parse(data, 0)
389
390 def close(self):
391 self._parser.Parse("", 1) # end of data
392 del self._target, self._parser # get rid of circular references
393
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000394class SlowParser:
395 """Default XML parser (based on xmllib.XMLParser)."""
396 # this is about 10 times slower than sgmlop, on roundtrip
397 # testing.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000398 def __init__(self, target):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000399 import xmllib # lazy subclassing (!)
400 if xmllib.XMLParser not in SlowParser.__bases__:
401 SlowParser.__bases__ = (xmllib.XMLParser,)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000402 self.handle_xml = target.xml
403 self.unknown_starttag = target.start
404 self.handle_data = target.data
405 self.unknown_endtag = target.end
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000406 try:
407 xmllib.XMLParser.__init__(self, accept_utf8=1)
408 except TypeError:
409 xmllib.XMLParser.__init__(self) # pre-2.0
Fredrik Lundhb9056332001-07-11 17:42:21 +0000410
411# --------------------------------------------------------------------
412# XML-RPC marshalling and unmarshalling code
413
414class Marshaller:
Fred Drake1b410792001-09-04 18:55:03 +0000415 """Generate an XML-RPC params chunk from a Python data structure.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000416
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000417 Create a Marshaller instance for each set of parameters, and use
418 the "dumps" method to convert your data (represented as a tuple)
419 to an XML-RPC params chunk. To write a fault response, pass a
420 Fault instance instead. You may prefer to use the "dumps" module
421 function for this purpose.
Fred Drake1b410792001-09-04 18:55:03 +0000422 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000423
424 # by the way, if you don't understand what's going on in here,
425 # that's perfectly ok.
426
427 def __init__(self, encoding=None):
428 self.memo = {}
429 self.data = None
430 self.encoding = encoding
431
432 dispatch = {}
433
434 def dumps(self, values):
435 self.__out = []
436 self.write = write = self.__out.append
437 if isinstance(values, Fault):
438 # fault instance
439 write("<fault>\n")
440 self.__dump(vars(values))
441 write("</fault>\n")
442 else:
443 # parameter block
Fredrik Lundhc266bb02001-08-23 20:13:08 +0000444 # FIXME: the xml-rpc specification allows us to leave out
445 # the entire <params> block if there are no parameters.
446 # however, changing this may break older code (including
447 # old versions of xmlrpclib.py), so this is better left as
448 # is for now. See @XMLRPC3 for more information. /F
Fredrik Lundhb9056332001-07-11 17:42:21 +0000449 write("<params>\n")
450 for v in values:
451 write("<param>\n")
452 self.__dump(v)
453 write("</param>\n")
454 write("</params>\n")
455 result = string.join(self.__out, "")
456 del self.__out, self.write # don't need this any more
457 return result
458
459 def __dump(self, value):
460 try:
461 f = self.dispatch[type(value)]
462 except KeyError:
463 raise TypeError, "cannot marshal %s objects" % type(value)
464 else:
465 f(self, value)
466
467 def dump_int(self, value):
468 self.write("<value><int>%s</int></value>\n" % value)
469 dispatch[IntType] = dump_int
470
471 def dump_double(self, value):
472 self.write("<value><double>%s</double></value>\n" % value)
473 dispatch[FloatType] = dump_double
474
475 def dump_string(self, value):
Skip Montanarofbacaf72001-10-01 17:50:29 +0000476 self.write("<value><string>%s</string></value>\n" % _escape(value))
Fredrik Lundhb9056332001-07-11 17:42:21 +0000477 dispatch[StringType] = dump_string
478
479 if unicode:
480 def dump_unicode(self, value):
481 value = value.encode(self.encoding)
Skip Montanarofbacaf72001-10-01 17:50:29 +0000482 self.write("<value><string>%s</string></value>\n" % _escape(value))
Fredrik Lundhb9056332001-07-11 17:42:21 +0000483 dispatch[UnicodeType] = dump_unicode
484
485 def container(self, value):
486 if value:
487 i = id(value)
488 if self.memo.has_key(i):
489 raise TypeError, "cannot marshal recursive data structures"
490 self.memo[i] = None
491
Martin v. Löwis5f12d752001-09-30 20:15:41 +0000492 def endcontainer(self, value):
493 if value:
494 del self.memo[id(value)]
495
Fredrik Lundhb9056332001-07-11 17:42:21 +0000496 def dump_array(self, value):
497 self.container(value)
498 write = self.write
499 write("<value><array><data>\n")
500 for v in value:
501 self.__dump(v)
502 write("</data></array></value>\n")
Martin v. Löwis5f12d752001-09-30 20:15:41 +0000503 self.endcontainer(value)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000504 dispatch[TupleType] = dump_array
505 dispatch[ListType] = dump_array
506
507 def dump_struct(self, value):
508 self.container(value)
509 write = self.write
510 write("<value><struct>\n")
511 for k, v in value.items():
512 write("<member>\n")
513 if type(k) is not StringType:
514 raise TypeError, "dictionary key must be string"
Skip Montanarofbacaf72001-10-01 17:50:29 +0000515 write("<name>%s</name>\n" % _escape(k))
Fredrik Lundhb9056332001-07-11 17:42:21 +0000516 self.__dump(v)
517 write("</member>\n")
518 write("</struct></value>\n")
Martin v. Löwis5f12d752001-09-30 20:15:41 +0000519 self.endcontainer(value)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000520 dispatch[DictType] = dump_struct
521
522 def dump_instance(self, value):
523 # check for special wrappers
524 if value.__class__ in WRAPPERS:
525 value.encode(self)
526 else:
527 # store instance attributes as a struct (really?)
528 self.dump_struct(value.__dict__)
529 dispatch[InstanceType] = dump_instance
530
531class Unmarshaller:
Fred Drake1b410792001-09-04 18:55:03 +0000532 """Unmarshal an XML-RPC response, based on incoming XML event
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000533 messages (start, data, end). Call close() to get the resulting
Fred Drake1b410792001-09-04 18:55:03 +0000534 data structure.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000535
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000536 Note that this reader is fairly tolerant, and gladly accepts bogus
537 XML-RPC data without complaining (but not bogus XML).
Fred Drake1b410792001-09-04 18:55:03 +0000538 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000539
540 # and again, if you don't understand what's going on in here,
541 # that's perfectly ok.
542
543 def __init__(self):
544 self._type = None
545 self._stack = []
546 self._marks = []
547 self._data = []
548 self._methodname = None
549 self._encoding = "utf-8"
550 self.append = self._stack.append
551
552 def close(self):
553 # return response tuple and target method
554 if self._type is None or self._marks:
555 raise ResponseError()
556 if self._type == "fault":
557 raise apply(Fault, (), self._stack[0])
558 return tuple(self._stack)
559
560 def getmethodname(self):
561 return self._methodname
562
563 #
564 # event handlers
565
566 def xml(self, encoding, standalone):
567 self._encoding = encoding
568 # FIXME: assert standalone == 1 ???
569
570 def start(self, tag, attrs):
571 # prepare to handle this element
572 if tag == "array" or tag == "struct":
573 self._marks.append(len(self._stack))
574 self._data = []
575 self._value = (tag == "value")
576
577 def data(self, text):
578 self._data.append(text)
579
580 def end(self, tag):
581 # call the appropriate end tag handler
582 try:
583 f = self.dispatch[tag]
584 except KeyError:
585 pass # unknown tag ?
586 else:
587 return f(self, self._data)
588
589 #
590 # accelerator support
591
592 def end_dispatch(self, tag, data):
593 # dispatch data
594 try:
595 f = self.dispatch[tag]
596 except KeyError:
597 pass # unknown tag ?
598 else:
599 return f(self, data)
600
601 #
602 # element decoders
603
604 dispatch = {}
605
606 def end_boolean(self, data, join=string.join):
607 data = join(data, "")
608 if data == "0":
609 self.append(False)
610 elif data == "1":
611 self.append(True)
612 else:
613 raise TypeError, "bad boolean value"
614 self._value = 0
615 dispatch["boolean"] = end_boolean
616
617 def end_int(self, data, join=string.join):
618 self.append(int(join(data, "")))
619 self._value = 0
620 dispatch["i4"] = end_int
621 dispatch["int"] = end_int
622
623 def end_double(self, data, join=string.join):
624 self.append(float(join(data, "")))
625 self._value = 0
626 dispatch["double"] = end_double
627
628 def end_string(self, data, join=string.join):
629 data = join(data, "")
630 if self._encoding:
631 data = _decode(data, self._encoding)
632 self.append(_stringify(data))
633 self._value = 0
634 dispatch["string"] = end_string
635 dispatch["name"] = end_string # struct keys are always strings
636
637 def end_array(self, data):
638 mark = self._marks[-1]
639 del self._marks[-1]
640 # map arrays to Python lists
641 self._stack[mark:] = [self._stack[mark:]]
642 self._value = 0
643 dispatch["array"] = end_array
644
645 def end_struct(self, data):
646 mark = self._marks[-1]
647 del self._marks[-1]
648 # map structs to Python dictionaries
649 dict = {}
650 items = self._stack[mark:]
651 for i in range(0, len(items), 2):
652 dict[_stringify(items[i])] = items[i+1]
653 self._stack[mark:] = [dict]
654 self._value = 0
655 dispatch["struct"] = end_struct
656
657 def end_base64(self, data, join=string.join):
658 value = Binary()
659 value.decode(join(data, ""))
660 self.append(value)
661 self._value = 0
662 dispatch["base64"] = end_base64
663
664 def end_dateTime(self, data, join=string.join):
665 value = DateTime()
666 value.decode(join(data, ""))
667 self.append(value)
668 dispatch["dateTime.iso8601"] = end_dateTime
669
670 def end_value(self, data):
671 # if we stumble upon an value element with no internal
672 # elements, treat it as a string element
673 if self._value:
674 self.end_string(data)
675 dispatch["value"] = end_value
676
677 def end_params(self, data):
678 self._type = "params"
679 dispatch["params"] = end_params
680
681 def end_fault(self, data):
682 self._type = "fault"
683 dispatch["fault"] = end_fault
684
685 def end_methodName(self, data, join=string.join):
686 data = join(data, "")
687 if self._encoding:
688 data = _decode(data, self._encoding)
689 self._methodname = data
690 self._type = "methodName" # no params
691 dispatch["methodName"] = end_methodName
692
693
694# --------------------------------------------------------------------
695# convenience functions
696
697def getparser():
698 """getparser() -> parser, unmarshaller
699
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000700 Create an instance of the fastest available parser, and attach it
701 to an unmarshalling object. Return both objects.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000702 """
703 if FastParser and FastUnmarshaller:
704 target = FastUnmarshaller(True, False, binary, datetime)
705 parser = FastParser(target)
706 else:
707 target = Unmarshaller()
708 if FastParser:
709 parser = FastParser(target)
710 elif SgmlopParser:
711 parser = SgmlopParser(target)
712 elif ExpatParser:
713 parser = ExpatParser(target)
714 else:
715 parser = SlowParser(target)
716 return parser, target
717
718def dumps(params, methodname=None, methodresponse=None, encoding=None):
719 """data [,options] -> marshalled data
720
721 Convert an argument tuple or a Fault instance to an XML-RPC
722 request (or response, if the methodresponse option is used).
723
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000724 In addition to the data object, the following options can be given
725 as keyword arguments:
Fredrik Lundhb9056332001-07-11 17:42:21 +0000726
727 methodname: the method name for a methodCall packet
728
729 methodresponse: true to create a methodResponse packet.
730 If this option is used with a tuple, the tuple must be
731 a singleton (i.e. it can contain only one element).
732
733 encoding: the packet encoding (default is UTF-8)
734
735 All 8-bit strings in the data structure are assumed to use the
736 packet encoding. Unicode strings are automatically converted,
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000737 where necessary.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000738 """
739
740 assert isinstance(params, TupleType) or isinstance(params, Fault),\
741 "argument must be tuple or Fault instance"
742
743 if isinstance(params, Fault):
744 methodresponse = 1
745 elif methodresponse and isinstance(params, TupleType):
746 assert len(params) == 1, "response tuple must be a singleton"
747
748 if not encoding:
749 encoding = "utf-8"
750
751 m = Marshaller(encoding)
752 data = m.dumps(params)
753
754 if encoding != "utf-8":
755 xmlheader = "<?xml version='1.0' encoding=%s?>\n" % repr(encoding)
756 else:
757 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
758
759 # standard XML-RPC wrappings
760 if methodname:
761 # a method call
762 if not isinstance(methodname, StringType):
763 methodname = methodname.encode(encoding)
764 data = (
765 xmlheader,
766 "<methodCall>\n"
767 "<methodName>", methodname, "</methodName>\n",
768 data,
769 "</methodCall>\n"
770 )
771 elif methodresponse:
772 # a method response, or a fault structure
773 data = (
774 xmlheader,
775 "<methodResponse>\n",
776 data,
777 "</methodResponse>\n"
778 )
779 else:
780 return data # return as is
781 return string.join(data, "")
782
783def loads(data):
784 """data -> unmarshalled data, method name
785
786 Convert an XML-RPC packet to unmarshalled data plus a method
787 name (None if not present).
788
789 If the XML-RPC packet represents a fault condition, this function
790 raises a Fault exception.
791 """
792 p, u = getparser()
793 p.feed(data)
794 p.close()
795 return u.close(), u.getmethodname()
796
797
798# --------------------------------------------------------------------
799# request dispatcher
800
801class _Method:
802 # some magic to bind an XML-RPC method to an RPC server.
803 # supports "nested" methods (e.g. examples.getStateName)
804 def __init__(self, send, name):
805 self.__send = send
806 self.__name = name
807 def __getattr__(self, name):
808 return _Method(self.__send, "%s.%s" % (self.__name, name))
809 def __call__(self, *args):
810 return self.__send(self.__name, args)
811
812
813class Transport:
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000814 """Handles an HTTP transaction to an XML-RPC server."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000815
816 # client identifier (may be overridden)
817 user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
818
819 def request(self, host, handler, request_body, verbose=0):
820 # issue XML-RPC request
821
822 h = self.make_connection(host)
823 if verbose:
824 h.set_debuglevel(1)
825
826 self.send_request(h, handler, request_body)
827 self.send_host(h, host)
828 self.send_user_agent(h)
829 self.send_content(h, request_body)
830
831 errcode, errmsg, headers = h.getreply()
832
833 if errcode != 200:
834 raise ProtocolError(
835 host + handler,
836 errcode, errmsg,
837 headers
838 )
839
840 self.verbose = verbose
841
842 return self.parse_response(h.getfile())
843
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000844 def getparser(self):
845 # get parser and unmarshaller
846 return getparser()
847
Fredrik Lundhb9056332001-07-11 17:42:21 +0000848 def make_connection(self, host):
849 # create a HTTP connection object from a host descriptor
850 import httplib
851 return httplib.HTTP(host)
852
853 def send_request(self, connection, handler, request_body):
854 connection.putrequest("POST", handler)
855
856 def send_host(self, connection, host):
857 connection.putheader("Host", host)
858
859 def send_user_agent(self, connection):
860 connection.putheader("User-Agent", self.user_agent)
861
862 def send_content(self, connection, request_body):
863 connection.putheader("Content-Type", "text/xml")
864 connection.putheader("Content-Length", str(len(request_body)))
865 connection.endheaders()
866 if request_body:
867 connection.send(request_body)
868
869 def parse_response(self, f):
870 # read response from input file, and parse it
871
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000872 p, u = self.getparser()
Fredrik Lundhb9056332001-07-11 17:42:21 +0000873
874 while 1:
875 response = f.read(1024)
876 if not response:
877 break
878 if self.verbose:
879 print "body:", repr(response)
880 p.feed(response)
881
882 f.close()
883 p.close()
884
885 return u.close()
886
887class SafeTransport(Transport):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000888 """Handles an HTTPS transaction to an XML-RPC server."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000889
890 def make_connection(self, host):
891 # create a HTTPS connection object from a host descriptor
892 # host may be a string, or a (host, x509-dict) tuple
893 import httplib
894 if isinstance(host, TupleType):
895 host, x509 = host
896 else:
897 x509 = {}
898 try:
899 HTTPS = httplib.HTTPS
900 except AttributeError:
901 raise NotImplementedError,\
902 "your version of httplib doesn't support HTTPS"
903 else:
904 return apply(HTTPS, (host, None), x509)
905
906 def send_host(self, connection, host):
907 if isinstance(host, TupleType):
908 host, x509 = host
909 connection.putheader("Host", host)
910
911class ServerProxy:
912 """uri [,options] -> a logical connection to an XML-RPC server
913
914 uri is the connection point on the server, given as
915 scheme://host/target.
916
917 The standard implementation always supports the "http" scheme. If
918 SSL socket support is available (Python 2.0), it also supports
919 "https".
920
921 If the target part and the slash preceding it are both omitted,
922 "/RPC2" is assumed.
923
924 The following options can be given as keyword arguments:
925
926 transport: a transport factory
927 encoding: the request encoding (default is UTF-8)
928
929 All 8-bit strings passed to the server proxy are assumed to use
930 the given encoding.
931 """
932
933 def __init__(self, uri, transport=None, encoding=None, verbose=0):
934 # establish a "logical" server connection
935
936 # get the url
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000937 import urllib
Fredrik Lundhb9056332001-07-11 17:42:21 +0000938 type, uri = urllib.splittype(uri)
939 if type not in ("http", "https"):
940 raise IOError, "unsupported XML-RPC protocol"
941 self.__host, self.__handler = urllib.splithost(uri)
942 if not self.__handler:
943 self.__handler = "/RPC2"
944
945 if transport is None:
946 if type == "https":
947 transport = SafeTransport()
948 else:
949 transport = Transport()
950 self.__transport = transport
951
952 self.__encoding = encoding
953 self.__verbose = verbose
954
955 def __request(self, methodname, params):
956 # call a method on the remote server
957
958 request = dumps(params, methodname, encoding=self.__encoding)
959
960 response = self.__transport.request(
961 self.__host,
962 self.__handler,
963 request,
964 verbose=self.__verbose
965 )
966
967 if len(response) == 1:
968 response = response[0]
969
970 return response
971
972 def __repr__(self):
973 return (
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000974 "<ServerProxy for %s%s>" %
Fredrik Lundhb9056332001-07-11 17:42:21 +0000975 (self.__host, self.__handler)
976 )
977
978 __str__ = __repr__
979
980 def __getattr__(self, name):
981 # magic method dispatcher
982 return _Method(self.__request, name)
983
984 # note: to call a remote object with an non-standard name, use
985 # result getattr(server, "strange-python-name")(args)
986
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000987# compatibility
Fredrik Lundhb9056332001-07-11 17:42:21 +0000988Server = ServerProxy
989
990# --------------------------------------------------------------------
991# test code
992
993if __name__ == "__main__":
994
995 # simple test program (from the XML-RPC specification)
996
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000997 # server = ServerProxy("http://localhost:8000") # local server
998 server = ServerProxy("http://betty.userland.com")
Fredrik Lundhb9056332001-07-11 17:42:21 +0000999
1000 print server
1001
1002 try:
1003 print server.examples.getStateName(41)
1004 except Error, v:
1005 print "ERROR", v