2 # XML-RPC CLIENT LIBRARY
5 # an XML-RPC client interface for Python.
7 # the marshalling and response parser code can also be used to
8 # implement XML-RPC servers.
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.
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)
30 # 2001-06-10 fl Folded in _xmlrpclib accelerator support
32 # Copyright (c) 1999-2001 by Secret Labs AB.
33 # Copyright (c) 1999-2001 by Fredrik Lundh.
36 # http://www.pythonware.com
38 # --------------------------------------------------------------------
39 # The XML-RPC client interface is
41 # Copyright (c) 1999-2001 by Secret Labs AB
42 # Copyright (c) 1999-2001 by Fredrik Lundh
44 # By obtaining, using, and/or copying this software and/or its
45 # associated documentation, you agree that you have read, understood,
46 # and will comply with the following terms and conditions:
48 # Permission to use, copy, modify, and distribute this software and
49 # its associated documentation for any purpose and without fee is
50 # hereby granted, provided that the above copyright notice appears in
51 # all copies, and that both that copyright notice and this permission
52 # notice appear in supporting documentation, and that the name of
53 # Secret Labs AB or the author not be used in advertising or publicity
54 # pertaining to distribution of the software without specific, written
57 # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
58 # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
59 # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
60 # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
61 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
62 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
63 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
65 # --------------------------------------------------------------------
68 # things to look into before 1.0 final:
70 # TODO: unicode marshalling -DONE
71 # TODO: ascii-compatible encoding support -DONE
72 # TODO: safe transport -DONE (but mostly untested)
73 # TODO: sgmlop memory leak -DONE
74 # TODO: sgmlop xml parsing -DONE
75 # TODO: support unicode method names -DONE
76 # TODO: update selftest -DONE
77 # TODO: add docstrings -DONE
78 # TODO: clean up parser encoding (trust the parser) -DONE
79 # TODO: expat support -DONE
80 # TODO: _xmlrpclib accelerator support -DONE
81 # TODO: use smarter/faster escape from effdom
82 # TODO: support basic authentication (see robin's patch)
83 # TODO: fix host tuple handling in the server constructor
84 # TODO: let transport verify schemes
85 # TODO: update documentation
86 # TODO: authentication plugins
87 # TODO: memo problem (see HP's mail)
89 import re
, string
, time
, operator
92 from cgi
import escape
97 unicode = None # unicode support not available
99 def _decode(data
, encoding
, is8bit
=re
.compile("[\x80-\xff]").search
):
100 # decode non-ascii string (if possible)
101 if unicode and encoding
and is8bit(data
):
102 data
= unicode(data
, encoding
)
106 def _stringify(string
):
107 # convert to 7-bit ascii if possible
113 def _stringify(string
):
116 __version__
= "1.0b2"
118 # --------------------------------------------------------------------
122 # base class for client errors
125 class ProtocolError(Error
):
126 # indicates an HTTP protocol error
127 def __init__(self
, url
, errcode
, errmsg
, headers
):
129 self
.errcode
= errcode
131 self
.headers
= headers
134 "<ProtocolError for %s: %s %s>" %
135 (self
.url
, self
.errcode
, self
.errmsg
)
138 class ResponseError(Error
):
139 # indicates a broken response package
143 # indicates a XML-RPC fault package
144 def __init__(self
, faultCode
, faultString
, **extra
):
145 self
.faultCode
= faultCode
146 self
.faultString
= faultString
150 (self
.faultCode
, repr(self
.faultString
))
153 # --------------------------------------------------------------------
157 # use True or False to generate a "boolean" XML-RPC value
161 def __init__(self
, value
= 0):
162 self
.value
= operator
.truth(value
)
164 def encode(self
, out
):
165 out
.write("<value><boolean>%d</boolean></value>\n" % self
.value
)
167 def __cmp__(self
, other
):
168 if isinstance(other
, Boolean
):
170 return cmp(self
.value
, other
)
174 return "<Boolean True at %x>" % id(self
)
176 return "<Boolean False at %x>" % id(self
)
181 def __nonzero__(self
):
184 True, False = Boolean(1), Boolean(0)
186 def boolean(value
, truefalse
=(False, True)):
187 # convert any Python value to XML-RPC boolean
188 return truefalse
[operator
.truth(value
)]
192 # wrap your iso8601 string or time tuple or localtime integer value
193 # in this class to generate a "dateTime.iso8601" XML-RPC value
197 def __init__(self
, value
=0):
199 if not isinstance(t
, StringType
):
200 if not isinstance(t
, TupleType
):
203 value
= time
.localtime(value
)
204 value
= time
.strftime("%Y%m%dT%H:%M:%S", value
)
207 def __cmp__(self
, other
):
208 if isinstance(other
, DateTime
):
210 return cmp(self
.value
, other
)
213 return "<DateTime %s at %x>" % (self
.value
, id(self
))
215 def decode(self
, data
):
216 self
.value
= string
.strip(data
)
218 def encode(self
, out
):
219 out
.write("<value><dateTime.iso8601>")
220 out
.write(self
.value
)
221 out
.write("</dateTime.iso8601></value>\n")
229 # binary data wrapper
233 def __init__(self
, data
=None):
236 def __cmp__(self
, other
):
237 if isinstance(other
, Binary
):
239 return cmp(self
.data
, other
)
241 def decode(self
, data
):
243 self
.data
= base64
.decodestring(data
)
245 def encode(self
, out
):
246 import base64
, StringIO
247 out
.write("<value><base64>\n")
248 base64
.encode(StringIO
.StringIO(self
.data
), out
)
249 out
.write("</base64></value>\n")
256 WRAPPERS
= DateTime
, Binary
, Boolean
258 # --------------------------------------------------------------------
262 # optional xmlrpclib accelerator. for more information on this
263 # component, contact info@pythonware.com
265 FastParser
= _xmlrpclib
.Parser
266 FastUnmarshaller
= _xmlrpclib
.Unmarshaller
267 except (AttributeError, ImportError):
268 FastParser
= FastUnmarshaller
= None
271 # the SGMLOP parser is about 15x faster than Python's builtin
272 # XML parser. SGMLOP sources can be downloaded from:
274 # http://www.pythonware.com/products/xml/sgmlop.htm
279 if not hasattr(sgmlop
, "XMLParser"):
282 SgmlopParser
= None # sgmlop accelerator not available
285 def __init__(self
, target
):
288 self
.finish_starttag
= target
.start
289 self
.finish_endtag
= target
.end
290 self
.handle_data
= target
.data
291 self
.handle_xml
= target
.xml
294 self
.parser
= sgmlop
.XMLParser()
295 self
.parser
.register(self
)
296 self
.feed
= self
.parser
.feed
298 "amp": "&", "gt": ">", "lt": "<",
299 "apos": "'", "quot": '"'
306 self
.parser
= self
.feed
= None # nuke circular reference
308 def handle_proc(self
, tag
, attr
):
309 m
= re
.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr
)
311 self
.handle_xml(m
.group(1), 1)
313 def handle_entityref(self
, entity
):
316 self
.handle_data(self
.entity
[entity
])
318 self
.handle_data("&%s;" % entity
)
321 from xml
.parsers
import expat
326 # fast expat parser for Python 2.0. this is about 50%
327 # slower than sgmlop, on roundtrip testing
328 def __init__(self
, target
):
329 self
._parser
= parser
= expat
.ParserCreate(None, None)
330 self
._target
= target
331 parser
.StartElementHandler
= target
.start
332 parser
.EndElementHandler
= target
.end
333 parser
.CharacterDataHandler
= target
.data
335 if not parser
.returns_unicode
:
337 target
.xml(encoding
, None)
339 def feed(self
, data
):
340 self
._parser
.Parse(data
, 0)
343 self
._parser
.Parse("", 1) # end of data
344 del self
._target
, self
._parser
# get rid of circular references
346 class SlowParser(xmllib
.XMLParser
):
347 # slow but safe standard parser, based on the XML parser in
348 # Python's standard library. this is about 10 times slower
349 # than sgmlop, on roundtrip testing.
350 def __init__(self
, target
):
351 self
.handle_xml
= target
.xml
352 self
.unknown_starttag
= target
.start
353 self
.handle_data
= target
.data
354 self
.unknown_endtag
= target
.end
355 xmllib
.XMLParser
.__init
__(self
)
358 # --------------------------------------------------------------------
359 # XML-RPC marshalling and unmarshalling code
362 """Generate an XML-RPC params chunk from a Python data structure"""
364 # USAGE: create a marshaller instance for each set of parameters,
365 # and use "dumps" to convert your data (represented as a tuple) to
366 # a XML-RPC params chunk. to write a fault response, pass a Fault
367 # instance instead. you may prefer to use the "dumps" convenience
368 # function for this purpose (see below).
370 # by the way, if you don't understand what's going on in here,
371 # that's perfectly ok.
373 def __init__(self
, encoding
=None):
376 self
.encoding
= encoding
380 def dumps(self
, values
):
382 self
.write
= write
= self
.__out
.append
383 if isinstance(values
, Fault
):
386 self
.__dump
(vars(values
))
396 result
= string
.join(self
.__out
, "")
397 del self
.__out
, self
.write
# don't need this any more
400 def __dump(self
, value
):
402 f
= self
.dispatch
[type(value
)]
404 raise TypeError, "cannot marshal %s objects" % type(value
)
408 def dump_int(self
, value
):
409 self
.write("<value><int>%s</int></value>\n" % value
)
410 dispatch
[IntType
] = dump_int
412 def dump_double(self
, value
):
413 self
.write("<value><double>%s</double></value>\n" % value
)
414 dispatch
[FloatType
] = dump_double
416 def dump_string(self
, value
):
417 self
.write("<value><string>%s</string></value>\n" % escape(value
))
418 dispatch
[StringType
] = dump_string
421 def dump_unicode(self
, value
):
422 value
= value
.encode(self
.encoding
)
423 self
.write("<value><string>%s</string></value>\n" % escape(value
))
424 dispatch
[UnicodeType
] = dump_unicode
426 def container(self
, value
):
429 if self
.memo
.has_key(i
):
430 raise TypeError, "cannot marshal recursive data structures"
433 def dump_array(self
, value
):
434 self
.container(value
)
436 write("<value><array><data>\n")
439 write("</data></array></value>\n")
440 dispatch
[TupleType
] = dump_array
441 dispatch
[ListType
] = dump_array
443 def dump_struct(self
, value
):
444 self
.container(value
)
446 write("<value><struct>\n")
447 for k
, v
in value
.items():
449 if type(k
) is not StringType
:
450 raise TypeError, "dictionary key must be string"
451 write("<name>%s</name>\n" % escape(k
))
454 write("</struct></value>\n")
455 dispatch
[DictType
] = dump_struct
457 def dump_instance(self
, value
):
458 # check for special wrappers
459 if value
.__class
__ in WRAPPERS
:
462 # store instance attributes as a struct (really?)
463 self
.dump_struct(value
.__dict
__)
464 dispatch
[InstanceType
] = dump_instance
468 # unmarshal an XML-RPC response, based on incoming XML event
469 # messages (start, data, end). call close to get the resulting
472 # note that this reader is fairly tolerant, and gladly accepts
473 # bogus XML-RPC data without complaining (but not bogus XML).
475 # and again, if you don't understand what's going on in here,
476 # that's perfectly ok.
483 self
._methodname
= None
484 self
._encoding
= "utf-8"
485 self
.append
= self
._stack
.append
488 # return response tuple and target method
489 if self
._type
is None or self
._marks
:
490 raise ResponseError()
491 if self
._type
== "fault":
492 raise apply(Fault
, (), self
._stack
[0])
493 return tuple(self
._stack
)
495 def getmethodname(self
):
496 return self
._methodname
501 def xml(self
, encoding
, standalone
):
502 self
._encoding
= encoding
503 # FIXME: assert standalone == 1 ???
505 def start(self
, tag
, attrs
):
506 # prepare to handle this element
507 if tag
== "array" or tag
== "struct":
508 self
._marks
.append(len(self
._stack
))
510 self
._value
= (tag
== "value")
512 def data(self
, text
):
513 self
._data
.append(text
)
516 # call the appropriate end tag handler
518 f
= self
.dispatch
[tag
]
522 return f(self
, self
._data
)
525 # accelerator support
527 def end_dispatch(self
, tag
, data
):
530 f
= self
.dispatch
[tag
]
541 def end_boolean(self
, data
, join
=string
.join
):
542 data
= join(data
, "")
548 raise TypeError, "bad boolean value"
550 dispatch
["boolean"] = end_boolean
552 def end_int(self
, data
, join
=string
.join
):
553 self
.append(int(join(data
, "")))
555 dispatch
["i4"] = end_int
556 dispatch
["int"] = end_int
558 def end_double(self
, data
, join
=string
.join
):
559 self
.append(float(join(data
, "")))
561 dispatch
["double"] = end_double
563 def end_string(self
, data
, join
=string
.join
):
564 data
= join(data
, "")
566 data
= _decode(data
, self
._encoding
)
567 self
.append(_stringify(data
))
569 dispatch
["string"] = end_string
570 dispatch
["name"] = end_string
# struct keys are always strings
572 def end_array(self
, data
):
573 mark
= self
._marks
[-1]
575 # map arrays to Python lists
576 self
._stack
[mark
:] = [self
._stack
[mark
:]]
578 dispatch
["array"] = end_array
580 def end_struct(self
, data
):
581 mark
= self
._marks
[-1]
583 # map structs to Python dictionaries
585 items
= self
._stack
[mark
:]
586 for i
in range(0, len(items
), 2):
587 dict[_stringify(items
[i
])] = items
[i
+1]
588 self
._stack
[mark
:] = [dict]
590 dispatch
["struct"] = end_struct
592 def end_base64(self
, data
, join
=string
.join
):
594 value
.decode(join(data
, ""))
597 dispatch
["base64"] = end_base64
599 def end_dateTime(self
, data
, join
=string
.join
):
601 value
.decode(join(data
, ""))
603 dispatch
["dateTime.iso8601"] = end_dateTime
605 def end_value(self
, data
):
606 # if we stumble upon an value element with no internal
607 # elements, treat it as a string element
609 self
.end_string(data
)
610 dispatch
["value"] = end_value
612 def end_params(self
, data
):
613 self
._type
= "params"
614 dispatch
["params"] = end_params
616 def end_fault(self
, data
):
618 dispatch
["fault"] = end_fault
620 def end_methodName(self
, data
, join
=string
.join
):
621 data
= join(data
, "")
623 data
= _decode(data
, self
._encoding
)
624 self
._methodname
= data
625 self
._type
= "methodName" # no params
626 dispatch
["methodName"] = end_methodName
629 # --------------------------------------------------------------------
630 # convenience functions
633 """getparser() -> parser, unmarshaller
635 Create an instance of the fastest available parser, and attach
636 it to an unmarshalling object. Return both objects.
638 if FastParser
and FastUnmarshaller
:
639 target
= FastUnmarshaller(True, False, binary
, datetime
)
640 parser
= FastParser(target
)
642 target
= Unmarshaller()
644 parser
= FastParser(target
)
646 parser
= SgmlopParser(target
)
648 parser
= ExpatParser(target
)
650 parser
= SlowParser(target
)
651 return parser
, target
653 def dumps(params
, methodname
=None, methodresponse
=None, encoding
=None):
654 """data [,options] -> marshalled data
656 Convert an argument tuple or a Fault instance to an XML-RPC
657 request (or response, if the methodresponse option is used).
659 In addition to the data object, the following options can be
660 given as keyword arguments:
662 methodname: the method name for a methodCall packet
664 methodresponse: true to create a methodResponse packet.
665 If this option is used with a tuple, the tuple must be
666 a singleton (i.e. it can contain only one element).
668 encoding: the packet encoding (default is UTF-8)
670 All 8-bit strings in the data structure are assumed to use the
671 packet encoding. Unicode strings are automatically converted,
675 assert isinstance(params
, TupleType
) or isinstance(params
, Fault
),\
676 "argument must be tuple or Fault instance"
678 if isinstance(params
, Fault
):
680 elif methodresponse
and isinstance(params
, TupleType
):
681 assert len(params
) == 1, "response tuple must be a singleton"
686 m
= Marshaller(encoding
)
687 data
= m
.dumps(params
)
689 if encoding
!= "utf-8":
690 xmlheader
= "<?xml version='1.0' encoding=%s?>\n" % repr(encoding
)
692 xmlheader
= "<?xml version='1.0'?>\n" # utf-8 is default
694 # standard XML-RPC wrappings
697 if not isinstance(methodname
, StringType
):
698 methodname
= methodname
.encode(encoding
)
702 "<methodName>", methodname
, "</methodName>\n",
707 # a method response, or a fault structure
710 "<methodResponse>\n",
712 "</methodResponse>\n"
715 return data
# return as is
716 return string
.join(data
, "")
719 """data -> unmarshalled data, method name
721 Convert an XML-RPC packet to unmarshalled data plus a method
722 name (None if not present).
724 If the XML-RPC packet represents a fault condition, this function
725 raises a Fault exception.
730 return u
.close(), u
.getmethodname()
733 # --------------------------------------------------------------------
737 # some magic to bind an XML-RPC method to an RPC server.
738 # supports "nested" methods (e.g. examples.getStateName)
739 def __init__(self
, send
, name
):
742 def __getattr__(self
, name
):
743 return _Method(self
.__send
, "%s.%s" % (self
.__name
, name
))
744 def __call__(self
, *args
):
745 return self
.__send
(self
.__name
, args
)
749 """Handles an HTTP transaction to an XML-RPC server"""
751 # client identifier (may be overridden)
752 user_agent
= "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
754 def request(self
, host
, handler
, request_body
, verbose
=0):
755 # issue XML-RPC request
757 h
= self
.make_connection(host
)
761 self
.send_request(h
, handler
, request_body
)
762 self
.send_host(h
, host
)
763 self
.send_user_agent(h
)
764 self
.send_content(h
, request_body
)
766 errcode
, errmsg
, headers
= h
.getreply()
775 self
.verbose
= verbose
777 return self
.parse_response(h
.getfile())
779 def make_connection(self
, host
):
780 # create a HTTP connection object from a host descriptor
782 return httplib
.HTTP(host
)
784 def send_request(self
, connection
, handler
, request_body
):
785 connection
.putrequest("POST", handler
)
787 def send_host(self
, connection
, host
):
788 connection
.putheader("Host", host
)
790 def send_user_agent(self
, connection
):
791 connection
.putheader("User-Agent", self
.user_agent
)
793 def send_content(self
, connection
, request_body
):
794 connection
.putheader("Content-Type", "text/xml")
795 connection
.putheader("Content-Length", str(len(request_body
)))
796 connection
.endheaders()
798 connection
.send(request_body
)
800 def parse_response(self
, f
):
801 # read response from input file, and parse it
806 response
= f
.read(1024)
810 print "body:", repr(response
)
818 class SafeTransport(Transport
):
819 """Handles an HTTPS transaction to an XML-RPC server"""
821 def make_connection(self
, host
):
822 # create a HTTPS connection object from a host descriptor
823 # host may be a string, or a (host, x509-dict) tuple
825 if isinstance(host
, TupleType
):
830 HTTPS
= httplib
.HTTPS
831 except AttributeError:
832 raise NotImplementedError,\
833 "your version of httplib doesn't support HTTPS"
835 return apply(HTTPS
, (host
, None), x509
)
837 def send_host(self
, connection
, host
):
838 if isinstance(host
, TupleType
):
840 connection
.putheader("Host", host
)
843 """uri [,options] -> a logical connection to an XML-RPC server
845 uri is the connection point on the server, given as
846 scheme://host/target.
848 The standard implementation always supports the "http" scheme. If
849 SSL socket support is available (Python 2.0), it also supports
852 If the target part and the slash preceding it are both omitted,
855 The following options can be given as keyword arguments:
857 transport: a transport factory
858 encoding: the request encoding (default is UTF-8)
860 All 8-bit strings passed to the server proxy are assumed to use
864 def __init__(self
, uri
, transport
=None, encoding
=None, verbose
=0):
865 # establish a "logical" server connection
868 type, uri
= urllib
.splittype(uri
)
869 if type not in ("http", "https"):
870 raise IOError, "unsupported XML-RPC protocol"
871 self
.__host
, self
.__handler
= urllib
.splithost(uri
)
872 if not self
.__handler
:
873 self
.__handler
= "/RPC2"
875 if transport
is None:
877 transport
= SafeTransport()
879 transport
= Transport()
880 self
.__transport
= transport
882 self
.__encoding
= encoding
883 self
.__verbose
= verbose
885 def __request(self
, methodname
, params
):
886 # call a method on the remote server
888 request
= dumps(params
, methodname
, encoding
=self
.__encoding
)
890 response
= self
.__transport
.request(
894 verbose
=self
.__verbose
897 if len(response
) == 1:
898 response
= response
[0]
904 "<Server proxy for %s%s>" %
905 (self
.__host
, self
.__handler
)
910 def __getattr__(self
, name
):
911 # magic method dispatcher
912 return _Method(self
.__request
, name
)
914 # note: to call a remote object with an non-standard name, use
915 # result getattr(server, "strange-python-name")(args)
919 # --------------------------------------------------------------------
922 if __name__
== "__main__":
924 # simple test program (from the XML-RPC specification)
926 # server = Server("http://localhost:8000") # local server
927 server
= Server("http://betty.userland.com")
932 print server
.examples
.getStateName(41)