blob: ea4f9323a9ce715b9782bbd0b621dd44e6a502b4 [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
Martin v. Löwis5f12d752001-09-30 20:15:41 +0000493 def endcontainer(self, value):
494 if value:
495 del self.memo[id(value)]
496
Fredrik Lundhb9056332001-07-11 17:42:21 +0000497 def dump_array(self, value):
498 self.container(value)
499 write = self.write
500 write("<value><array><data>\n")
501 for v in value:
502 self.__dump(v)
503 write("</data></array></value>\n")
Martin v. Löwis5f12d752001-09-30 20:15:41 +0000504 self.endcontainer(value)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000505 dispatch[TupleType] = dump_array
506 dispatch[ListType] = dump_array
507
508 def dump_struct(self, value):
509 self.container(value)
510 write = self.write
511 write("<value><struct>\n")
512 for k, v in value.items():
513 write("<member>\n")
514 if type(k) is not StringType:
515 raise TypeError, "dictionary key must be string"
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000516 from cgi import escape
Fredrik Lundhb9056332001-07-11 17:42:21 +0000517 write("<name>%s</name>\n" % escape(k))
518 self.__dump(v)
519 write("</member>\n")
520 write("</struct></value>\n")
Martin v. Löwis5f12d752001-09-30 20:15:41 +0000521 self.endcontainer(value)
Fredrik Lundhb9056332001-07-11 17:42:21 +0000522 dispatch[DictType] = dump_struct
523
524 def dump_instance(self, value):
525 # check for special wrappers
526 if value.__class__ in WRAPPERS:
527 value.encode(self)
528 else:
529 # store instance attributes as a struct (really?)
530 self.dump_struct(value.__dict__)
531 dispatch[InstanceType] = dump_instance
532
533class Unmarshaller:
Fred Drake1b410792001-09-04 18:55:03 +0000534 """Unmarshal an XML-RPC response, based on incoming XML event
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000535 messages (start, data, end). Call close() to get the resulting
Fred Drake1b410792001-09-04 18:55:03 +0000536 data structure.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000537
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000538 Note that this reader is fairly tolerant, and gladly accepts bogus
539 XML-RPC data without complaining (but not bogus XML).
Fred Drake1b410792001-09-04 18:55:03 +0000540 """
Fredrik Lundhb9056332001-07-11 17:42:21 +0000541
542 # and again, if you don't understand what's going on in here,
543 # that's perfectly ok.
544
545 def __init__(self):
546 self._type = None
547 self._stack = []
548 self._marks = []
549 self._data = []
550 self._methodname = None
551 self._encoding = "utf-8"
552 self.append = self._stack.append
553
554 def close(self):
555 # return response tuple and target method
556 if self._type is None or self._marks:
557 raise ResponseError()
558 if self._type == "fault":
559 raise apply(Fault, (), self._stack[0])
560 return tuple(self._stack)
561
562 def getmethodname(self):
563 return self._methodname
564
565 #
566 # event handlers
567
568 def xml(self, encoding, standalone):
569 self._encoding = encoding
570 # FIXME: assert standalone == 1 ???
571
572 def start(self, tag, attrs):
573 # prepare to handle this element
574 if tag == "array" or tag == "struct":
575 self._marks.append(len(self._stack))
576 self._data = []
577 self._value = (tag == "value")
578
579 def data(self, text):
580 self._data.append(text)
581
582 def end(self, tag):
583 # call the appropriate end tag handler
584 try:
585 f = self.dispatch[tag]
586 except KeyError:
587 pass # unknown tag ?
588 else:
589 return f(self, self._data)
590
591 #
592 # accelerator support
593
594 def end_dispatch(self, tag, data):
595 # dispatch data
596 try:
597 f = self.dispatch[tag]
598 except KeyError:
599 pass # unknown tag ?
600 else:
601 return f(self, data)
602
603 #
604 # element decoders
605
606 dispatch = {}
607
608 def end_boolean(self, data, join=string.join):
609 data = join(data, "")
610 if data == "0":
611 self.append(False)
612 elif data == "1":
613 self.append(True)
614 else:
615 raise TypeError, "bad boolean value"
616 self._value = 0
617 dispatch["boolean"] = end_boolean
618
619 def end_int(self, data, join=string.join):
620 self.append(int(join(data, "")))
621 self._value = 0
622 dispatch["i4"] = end_int
623 dispatch["int"] = end_int
624
625 def end_double(self, data, join=string.join):
626 self.append(float(join(data, "")))
627 self._value = 0
628 dispatch["double"] = end_double
629
630 def end_string(self, data, join=string.join):
631 data = join(data, "")
632 if self._encoding:
633 data = _decode(data, self._encoding)
634 self.append(_stringify(data))
635 self._value = 0
636 dispatch["string"] = end_string
637 dispatch["name"] = end_string # struct keys are always strings
638
639 def end_array(self, data):
640 mark = self._marks[-1]
641 del self._marks[-1]
642 # map arrays to Python lists
643 self._stack[mark:] = [self._stack[mark:]]
644 self._value = 0
645 dispatch["array"] = end_array
646
647 def end_struct(self, data):
648 mark = self._marks[-1]
649 del self._marks[-1]
650 # map structs to Python dictionaries
651 dict = {}
652 items = self._stack[mark:]
653 for i in range(0, len(items), 2):
654 dict[_stringify(items[i])] = items[i+1]
655 self._stack[mark:] = [dict]
656 self._value = 0
657 dispatch["struct"] = end_struct
658
659 def end_base64(self, data, join=string.join):
660 value = Binary()
661 value.decode(join(data, ""))
662 self.append(value)
663 self._value = 0
664 dispatch["base64"] = end_base64
665
666 def end_dateTime(self, data, join=string.join):
667 value = DateTime()
668 value.decode(join(data, ""))
669 self.append(value)
670 dispatch["dateTime.iso8601"] = end_dateTime
671
672 def end_value(self, data):
673 # if we stumble upon an value element with no internal
674 # elements, treat it as a string element
675 if self._value:
676 self.end_string(data)
677 dispatch["value"] = end_value
678
679 def end_params(self, data):
680 self._type = "params"
681 dispatch["params"] = end_params
682
683 def end_fault(self, data):
684 self._type = "fault"
685 dispatch["fault"] = end_fault
686
687 def end_methodName(self, data, join=string.join):
688 data = join(data, "")
689 if self._encoding:
690 data = _decode(data, self._encoding)
691 self._methodname = data
692 self._type = "methodName" # no params
693 dispatch["methodName"] = end_methodName
694
695
696# --------------------------------------------------------------------
697# convenience functions
698
699def getparser():
700 """getparser() -> parser, unmarshaller
701
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000702 Create an instance of the fastest available parser, and attach it
703 to an unmarshalling object. Return both objects.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000704 """
705 if FastParser and FastUnmarshaller:
706 target = FastUnmarshaller(True, False, binary, datetime)
707 parser = FastParser(target)
708 else:
709 target = Unmarshaller()
710 if FastParser:
711 parser = FastParser(target)
712 elif SgmlopParser:
713 parser = SgmlopParser(target)
714 elif ExpatParser:
715 parser = ExpatParser(target)
716 else:
717 parser = SlowParser(target)
718 return parser, target
719
720def dumps(params, methodname=None, methodresponse=None, encoding=None):
721 """data [,options] -> marshalled data
722
723 Convert an argument tuple or a Fault instance to an XML-RPC
724 request (or response, if the methodresponse option is used).
725
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000726 In addition to the data object, the following options can be given
727 as keyword arguments:
Fredrik Lundhb9056332001-07-11 17:42:21 +0000728
729 methodname: the method name for a methodCall packet
730
731 methodresponse: true to create a methodResponse packet.
732 If this option is used with a tuple, the tuple must be
733 a singleton (i.e. it can contain only one element).
734
735 encoding: the packet encoding (default is UTF-8)
736
737 All 8-bit strings in the data structure are assumed to use the
738 packet encoding. Unicode strings are automatically converted,
Fredrik Lundhb0e8e9b2001-09-10 21:45:42 +0000739 where necessary.
Fredrik Lundhb9056332001-07-11 17:42:21 +0000740 """
741
742 assert isinstance(params, TupleType) or isinstance(params, Fault),\
743 "argument must be tuple or Fault instance"
744
745 if isinstance(params, Fault):
746 methodresponse = 1
747 elif methodresponse and isinstance(params, TupleType):
748 assert len(params) == 1, "response tuple must be a singleton"
749
750 if not encoding:
751 encoding = "utf-8"
752
753 m = Marshaller(encoding)
754 data = m.dumps(params)
755
756 if encoding != "utf-8":
757 xmlheader = "<?xml version='1.0' encoding=%s?>\n" % repr(encoding)
758 else:
759 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
760
761 # standard XML-RPC wrappings
762 if methodname:
763 # a method call
764 if not isinstance(methodname, StringType):
765 methodname = methodname.encode(encoding)
766 data = (
767 xmlheader,
768 "<methodCall>\n"
769 "<methodName>", methodname, "</methodName>\n",
770 data,
771 "</methodCall>\n"
772 )
773 elif methodresponse:
774 # a method response, or a fault structure
775 data = (
776 xmlheader,
777 "<methodResponse>\n",
778 data,
779 "</methodResponse>\n"
780 )
781 else:
782 return data # return as is
783 return string.join(data, "")
784
785def loads(data):
786 """data -> unmarshalled data, method name
787
788 Convert an XML-RPC packet to unmarshalled data plus a method
789 name (None if not present).
790
791 If the XML-RPC packet represents a fault condition, this function
792 raises a Fault exception.
793 """
794 p, u = getparser()
795 p.feed(data)
796 p.close()
797 return u.close(), u.getmethodname()
798
799
800# --------------------------------------------------------------------
801# request dispatcher
802
803class _Method:
804 # some magic to bind an XML-RPC method to an RPC server.
805 # supports "nested" methods (e.g. examples.getStateName)
806 def __init__(self, send, name):
807 self.__send = send
808 self.__name = name
809 def __getattr__(self, name):
810 return _Method(self.__send, "%s.%s" % (self.__name, name))
811 def __call__(self, *args):
812 return self.__send(self.__name, args)
813
814
815class Transport:
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000816 """Handles an HTTP transaction to an XML-RPC server."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000817
818 # client identifier (may be overridden)
819 user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
820
821 def request(self, host, handler, request_body, verbose=0):
822 # issue XML-RPC request
823
824 h = self.make_connection(host)
825 if verbose:
826 h.set_debuglevel(1)
827
828 self.send_request(h, handler, request_body)
829 self.send_host(h, host)
830 self.send_user_agent(h)
831 self.send_content(h, request_body)
832
833 errcode, errmsg, headers = h.getreply()
834
835 if errcode != 200:
836 raise ProtocolError(
837 host + handler,
838 errcode, errmsg,
839 headers
840 )
841
842 self.verbose = verbose
843
844 return self.parse_response(h.getfile())
845
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000846 def getparser(self):
847 # get parser and unmarshaller
848 return getparser()
849
Fredrik Lundhb9056332001-07-11 17:42:21 +0000850 def make_connection(self, host):
851 # create a HTTP connection object from a host descriptor
852 import httplib
853 return httplib.HTTP(host)
854
855 def send_request(self, connection, handler, request_body):
856 connection.putrequest("POST", handler)
857
858 def send_host(self, connection, host):
859 connection.putheader("Host", host)
860
861 def send_user_agent(self, connection):
862 connection.putheader("User-Agent", self.user_agent)
863
864 def send_content(self, connection, request_body):
865 connection.putheader("Content-Type", "text/xml")
866 connection.putheader("Content-Length", str(len(request_body)))
867 connection.endheaders()
868 if request_body:
869 connection.send(request_body)
870
871 def parse_response(self, f):
872 # read response from input file, and parse it
873
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000874 p, u = self.getparser()
Fredrik Lundhb9056332001-07-11 17:42:21 +0000875
876 while 1:
877 response = f.read(1024)
878 if not response:
879 break
880 if self.verbose:
881 print "body:", repr(response)
882 p.feed(response)
883
884 f.close()
885 p.close()
886
887 return u.close()
888
889class SafeTransport(Transport):
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000890 """Handles an HTTPS transaction to an XML-RPC server."""
Fredrik Lundhb9056332001-07-11 17:42:21 +0000891
892 def make_connection(self, host):
893 # create a HTTPS connection object from a host descriptor
894 # host may be a string, or a (host, x509-dict) tuple
895 import httplib
896 if isinstance(host, TupleType):
897 host, x509 = host
898 else:
899 x509 = {}
900 try:
901 HTTPS = httplib.HTTPS
902 except AttributeError:
903 raise NotImplementedError,\
904 "your version of httplib doesn't support HTTPS"
905 else:
906 return apply(HTTPS, (host, None), x509)
907
908 def send_host(self, connection, host):
909 if isinstance(host, TupleType):
910 host, x509 = host
911 connection.putheader("Host", host)
912
913class ServerProxy:
914 """uri [,options] -> a logical connection to an XML-RPC server
915
916 uri is the connection point on the server, given as
917 scheme://host/target.
918
919 The standard implementation always supports the "http" scheme. If
920 SSL socket support is available (Python 2.0), it also supports
921 "https".
922
923 If the target part and the slash preceding it are both omitted,
924 "/RPC2" is assumed.
925
926 The following options can be given as keyword arguments:
927
928 transport: a transport factory
929 encoding: the request encoding (default is UTF-8)
930
931 All 8-bit strings passed to the server proxy are assumed to use
932 the given encoding.
933 """
934
935 def __init__(self, uri, transport=None, encoding=None, verbose=0):
936 # establish a "logical" server connection
937
938 # get the url
Fredrik Lundhc4c062f2001-09-10 19:45:02 +0000939 import urllib
Fredrik Lundhb9056332001-07-11 17:42:21 +0000940 type, uri = urllib.splittype(uri)
941 if type not in ("http", "https"):
942 raise IOError, "unsupported XML-RPC protocol"
943 self.__host, self.__handler = urllib.splithost(uri)
944 if not self.__handler:
945 self.__handler = "/RPC2"
946
947 if transport is None:
948 if type == "https":
949 transport = SafeTransport()
950 else:
951 transport = Transport()
952 self.__transport = transport
953
954 self.__encoding = encoding
955 self.__verbose = verbose
956
957 def __request(self, methodname, params):
958 # call a method on the remote server
959
960 request = dumps(params, methodname, encoding=self.__encoding)
961
962 response = self.__transport.request(
963 self.__host,
964 self.__handler,
965 request,
966 verbose=self.__verbose
967 )
968
969 if len(response) == 1:
970 response = response[0]
971
972 return response
973
974 def __repr__(self):
975 return (
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000976 "<ServerProxy for %s%s>" %
Fredrik Lundhb9056332001-07-11 17:42:21 +0000977 (self.__host, self.__handler)
978 )
979
980 __str__ = __repr__
981
982 def __getattr__(self, name):
983 # magic method dispatcher
984 return _Method(self.__request, name)
985
986 # note: to call a remote object with an non-standard name, use
987 # result getattr(server, "strange-python-name")(args)
988
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000989# compatibility
Fredrik Lundhb9056332001-07-11 17:42:21 +0000990Server = ServerProxy
991
992# --------------------------------------------------------------------
993# test code
994
995if __name__ == "__main__":
996
997 # simple test program (from the XML-RPC specification)
998
Fredrik Lundh78eedce2001-08-23 20:04:33 +0000999 # server = ServerProxy("http://localhost:8000") # local server
1000 server = ServerProxy("http://betty.userland.com")
Fredrik Lundhb9056332001-07-11 17:42:21 +00001001
1002 print server
1003
1004 try:
1005 print server.examples.getStateName(41)
1006 except Error, v:
1007 print "ERROR", v