blob: 9f96163be13eb99d5a38d959dfcce791bce893a9 [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 *
Fredrik Lundhb9056332001-07-11 17:42:21 +0000139
140try:
141 unicode
142except NameError:
143 unicode = None # unicode support not available
144
145def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search):
146 # decode non-ascii string (if possible)
147 if unicode and encoding and is8bit(data):
148 data = unicode(data, encoding)
149 return data
150
151if unicode:
152 def _stringify(string):
153 # convert to 7-bit ascii if possible
154 try:
155 return str(string)
156 except UnicodeError:
157 return string
158else:
159 def _stringify(string):
160 return string
161
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000162__version__ = "1.0b3"
Fredrik Lundhb9056332001-07-11 17:42:21 +0000163
164# --------------------------------------------------------------------
165# Exceptions
166
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000167class Error(Exception):
Fred Drake1b410792001-09-04 18:55:03 +0000168 """Base class for client errors."""
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000169 def __str__(self):
170 return repr(self)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000171
172class ProtocolError(Error):
Fred Drake1b410792001-09-04 18:55:03 +0000173 """Indicates an HTTP protocol error."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000174 def __init__(self, url, errcode, errmsg, headers):
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000175 Error.__init__(self)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000176 self.url = url
177 self.errcode = errcode
178 self.errmsg = errmsg
179 self.headers = headers
180 def __repr__(self):
181 return (
182 "<ProtocolError for %s: %s %s>" %
183 (self.url, self.errcode, self.errmsg)
184 )
185
186class ResponseError(Error):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000187 """Indicates a broken response package."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000188 pass
189
190class Fault(Error):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000191 """Indicates an XML-RPC fault package."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000192 def __init__(self, faultCode, faultString, **extra):
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000193 Error.__init__(self)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000194 self.faultCode = faultCode
195 self.faultString = faultString
196 def __repr__(self):
197 return (
198 "<Fault %s: %s>" %
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000199 (self.faultCode, repr(self.faultString))
Fredrik Lundhb9056332001-07-11 17:42:21 +0000200 )
201
202# --------------------------------------------------------------------
203# Special values
204
Fredrik Lundhb9056332001-07-11 17:42:21 +0000205class Boolean:
Fred Drake1b410792001-09-04 18:55:03 +0000206 """Boolean-value wrapper.
207
208 Use True or False to generate a "boolean" XML-RPC value.
209 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000210
211 def __init__(self, value = 0):
212 self.value = operator.truth(value)
213
214 def encode(self, out):
215 out.write("<value><boolean>%d</boolean></value>\n" % self.value)
216
217 def __cmp__(self, other):
218 if isinstance(other, Boolean):
219 other = other.value
220 return cmp(self.value, other)
221
222 def __repr__(self):
223 if self.value:
224 return "<Boolean True at %x>" % id(self)
225 else:
226 return "<Boolean False at %x>" % id(self)
227
228 def __int__(self):
229 return self.value
230
231 def __nonzero__(self):
232 return self.value
233
234True, False = Boolean(1), Boolean(0)
235
236def boolean(value, truefalse=(False, True)):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000237 """Convert any Python value to XML-RPC 'boolean'."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000238 return truefalse[operator.truth(value)]
239
Fredrik Lundhb9056332001-07-11 17:42:21 +0000240class DateTime:
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000241 """DateTime wrapper for an ISO 8601 string or time tuple or
242 localtime integer value to generate 'dateTime.iso8601' XML-RPC
243 value.
244 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000245
246 def __init__(self, value=0):
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000247 if not isinstance(value, StringType):
248 if not isinstance(value, TupleType):
Fredrik Lundhb9056332001-07-11 17:42:21 +0000249 if value == 0:
250 value = time.time()
251 value = time.localtime(value)
252 value = time.strftime("%Y%m%dT%H:%M:%S", value)
253 self.value = value
254
255 def __cmp__(self, other):
256 if isinstance(other, DateTime):
257 other = other.value
258 return cmp(self.value, other)
259
260 def __repr__(self):
261 return "<DateTime %s at %x>" % (self.value, id(self))
262
263 def decode(self, data):
264 self.value = string.strip(data)
265
266 def encode(self, out):
267 out.write("<value><dateTime.iso8601>")
268 out.write(self.value)
269 out.write("</dateTime.iso8601></value>\n")
270
271def datetime(data):
272 value = DateTime()
273 value.decode(data)
274 return value
275
Fredrik Lundhb9056332001-07-11 17:42:21 +0000276class Binary:
Fred Drake1b410792001-09-04 18:55:03 +0000277 """Wrapper for binary data."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000278
279 def __init__(self, data=None):
280 self.data = data
281
282 def __cmp__(self, other):
283 if isinstance(other, Binary):
284 other = other.data
285 return cmp(self.data, other)
286
287 def decode(self, data):
288 import base64
289 self.data = base64.decodestring(data)
290
291 def encode(self, out):
292 import base64, StringIO
293 out.write("<value><base64>\n")
294 base64.encode(StringIO.StringIO(self.data), out)
295 out.write("</base64></value>\n")
296
297def binary(data):
298 value = Binary()
299 value.decode(data)
300 return value
301
302WRAPPERS = DateTime, Binary, Boolean
303
304# --------------------------------------------------------------------
305# XML parsers
306
307try:
308 # optional xmlrpclib accelerator. for more information on this
309 # component, contact info@pythonware.com
310 import _xmlrpclib
311 FastParser = _xmlrpclib.Parser
312 FastUnmarshaller = _xmlrpclib.Unmarshaller
313except (AttributeError, ImportError):
314 FastParser = FastUnmarshaller = None
315
316#
317# the SGMLOP parser is about 15x faster than Python's builtin
318# XML parser. SGMLOP sources can be downloaded from:
319#
320# http://www.pythonware.com/products/xml/sgmlop.htm
321#
322
323try:
324 import sgmlop
325 if not hasattr(sgmlop, "XMLParser"):
326 raise ImportError
327except ImportError:
328 SgmlopParser = None # sgmlop accelerator not available
329else:
330 class SgmlopParser:
331 def __init__(self, target):
332
333 # setup callbacks
334 self.finish_starttag = target.start
335 self.finish_endtag = target.end
336 self.handle_data = target.data
337 self.handle_xml = target.xml
338
339 # activate parser
340 self.parser = sgmlop.XMLParser()
341 self.parser.register(self)
342 self.feed = self.parser.feed
343 self.entity = {
344 "amp": "&", "gt": ">", "lt": "<",
345 "apos": "'", "quot": '"'
346 }
347
348 def close(self):
349 try:
350 self.parser.close()
351 finally:
352 self.parser = self.feed = None # nuke circular reference
353
354 def handle_proc(self, tag, attr):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000355 import re
Fredrik Lundhb9056332001-07-11 17:42:21 +0000356 m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr)
357 if m:
358 self.handle_xml(m.group(1), 1)
359
360 def handle_entityref(self, entity):
361 # <string> entity
362 try:
363 self.handle_data(self.entity[entity])
364 except KeyError:
365 self.handle_data("&%s;" % entity)
366
367try:
368 from xml.parsers import expat
369except ImportError:
370 ExpatParser = None
371else:
372 class ExpatParser:
373 # fast expat parser for Python 2.0. this is about 50%
374 # slower than sgmlop, on roundtrip testing
375 def __init__(self, target):
376 self._parser = parser = expat.ParserCreate(None, None)
377 self._target = target
378 parser.StartElementHandler = target.start
379 parser.EndElementHandler = target.end
380 parser.CharacterDataHandler = target.data
381 encoding = None
382 if not parser.returns_unicode:
383 encoding = "utf-8"
384 target.xml(encoding, None)
385
386 def feed(self, data):
387 self._parser.Parse(data, 0)
388
389 def close(self):
390 self._parser.Parse("", 1) # end of data
391 del self._target, self._parser # get rid of circular references
392
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000393class SlowParser:
394 """Default XML parser (based on xmllib.XMLParser)."""
395 # this is about 10 times slower than sgmlop, on roundtrip
396 # testing.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000397 def __init__(self, target):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000398 import xmllib # lazy subclassing (!)
399 if xmllib.XMLParser not in SlowParser.__bases__:
400 SlowParser.__bases__ = (xmllib.XMLParser,)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000401 self.handle_xml = target.xml
402 self.unknown_starttag = target.start
403 self.handle_data = target.data
404 self.unknown_endtag = target.end
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000405 try:
406 xmllib.XMLParser.__init__(self, accept_utf8=1)
407 except TypeError:
408 xmllib.XMLParser.__init__(self) # pre-2.0
Fredrik Lundhb9056332001-07-11 17:42:21 +0000409
410# --------------------------------------------------------------------
411# XML-RPC marshalling and unmarshalling code
412
413class Marshaller:
Fred Drake1b410792001-09-04 18:55:03 +0000414 """Generate an XML-RPC params chunk from a Python data structure.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000415
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000416 Create a Marshaller instance for each set of parameters, and use
417 the "dumps" method to convert your data (represented as a tuple)
418 to an XML-RPC params chunk. To write a fault response, pass a
419 Fault instance instead. You may prefer to use the "dumps" module
420 function for this purpose.
Fred Drake1b410792001-09-04 18:55:03 +0000421 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000422
423 # by the way, if you don't understand what's going on in here,
424 # that's perfectly ok.
425
426 def __init__(self, encoding=None):
427 self.memo = {}
428 self.data = None
429 self.encoding = encoding
430
431 dispatch = {}
432
433 def dumps(self, values):
434 self.__out = []
435 self.write = write = self.__out.append
436 if isinstance(values, Fault):
437 # fault instance
438 write("<fault>\n")
439 self.__dump(vars(values))
440 write("</fault>\n")
441 else:
442 # parameter block
Fredrik Lundhc266bb02001-08-23 20:13:08 +0000443 # FIXME: the xml-rpc specification allows us to leave out
444 # the entire <params> block if there are no parameters.
445 # however, changing this may break older code (including
446 # old versions of xmlrpclib.py), so this is better left as
447 # is for now. See @XMLRPC3 for more information. /F
Fredrik Lundhb9056332001-07-11 17:42:21 +0000448 write("<params>\n")
449 for v in values:
450 write("<param>\n")
451 self.__dump(v)
452 write("</param>\n")
453 write("</params>\n")
454 result = string.join(self.__out, "")
455 del self.__out, self.write # don't need this any more
456 return result
457
458 def __dump(self, value):
459 try:
460 f = self.dispatch[type(value)]
461 except KeyError:
462 raise TypeError, "cannot marshal %s objects" % type(value)
463 else:
464 f(self, value)
465
466 def dump_int(self, value):
467 self.write("<value><int>%s</int></value>\n" % value)
468 dispatch[IntType] = dump_int
469
470 def dump_double(self, value):
471 self.write("<value><double>%s</double></value>\n" % value)
472 dispatch[FloatType] = dump_double
473
474 def dump_string(self, value):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000475 from cgi import escape
Fredrik Lundhb9056332001-07-11 17:42:21 +0000476 self.write("<value><string>%s</string></value>\n" % escape(value))
477 dispatch[StringType] = dump_string
478
479 if unicode:
480 def dump_unicode(self, value):
481 value = value.encode(self.encoding)
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000482 from cgi import escape
Fredrik Lundhb9056332001-07-11 17:42:21 +0000483 self.write("<value><string>%s</string></value>\n" % escape(value))
484 dispatch[UnicodeType] = dump_unicode
485
486 def container(self, value):
487 if value:
488 i = id(value)
489 if self.memo.has_key(i):
490 raise TypeError, "cannot marshal recursive data structures"
491 self.memo[i] = None
492
493 def dump_array(self, value):
494 self.container(value)
495 write = self.write
496 write("<value><array><data>\n")
497 for v in value:
498 self.__dump(v)
499 write("</data></array></value>\n")
500 dispatch[TupleType] = dump_array
501 dispatch[ListType] = dump_array
502
503 def dump_struct(self, value):
504 self.container(value)
505 write = self.write
506 write("<value><struct>\n")
507 for k, v in value.items():
508 write("<member>\n")
509 if type(k) is not StringType:
510 raise TypeError, "dictionary key must be string"
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000511 from cgi import escape
Fredrik Lundhb9056332001-07-11 17:42:21 +0000512 write("<name>%s</name>\n" % escape(k))
513 self.__dump(v)
514 write("</member>\n")
515 write("</struct></value>\n")
516 dispatch[DictType] = dump_struct
517
518 def dump_instance(self, value):
519 # check for special wrappers
520 if value.__class__ in WRAPPERS:
521 value.encode(self)
522 else:
523 # store instance attributes as a struct (really?)
524 self.dump_struct(value.__dict__)
525 dispatch[InstanceType] = dump_instance
526
527class Unmarshaller:
Fred Drake1b410792001-09-04 18:55:03 +0000528 """Unmarshal an XML-RPC response, based on incoming XML event
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000529 messages (start, data, end). Call close() to get the resulting
Fred Drake1b410792001-09-04 18:55:03 +0000530 data structure.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000531
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000532 Note that this reader is fairly tolerant, and gladly accepts bogus
533 XML-RPC data without complaining (but not bogus XML).
Fred Drake1b410792001-09-04 18:55:03 +0000534 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000535
536 # and again, if you don't understand what's going on in here,
537 # that's perfectly ok.
538
539 def __init__(self):
540 self._type = None
541 self._stack = []
542 self._marks = []
543 self._data = []
544 self._methodname = None
545 self._encoding = "utf-8"
546 self.append = self._stack.append
547
548 def close(self):
549 # return response tuple and target method
550 if self._type is None or self._marks:
551 raise ResponseError()
552 if self._type == "fault":
553 raise apply(Fault, (), self._stack[0])
554 return tuple(self._stack)
555
556 def getmethodname(self):
557 return self._methodname
558
559 #
560 # event handlers
561
562 def xml(self, encoding, standalone):
563 self._encoding = encoding
564 # FIXME: assert standalone == 1 ???
565
566 def start(self, tag, attrs):
567 # prepare to handle this element
568 if tag == "array" or tag == "struct":
569 self._marks.append(len(self._stack))
570 self._data = []
571 self._value = (tag == "value")
572
573 def data(self, text):
574 self._data.append(text)
575
576 def end(self, tag):
577 # call the appropriate end tag handler
578 try:
579 f = self.dispatch[tag]
580 except KeyError:
581 pass # unknown tag ?
582 else:
583 return f(self, self._data)
584
585 #
586 # accelerator support
587
588 def end_dispatch(self, tag, data):
589 # dispatch data
590 try:
591 f = self.dispatch[tag]
592 except KeyError:
593 pass # unknown tag ?
594 else:
595 return f(self, data)
596
597 #
598 # element decoders
599
600 dispatch = {}
601
602 def end_boolean(self, data, join=string.join):
603 data = join(data, "")
604 if data == "0":
605 self.append(False)
606 elif data == "1":
607 self.append(True)
608 else:
609 raise TypeError, "bad boolean value"
610 self._value = 0
611 dispatch["boolean"] = end_boolean
612
613 def end_int(self, data, join=string.join):
614 self.append(int(join(data, "")))
615 self._value = 0
616 dispatch["i4"] = end_int
617 dispatch["int"] = end_int
618
619 def end_double(self, data, join=string.join):
620 self.append(float(join(data, "")))
621 self._value = 0
622 dispatch["double"] = end_double
623
624 def end_string(self, data, join=string.join):
625 data = join(data, "")
626 if self._encoding:
627 data = _decode(data, self._encoding)
628 self.append(_stringify(data))
629 self._value = 0
630 dispatch["string"] = end_string
631 dispatch["name"] = end_string # struct keys are always strings
632
633 def end_array(self, data):
634 mark = self._marks[-1]
635 del self._marks[-1]
636 # map arrays to Python lists
637 self._stack[mark:] = [self._stack[mark:]]
638 self._value = 0
639 dispatch["array"] = end_array
640
641 def end_struct(self, data):
642 mark = self._marks[-1]
643 del self._marks[-1]
644 # map structs to Python dictionaries
645 dict = {}
646 items = self._stack[mark:]
647 for i in range(0, len(items), 2):
648 dict[_stringify(items[i])] = items[i+1]
649 self._stack[mark:] = [dict]
650 self._value = 0
651 dispatch["struct"] = end_struct
652
653 def end_base64(self, data, join=string.join):
654 value = Binary()
655 value.decode(join(data, ""))
656 self.append(value)
657 self._value = 0
658 dispatch["base64"] = end_base64
659
660 def end_dateTime(self, data, join=string.join):
661 value = DateTime()
662 value.decode(join(data, ""))
663 self.append(value)
664 dispatch["dateTime.iso8601"] = end_dateTime
665
666 def end_value(self, data):
667 # if we stumble upon an value element with no internal
668 # elements, treat it as a string element
669 if self._value:
670 self.end_string(data)
671 dispatch["value"] = end_value
672
673 def end_params(self, data):
674 self._type = "params"
675 dispatch["params"] = end_params
676
677 def end_fault(self, data):
678 self._type = "fault"
679 dispatch["fault"] = end_fault
680
681 def end_methodName(self, data, join=string.join):
682 data = join(data, "")
683 if self._encoding:
684 data = _decode(data, self._encoding)
685 self._methodname = data
686 self._type = "methodName" # no params
687 dispatch["methodName"] = end_methodName
688
689
690# --------------------------------------------------------------------
691# convenience functions
692
693def getparser():
694 """getparser() -> parser, unmarshaller
695
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000696 Create an instance of the fastest available parser, and attach it
697 to an unmarshalling object. Return both objects.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000698 """
699 if FastParser and FastUnmarshaller:
700 target = FastUnmarshaller(True, False, binary, datetime)
701 parser = FastParser(target)
702 else:
703 target = Unmarshaller()
704 if FastParser:
705 parser = FastParser(target)
706 elif SgmlopParser:
707 parser = SgmlopParser(target)
708 elif ExpatParser:
709 parser = ExpatParser(target)
710 else:
711 parser = SlowParser(target)
712 return parser, target
713
714def dumps(params, methodname=None, methodresponse=None, encoding=None):
715 """data [,options] -> marshalled data
716
717 Convert an argument tuple or a Fault instance to an XML-RPC
718 request (or response, if the methodresponse option is used).
719
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000720 In addition to the data object, the following options can be given
721 as keyword arguments:
Fredrik Lundhb9056332001-07-11 17:42:21 +0000722
723 methodname: the method name for a methodCall packet
724
725 methodresponse: true to create a methodResponse packet.
726 If this option is used with a tuple, the tuple must be
727 a singleton (i.e. it can contain only one element).
728
729 encoding: the packet encoding (default is UTF-8)
730
731 All 8-bit strings in the data structure are assumed to use the
732 packet encoding. Unicode strings are automatically converted,
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000733 where necessary.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000734 """
735
736 assert isinstance(params, TupleType) or isinstance(params, Fault),\
737 "argument must be tuple or Fault instance"
738
739 if isinstance(params, Fault):
740 methodresponse = 1
741 elif methodresponse and isinstance(params, TupleType):
742 assert len(params) == 1, "response tuple must be a singleton"
743
744 if not encoding:
745 encoding = "utf-8"
746
747 m = Marshaller(encoding)
748 data = m.dumps(params)
749
750 if encoding != "utf-8":
751 xmlheader = "<?xml version='1.0' encoding=%s?>\n" % repr(encoding)
752 else:
753 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
754
755 # standard XML-RPC wrappings
756 if methodname:
757 # a method call
758 if not isinstance(methodname, StringType):
759 methodname = methodname.encode(encoding)
760 data = (
761 xmlheader,
762 "<methodCall>\n"
763 "<methodName>", methodname, "</methodName>\n",
764 data,
765 "</methodCall>\n"
766 )
767 elif methodresponse:
768 # a method response, or a fault structure
769 data = (
770 xmlheader,
771 "<methodResponse>\n",
772 data,
773 "</methodResponse>\n"
774 )
775 else:
776 return data # return as is
777 return string.join(data, "")
778
779def loads(data):
780 """data -> unmarshalled data, method name
781
782 Convert an XML-RPC packet to unmarshalled data plus a method
783 name (None if not present).
784
785 If the XML-RPC packet represents a fault condition, this function
786 raises a Fault exception.
787 """
788 p, u = getparser()
789 p.feed(data)
790 p.close()
791 return u.close(), u.getmethodname()
792
793
794# --------------------------------------------------------------------
795# request dispatcher
796
797class _Method:
798 # some magic to bind an XML-RPC method to an RPC server.
799 # supports "nested" methods (e.g. examples.getStateName)
800 def __init__(self, send, name):
801 self.__send = send
802 self.__name = name
803 def __getattr__(self, name):
804 return _Method(self.__send, "%s.%s" % (self.__name, name))
805 def __call__(self, *args):
806 return self.__send(self.__name, args)
807
808
809class Transport:
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000810 """Handles an HTTP transaction to an XML-RPC server."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000811
812 # client identifier (may be overridden)
813 user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
814
815 def request(self, host, handler, request_body, verbose=0):
816 # issue XML-RPC request
817
818 h = self.make_connection(host)
819 if verbose:
820 h.set_debuglevel(1)
821
822 self.send_request(h, handler, request_body)
823 self.send_host(h, host)
824 self.send_user_agent(h)
825 self.send_content(h, request_body)
826
827 errcode, errmsg, headers = h.getreply()
828
829 if errcode != 200:
830 raise ProtocolError(
831 host + handler,
832 errcode, errmsg,
833 headers
834 )
835
836 self.verbose = verbose
837
838 return self.parse_response(h.getfile())
839
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000840 def getparser(self):
841 # get parser and unmarshaller
842 return getparser()
843
Fredrik Lundhb9056332001-07-11 17:42:21 +0000844 def make_connection(self, host):
845 # create a HTTP connection object from a host descriptor
846 import httplib
847 return httplib.HTTP(host)
848
849 def send_request(self, connection, handler, request_body):
850 connection.putrequest("POST", handler)
851
852 def send_host(self, connection, host):
853 connection.putheader("Host", host)
854
855 def send_user_agent(self, connection):
856 connection.putheader("User-Agent", self.user_agent)
857
858 def send_content(self, connection, request_body):
859 connection.putheader("Content-Type", "text/xml")
860 connection.putheader("Content-Length", str(len(request_body)))
861 connection.endheaders()
862 if request_body:
863 connection.send(request_body)
864
865 def parse_response(self, f):
866 # read response from input file, and parse it
867
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000868 p, u = self.getparser()
Fredrik Lundhb9056332001-07-11 17:42:21 +0000869
870 while 1:
871 response = f.read(1024)
872 if not response:
873 break
874 if self.verbose:
875 print "body:", repr(response)
876 p.feed(response)
877
878 f.close()
879 p.close()
880
881 return u.close()
882
883class SafeTransport(Transport):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000884 """Handles an HTTPS transaction to an XML-RPC server."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000885
886 def make_connection(self, host):
887 # create a HTTPS connection object from a host descriptor
888 # host may be a string, or a (host, x509-dict) tuple
889 import httplib
890 if isinstance(host, TupleType):
891 host, x509 = host
892 else:
893 x509 = {}
894 try:
895 HTTPS = httplib.HTTPS
896 except AttributeError:
897 raise NotImplementedError,\
898 "your version of httplib doesn't support HTTPS"
899 else:
900 return apply(HTTPS, (host, None), x509)
901
902 def send_host(self, connection, host):
903 if isinstance(host, TupleType):
904 host, x509 = host
905 connection.putheader("Host", host)
906
907class ServerProxy:
908 """uri [,options] -> a logical connection to an XML-RPC server
909
910 uri is the connection point on the server, given as
911 scheme://host/target.
912
913 The standard implementation always supports the "http" scheme. If
914 SSL socket support is available (Python 2.0), it also supports
915 "https".
916
917 If the target part and the slash preceding it are both omitted,
918 "/RPC2" is assumed.
919
920 The following options can be given as keyword arguments:
921
922 transport: a transport factory
923 encoding: the request encoding (default is UTF-8)
924
925 All 8-bit strings passed to the server proxy are assumed to use
926 the given encoding.
927 """
928
929 def __init__(self, uri, transport=None, encoding=None, verbose=0):
930 # establish a "logical" server connection
931
932 # get the url
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000933 import urllib
Fredrik Lundhb9056332001-07-11 17:42:21 +0000934 type, uri = urllib.splittype(uri)
935 if type not in ("http", "https"):
936 raise IOError, "unsupported XML-RPC protocol"
937 self.__host, self.__handler = urllib.splithost(uri)
938 if not self.__handler:
939 self.__handler = "/RPC2"
940
941 if transport is None:
942 if type == "https":
943 transport = SafeTransport()
944 else:
945 transport = Transport()
946 self.__transport = transport
947
948 self.__encoding = encoding
949 self.__verbose = verbose
950
951 def __request(self, methodname, params):
952 # call a method on the remote server
953
954 request = dumps(params, methodname, encoding=self.__encoding)
955
956 response = self.__transport.request(
957 self.__host,
958 self.__handler,
959 request,
960 verbose=self.__verbose
961 )
962
963 if len(response) == 1:
964 response = response[0]
965
966 return response
967
968 def __repr__(self):
969 return (
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000970 "<ServerProxy for %s%s>" %
Fredrik Lundhb9056332001-07-11 17:42:21 +0000971 (self.__host, self.__handler)
972 )
973
974 __str__ = __repr__
975
976 def __getattr__(self, name):
977 # magic method dispatcher
978 return _Method(self.__request, name)
979
980 # note: to call a remote object with an non-standard name, use
981 # result getattr(server, "strange-python-name")(args)
982
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000983# compatibility
Fredrik Lundhb9056332001-07-11 17:42:21 +0000984Server = ServerProxy
985
986# --------------------------------------------------------------------
987# test code
988
989if __name__ == "__main__":
990
991 # simple test program (from the XML-RPC specification)
992
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000993 # server = ServerProxy("http://localhost:8000") # local server
994 server = ServerProxy("http://betty.userland.com")
Fredrik Lundhb9056332001-07-11 17:42:21 +0000995
996 print server
997
998 try:
999 print server.examples.getStateName(41)
1000 except Error, v:
1001 print "ERROR", v