1 #
2 # XMLRPC CLIENT LIBRARY
3 # $Id: xmlrpclib.py 12937 2004-11-24 20:15:11Z ruff $
4 #
5 # an XMLRPC client interface for Python.
6 #
7 # the marshalling and response parser code can also be used to
8 # implement XMLRPC servers.
9 #
10 # Notes:
11 # this version uses the sgmlop XML parser, if installed. this is
12 # typically 10-15x faster than using Python's standard XML parser.
13 #
14 # you can get the sgmlop distribution from:
15 #
16 # http://www.pythonware.com/madscientist
17 #
18 # also note that this version is designed to work with Python 1.5.1
19 # or newer. it doesn't use any 1.5.2-specific features.
20 #
21 # History:
22 # 1999-01-14 fl Created
23 # 1999-01-15 fl Changed dateTime to use localtime
24 # 1999-01-16 fl Added Binary/base64 element, default to RPC2 service
25 # 1999-01-19 fl Fixed array data element (from Skip Montanaro)
26 # 1999-01-21 fl Fixed dateTime constructor, etc.
27 # 1999-02-02 fl Added fault handling, handle empty sequences, etc.
28 # 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro)
29 # 1999-06-20 fl Speed improvements, pluggable XML parsers and HTTP transports
30 #
31 # Copyright (c) 1999 by Secret Labs AB.
32 # Copyright (c) 1999 by Fredrik Lundh.
33 #
34 # fredrik@pythonware.com
35 # http://www.pythonware.com
36 #
37 # --------------------------------------------------------------------
38 # The XMLRPC client interface is
39 #
40 # Copyright (c) 1999 by Secret Labs AB
41 # Copyright (c) 1999 by Fredrik Lundh
42 #
43 # By obtaining, using, and/or copying this software and/or its
44 # associated documentation, you agree that you have read, understood,
45 # and will comply with the following terms and conditions:
46 #
47 # Permission to use, copy, modify, and distribute this software and
48 # its associated documentation for any purpose and without fee is
49 # hereby granted, provided that the above copyright notice appears in
50 # all copies, and that both that copyright notice and this permission
51 # notice appear in supporting documentation, and that the name of
52 # Secret Labs AB or the author not be used in advertising or publicity
53 # pertaining to distribution of the software without specific, written
54 # prior permission.
55 #
56 # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
57 # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
58 # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
59 # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
60 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
61 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
62 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
63 # OF THIS SOFTWARE.
64 # --------------------------------------------------------------------
65
66 import string, time
67 import urllib, xmllib
68 from types import *
69 from cgi import escape
70
71 try:
72 import sgmlop
73 except ImportError:
74 sgmlop = None # accelerator not available
75
76 __version__ = "0.9.8"
77
78
79 # --------------------------------------------------------------------
80 # Exceptions
81
82 class Error:
83 # base class for client errors
84 pass
85
86 class ProtocolError(Error):
87 # indicates an HTTP protocol error
88 def __init__(self, url, errcode, errmsg, headers):
89 self.url = url
90 self.errcode = errcode
91 self.errmsg = errmsg
92 self.headers = headers
93 def __repr__(self):
94 return (
95 "<ProtocolError for %s: %s %s>" %
96 (self.url, self.errcode, self.errmsg)
97 )
98
99 class ResponseError(Error):
100 # indicates a broken response package
101 pass
102
103 class Fault(Error):
104 # indicates a XMLRPC fault package
105 def __init__(self, faultCode, faultString, **extra):
106 self.faultCode = faultCode
107 self.faultString = faultString
108 def __repr__(self):
109 return (
110 "<Fault %s: %s>" %
111 (self.faultCode, repr(self.faultString))
112 )
113
114
115 # --------------------------------------------------------------------
116 # Special values
117
118 # boolean wrapper
119 # (you must use True or False to generate a "boolean" XMLRPC value)
120
121 class Boolean:
122
123 def __init__(self, value = 0):
124 self.value = (value != 0)
125
126 def encode(self, out):
127 out.write("<value><boolean>%d</boolean></value>\n" % self.value)
128
129 def __repr__(self):
130 if self.value:
131 return "<Boolean True at %x>" % id(self)
132 else:
133 return "<Boolean False at %x>" % id(self)
134
135 def __int__(self):
136 return self.value
137
138 def __nonzero__(self):
139 return self.value
140
141 True, False = Boolean(1), Boolean(0)
142
143 #
144 # dateTime wrapper
145 # (wrap your iso8601 string or time tuple or localtime time value in
146 # this class to generate a "dateTime.iso8601" XMLRPC value)
147
148 class DateTime:
149
150 def __init__(self, value = 0):
151 t = type(value)
152 if t is not StringType:
153 if t is not TupleType:
154 value = time.localtime(value)
155 value = time.strftime("%Y%m%dT%H:%M:%S", value)
156 self.value = value
157
158 def __repr__(self):
159 return "<DateTime %s at %x>" % (self.value, id(self))
160
161 def decode(self, data):
162 self.value = string.strip(data)
163
164 def encode(self, out):
165 out.write("<value><dateTime.iso8601>")
166 out.write(self.value)
167 out.write("</dateTime.iso8601></value>\n")
168
169 #
170 # binary data wrapper (NOTE: this is an extension to Userland's
171 # XMLRPC protocol! only for use with compatible servers!)
172
173 class Binary:
174
175 def __init__(self, data=None):
176 self.data = data
177
178 def decode(self, data):
179 import base64
180 self.data = base64.decodestring(data)
181
182 def encode(self, out):
183 import base64, StringIO
184 out.write("<value><base64>\n")
185 base64.encode(StringIO.StringIO(self.data), out)
186 out.write("</base64></value>\n")
187
188 WRAPPERS = DateTime, Binary, Boolean
189
190
191 # --------------------------------------------------------------------
192 # XML parsers
193
194 if sgmlop:
195
196 class FastParser:
197 # sgmlop based XML parser. this is typically 15x faster
198 # than SlowParser...
199
200 def __init__(self, target):
201
202 # setup callbacks
203 self.finish_starttag = target.start
204 self.finish_endtag = target.end
205 self.handle_data = target.data
206
207 # activate parser
208 self.parser = sgmlop.XMLParser()
209 self.parser.register(self)
210 self.feed = self.parser.feed
211 self.entity = {
212 "amp": "&", "gt": ">", "lt": "<",
213 "apos": "'", "quot": '"'
214 }
215
216 def close(self):
217 try:
218 self.parser.close()
219 finally:
220 self.parser = None # nuke circular reference
221
222 def handle_entityref(self, entity):
223 # <string> entity
224 try:
225 self.handle_data(self.entity[entity])
226 except KeyError:
227 self.handle_data("&%s;" % entity)
228
229 else:
230
231 FastParser = None
232
233 class SlowParser(xmllib.XMLParser):
234 # slow but safe standard parser, based on the XML parser in
235 # Python's standard library
236
237 def __init__(self, target):
238 self.unknown_starttag = target.start
239 self.handle_data = target.data
240 self.unknown_endtag = target.end
241 xmllib.XMLParser.__init__(self)
242
243
244 # --------------------------------------------------------------------
245 # XMLRPC marshalling and unmarshalling code
246
247 class Marshaller:
248 """Generate an XMLRPC params chunk from a Python data structure"""
249
250 # USAGE: create a marshaller instance for each set of parameters,
251 # and use "dumps" to convert your data (represented as a tuple) to
252 # a XMLRPC params chunk. to write a fault response, pass a Fault
253 # instance instead. you may prefer to use the "dumps" convenience
254 # function for this purpose (see below).
255
256 # by the way, if you don't understand what's going on in here,
257 # that's perfectly ok.
258
259 def __init__(self):
260 self.memo = {}
261 self.data = None
262
263 dispatch = {}
264
265 def dumps(self, values):
266 self.__out = []
267 self.write = write = self.__out.append
268 if isinstance(values, Fault):
269 # fault instance
270 write("<fault>\n")
271 self.__dump(vars(values))
272 write("</fault>\n")
273 else:
274 # parameter block
275 write("<params>\n")
276 for v in values:
277 write("<param>\n")
278 self.__dump(v)
279 write("</param>\n")
280 write("</params>\n")
281 result = string.join(self.__out, "")
282 del self.__out, self.write # don't need this any more
283 return result
284
285 def __dump(self, value):
286 try:
287 f = self.dispatch[type(value)]
288 except KeyError:
289 raise TypeError, "cannot marshal %s objects" % type(value)
290 else:
291 f(self, value)
292
293 def dump_int(self, value):
294 self.write("<value><int>%s</int></value>\n" % value)
295 dispatch[IntType] = dump_int
296
297 def dump_double(self, value):
298 self.write("<value><double>%s</double></value>\n" % value)
299 dispatch[FloatType] = dump_double
300
301 def dump_string(self, value):
302 self.write("<value><string>%s</string></value>\n" % escape(value))
303 dispatch[StringType] = dump_string
304
305 def container(self, value):
306 if value:
307 i = id(value)
308 if self.memo.has_key(i):
309 raise TypeError, "cannot marshal recursive data structures"
310 self.memo[i] = None
311
312 def dump_array(self, value):
313 self.container(value)
314 write = self.write
315 write("<value><array><data>\n")
316 for v in value:
317 self.__dump(v)
318 write("</data></array></value>\n")
319 dispatch[TupleType] = dump_array
320 dispatch[ListType] = dump_array
321
322 def dump_struct(self, value):
323 self.container(value)
324 write = self.write
325 write("<value><struct>\n")
326 for k, v in value.items():
327 write("<member>\n")
328 if type(k) is not StringType:
329 raise TypeError, "dictionary key must be string"
330 write("<name>%s</name>\n" % escape(k))
331 self.__dump(v)
332 write("</member>\n")
333 write("</struct></value>\n")
334 dispatch[DictType] = dump_struct
335
336 def dump_instance(self, value):
337 # check for special wrappers
338 if value.__class__ in WRAPPERS:
339 value.encode(self)
340 else:
341 # store instance attributes as a struct (really?)
342 self.dump_struct(value.__dict__)
343 dispatch[InstanceType] = dump_instance
344
345
346 class Unmarshaller:
347
348 # unmarshal an XMLRPC response, based on incoming XML event
349 # messages (start, data, end). call close to get the resulting
350 # data structure
351
352 # note that this reader is fairly tolerant, and gladly accepts
353 # bogus XMLRPC data without complaining (but not bogus XML).
354
355 # and again, if you don't understand what's going on in here,
356 # that's perfectly ok.
357
358 def __init__(self):
359 self._type = None
360 self._stack = []
361 self._marks = []
362 self._data = []
363 self._methodname = None
364 self.append = self._stack.append
365
366 def close(self):
367 # return response code and the actual response
368 if self._type is None or self._marks:
369 raise ResponseError()
370 if self._type == "fault":
371 raise apply(Fault, (), self._stack[0])
372 return tuple(self._stack)
373
374 def getmethodname(self):
375 return self._methodname
376
377 #
378 # event handlers
379
380 def start(self, tag, attrs):
381 # prepare to handle this element
382 if tag in ("array", "struct"):
383 self._marks.append(len(self._stack))
384 self._data = []
385 self._value = (tag == "value")
386
387 def data(self, text):
388 self._data.append(text)
389
390 dispatch = {}
391
392 def end(self, tag):
393 # call the appropriate end tag handler
394 try:
395 f = self.dispatch[tag]
396 except KeyError:
397 pass # unknown tag ?
398 else:
399 return f(self)
400
401 #
402 # element decoders
403
404 def end_boolean(self, join=string.join):
405 value = join(self._data, "")
406 if value == "0":
407 self.append(False)
408 elif value == "1":
409 self.append(True)
410 else:
411 raise TypeError, "bad boolean value"
412 self._value = 0
413 dispatch["boolean"] = end_boolean
414
415 def end_int(self, join=string.join):
416 self.append(int(join(self._data, "")))
417 self._value = 0
418 dispatch["i4"] = end_int
419 dispatch["int"] = end_int
420
421 def end_double(self, join=string.join):
422 self.append(float(join(self._data, "")))
423 self._value = 0
424 dispatch["double"] = end_double
425
426 def end_string(self, join=string.join):
427 self.append(join(self._data, ""))
428 self._value = 0
429 dispatch["string"] = end_string
430 dispatch["name"] = end_string # struct keys are always strings
431
432 def end_array(self):
433 mark = self._marks[-1]
434 del self._marks[-1]
435 # map arrays to Python lists
436 self._stack[mark:] = [self._stack[mark:]]
437 self._value = 0
438 dispatch["array"] = end_array
439
440 def end_struct(self):
441 mark = self._marks[-1]
442 del self._marks[-1]
443 # map structs to Python dictionaries
444 dict = {}
445 items = self._stack[mark:]
446 for i in range(0, len(items), 2):
447 dict[items[i]] = items[i+1]
448 self._stack[mark:] = [dict]
449 self._value = 0
450 dispatch["struct"] = end_struct
451
452 def end_base64(self, join=string.join):
453 value = Binary()
454 value.decode(join(self._data, ""))
455 self.append(value)
456 self._value = 0
457 dispatch["base64"] = end_base64
458
459 def end_dateTime(self, join=string.join):
460 value = DateTime()
461 value.decode(join(self._data, ""))
462 self.append(value)
463 dispatch["dateTime.iso8601"] = end_dateTime
464
465 def end_value(self):
466 # if we stumble upon an value element with no internal
467 # elements, treat it as a string element
468 if self._value:
469 self.end_string()
470 dispatch["value"] = end_value
471
472 def end_params(self):
473 self._type = "params"
474 dispatch["params"] = end_params
475
476 def end_fault(self):
477 self._type = "fault"
478 dispatch["fault"] = end_fault
479
480 def end_methodName(self, join=string.join):
481 self._methodname = join(self._data, "")
482 dispatch["methodName"] = end_methodName
483
484
485 # --------------------------------------------------------------------
486 # convenience functions
487
488 def getparser():
489 # get the fastest available parser, and attach it to an
490 # unmarshalling object. return both objects.
491 target = Unmarshaller()
492 if FastParser:
493 return FastParser(target), target
494 return SlowParser(target), target
495
496 def dumps(params, methodname=None, methodresponse=None):
497 # convert a tuple or a fault object to an XMLRPC packet
498
499 assert type(params) == TupleType or isinstance(params, Fault),\
500 "argument must be tuple or Fault instance"
501
502 m = Marshaller()
503 data = m.dumps(params)
504
505 # standard XMLRPC wrappings
506 if methodname:
507 # a method call
508 data = (
509 "<?xml version='1.0'?>\n"
510 "<methodCall>\n"
511 "<methodName>%s</methodName>\n"
512 "%s\n"
513 "</methodCall>\n" % (methodname, data)
514 )
515 elif methodresponse or isinstance(params, Fault):
516 # a method response
517 data = (
518 "<?xml version='1.0'?>\n"
519 "<methodResponse>\n"
520 "%s\n"
521 "</methodResponse>\n" % data
522 )
523 return data
524
525 def loads(data):
526 # convert an XMLRPC packet to data plus a method name (None
527 # if not present). if the XMLRPC packet represents a fault
528 # condition, this function raises a Fault exception.
529 p, u = getparser()
530 p.feed(data)
531 p.close()
532 return u.close(), u.getmethodname()
533
534
535 # --------------------------------------------------------------------
536 # request dispatcher
537
538 class _Method:
539 # some magic to bind an XMLRPC method to an RPC server.
540 # supports "nested" methods (e.g. examples.getStateName)
541 def __init__(self, send, name):
542 self.__send = send
543 self.__name = name
544 def __getattr__(self, name):
545 return _Method(self.__send, "%s.%s" % (self.__name, name))
546 def __call__(self, *args):
547 return self.__send(self.__name, args)
548
549
550 class Transport:
551 """Handles an HTTP transaction to an XMLRPC server"""
552
553 # client identifier (may be overridden)
554 user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
555
556 def request(self, host, handler, request_body):
557 # issue XMLRPC request
558
559 import httplib
560 h = httplib.HTTP(host)
561 h.putrequest("POST", handler)
562
563 # required by HTTP/1.1
564 h.putheader("Host", host)
565
566 # required by XMLRPC
567 h.putheader("User-Agent", self.user_agent)
568 h.putheader("Content-Type", "text/xml")
569 h.putheader("Content-Length", str(len(request_body)))
570
571 h.endheaders()
572
573 if request_body:
574 h.send(request_body)
575
576 errcode, errmsg, headers = h.getreply()
577
578 if errcode != 200:
579 raise ProtocolError(
580 host + handler,
581 errcode, errmsg,
582 headers
583 )
584
585 return self.parse_response(h.getfile())
586
587 def parse_response(self, f):
588 # read response from input file, and parse it
589
590 p, u = getparser()
591
592 while 1:
593 response = f.read(1024)
594 if not response:
595 break
596 p.feed(response)
597
598 f.close()
599 p.close()
600
601 return u.close()
602
603
604 class Server:
605 """Represents a connection to an XMLRPC server"""
606
607 def __init__(self, uri, transport=None):
608 # establish a "logical" server connection
609
610 # get the url
611 type, uri = urllib.splittype(uri)
612 if type != "http":
613 raise IOError, "unsupported XMLRPC protocol"
614 self.__host, self.__handler = urllib.splithost(uri)
615 if not self.__handler:
616 self.__handler = "/RPC2"
617
618 if transport is None:
619 transport = Transport()
620 self.__transport = transport
621
622 def __request(self, methodname, params):
623 # call a method on the remote server
624
625 request = dumps(params, methodname)
626
627 response = self.__transport.request(
628 self.__host,
629 self.__handler,
630 request
631 )
632
633 if len(response) == 1:
634 return response[0]
635
636 return response
637
638 def __repr__(self):
639 return (
640 "<Server proxy for %s%s>" %
641 (self.__host, self.__handler)
642 )
643
644 __str__ = __repr__
645
646 def __getattr__(self, name):
647 # magic method dispatcher
648 return _Method(self.__request, name)
649
650
651 if __name__ == "__main__":
652
653 # simple test program (from the XMLRPC specification)
654 # server = Server("http://localhost:8000") # local server
655
656 server = Server("http://betty.userland.com")
657
658 print server
659
660 try:
661 print server.examples.getStateName(41)
662 except Error, v:
663 print "ERROR", v
syntax highlighted by Code2HTML, v. 0.9.1