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