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