Commit d854f87a authored by Amos Latteier's avatar Amos Latteier

Updated xmlrpclib to version 0.9.8 from Pythonware.

parent 9a44f268
# #
# XML-RPC CLIENT LIBRARY # XML-RPC CLIENT LIBRARY
# $Id: xmlrpclib.py,v 1.1.1.1 1999/06/11 15:04:49 emk Exp $ # $Id$
# #
# an XML-RPC client interface for Python # an XML-RPC client interface for Python.
# #
# the marshalling and response parser code can also be used to # the marshalling and response parser code can also be used to
# implement XML-RPC servers # implement XML-RPC servers.
#
# Notes:
# this version uses the sgmlop XML parser, if installed. this is
# typically 10-15x faster than using Python's standard XML parser.
#
# you can get the sgmlop distribution from:
#
# http://www.pythonware.com/madscientist
#
# also note that this version is designed to work with Python 1.5.1
# or newer. it doesn't use any 1.5.2-specific features.
# #
# History: # History:
# 1999-01-14 fl Created # 1999-01-14 fl Created
...@@ -14,8 +25,8 @@ ...@@ -14,8 +25,8 @@
# 1999-01-19 fl Fixed array data element (from Skip Montanaro) # 1999-01-19 fl Fixed array data element (from Skip Montanaro)
# 1999-01-21 fl Fixed dateTime constructor, etc. # 1999-01-21 fl Fixed dateTime constructor, etc.
# 1999-02-02 fl Added fault handling, handle empty sequences, etc. # 1999-02-02 fl Added fault handling, handle empty sequences, etc.
# # 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro)
# written by Fredrik Lundh, January 1999. # 1999-06-20 fl Speed improvements, pluggable XML parsers and HTTP transports
# #
# Copyright (c) 1999 by Secret Labs AB. # Copyright (c) 1999 by Secret Labs AB.
# Copyright (c) 1999 by Fredrik Lundh. # Copyright (c) 1999 by Fredrik Lundh.
...@@ -33,33 +44,36 @@ ...@@ -33,33 +44,36 @@
# associated documentation, you agree that you have read, understood, # associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions: # and will comply with the following terms and conditions:
# #
# Permission to use, copy, modify, and distribute this software and its # Permission to use, copy, modify, and distribute this software and
# associated documentation for any purpose and without fee is hereby # its associated documentation for any purpose and without fee is
# granted, provided that the above copyright notice appears in all # hereby granted, provided that the above copyright notice appears in
# copies, and that both that copyright notice and this permission notice # all copies, and that both that copyright notice and this permission
# appear in supporting documentation, and that the name of Secret Labs # notice appear in supporting documentation, and that the name of
# AB or the author not be used in advertising or publicity pertaining to # Secret Labs AB or the author not be used in advertising or publicity
# distribution of the software without specific, written prior # pertaining to distribution of the software without specific, written
# permission. # prior permission.
# #
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# -------------------------------------------------------------------- # --------------------------------------------------------------------
import operator, string, time import string, time
import httplib, urllib import urllib, xmllib
import xmllib
from types import * from types import *
from cgi import escape from cgi import escape
__version__ = "0.9.5" try:
import sgmlop
except ImportError:
sgmlop = None # accelerator not available
USER_AGENT = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ __version__ = "0.9.8"
# -------------------------------------------------------------------- # --------------------------------------------------------------------
...@@ -83,7 +97,7 @@ class ProtocolError(Error): ...@@ -83,7 +97,7 @@ class ProtocolError(Error):
) )
class ResponseError(Error): class ResponseError(Error):
# indicates a broken response chunk # indicates a broken response package
pass pass
class Fault(Error): class Fault(Error):
...@@ -97,6 +111,7 @@ class Fault(Error): ...@@ -97,6 +111,7 @@ class Fault(Error):
(self.faultCode, repr(self.faultString)) (self.faultCode, repr(self.faultString))
) )
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Special values # Special values
...@@ -127,12 +142,12 @@ True, False = Boolean(1), Boolean(0) ...@@ -127,12 +142,12 @@ True, False = Boolean(1), Boolean(0)
# #
# dateTime wrapper # dateTime wrapper
# (wrap your iso8601 string or localtime tuple or time value in this # (wrap your iso8601 string or time tuple or localtime time value in
# class to generate a "dateTime.iso8601" XML-RPC value) # this class to generate a "dateTime.iso8601" XML-RPC value)
class DateTime: class DateTime:
def __init__(self, value): def __init__(self, value = 0):
t = type(value) t = type(value)
if t is not StringType: if t is not StringType:
if t is not TupleType: if t is not TupleType:
...@@ -172,136 +187,62 @@ class Binary: ...@@ -172,136 +187,62 @@ class Binary:
WRAPPERS = DateTime, Binary, Boolean WRAPPERS = DateTime, Binary, Boolean
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# XML-RPC response parser # XML parsers
class ResponseParser(xmllib.XMLParser): if sgmlop:
"""Parse an XML-RPC response into a Python data structure"""
# USAGE: create a parser instance, and call "feed" to add data to class FastParser:
# it (in chunks or as a single string). call "close" to flush the # sgmlop based XML parser. this is typically 15x faster
# internal buffers and return the resulting data structure. # than SlowParser...
# note that this reader is fairly tolerant, and gladly accepts def __init__(self, target):
# bogus XML-RPC data without complaining (but not bogus XML).
# this could of course be simplified by using an XML tree builder # setup callbacks
# (DOM, coreXML, or whatever), but this version works with any self.finish_starttag = target.start
# standard installation of 1.5 or later (except 1.5.2b1, but self.finish_endtag = target.end
# we're working on that) self.handle_data = target.data
# by the way, if you don't understand what's going on in here, # activate parser
# that's perfectly ok. self.parser = sgmlop.XMLParser()
self.parser.register(self)
def __init__(self): self.feed = self.parser.feed
self.__type = None self.entity = {
self.__stack = [] "amp": "&", "gt": ">", "lt": "<",
self.__marks = [] "apos": "'", "quot": '"'
self.__data = [] }
self.__methodname = None
xmllib.XMLParser.__init__(self)
def close(self): def close(self):
xmllib.XMLParser.close(self) try:
# return response code and the actual response self.parser.close()
if self.__type is None or self.__marks: finally:
raise ResponseError() self.parser = None # nuke circular reference
if self.__type == "fault":
raise apply(Fault, (), self.__stack[0])
return tuple(self.__stack)
def getmethodname(self):
return self.__methodname
#
# container types (containers can be nested, so we use a separate
# mark stack to keep track of the beginning of each container).
def start_array(self, attrs):
self.__marks.append(len(self.__stack))
start_struct = start_array
def unknown_starttag(self, tag, attrs):
self.__data = []
self.__value = (tag == "value")
def handle_data(self, text):
self.__data.append(text)
def unknown_endtag(self, tag, join=string.join):
# the standard dispatcher cannot handle tags with uncommon
# characters in them, so we have to do this ourselves.
if tag == "dateTime.iso8601":
value = DateTime()
value.decode(join(self.__data, ""))
self.__stack.append(value)
#
# add values to the stack on end tags
def end_boolean(self, join=string.join):
value = join(self.__data, "")
if value == "0":
self.__stack.append(False)
elif value == "1":
self.__stack.append(True)
else:
raise TypeError, "bad boolean value"
def end_int(self, join=string.join):
self.__stack.append(int(join(self.__data, "")))
def end_double(self, join=string.join):
self.__stack.append(float(join(self.__data, "")))
def end_string(self, join=string.join):
self.__stack.append(join(self.__data, ""))
# aliases
end_i4 = end_int
end_name = end_string # struct keys are always strings
def end_array(self):
mark = self.__marks[-1]
del self.__marks[-1]
# map arrays to Python lists
self.__stack[mark:] = [self.__stack[mark:]]
def end_struct(self):
mark = self.__marks[-1]
del self.__marks[-1]
# map structs to Python dictionaries
dict = {}
items = self.__stack[mark:]
for i in range(0, len(items), 2):
dict[items[i]] = items[i+1]
self.__stack[mark:] = [dict]
def end_base64(self, join=string.join): def handle_entityref(self, entity):
value = Binary() # <string> entity
value.decode(join(self.__data, "")) try:
self.__stack.append(value) self.handle_data(self.entity[entity])
except KeyError:
self.handle_data("&%s;" % entity)
def end_value(self): else:
# if we stumble upon an value element with no
# no internal elements, treat it as a string
# element
if self.__value:
self.end_string()
def end_params(self): FastParser = None
self.__type = "params"
def end_fault(self): class SlowParser(xmllib.XMLParser):
self.__type = "fault" # slow but safe standard parser, based on the XML parser in
# Python's standard library
def end_methodName(self, join=string.join): def __init__(self, target):
self.__methodname = join(self.__data, "") self.unknown_starttag = target.start
self.handle_data = target.data
self.unknown_endtag = target.end
xmllib.XMLParser.__init__(self)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# XML-RPC marshaller # XML-RPC marshalling and unmarshalling code
class Marshaller: class Marshaller:
"""Generate an XML-RPC params chunk from a Python data structure""" """Generate an XML-RPC params chunk from a Python data structure"""
...@@ -309,14 +250,10 @@ class Marshaller: ...@@ -309,14 +250,10 @@ class Marshaller:
# USAGE: create a marshaller instance for each set of parameters, # USAGE: create a marshaller instance for each set of parameters,
# and use "dumps" to convert your data (represented as a tuple) to # and use "dumps" to convert your data (represented as a tuple) to
# a XML-RPC params chunk. to write a fault response, pass a Fault # a XML-RPC params chunk. to write a fault response, pass a Fault
# instance instead. # instance instead. you may prefer to use the "dumps" convenience
# function for this purpose (see below).
# again, this could of course be simplified by using an XML writer
# (coreXML or whatever), but this version works with any standard
# installation of 1.5 or later (except 1.5.2b1, but we're working
# on that)
# and again, if you don't understand what's going on in here, # by the way, if you don't understand what's going on in here,
# that's perfectly ok. # that's perfectly ok.
def __init__(self): def __init__(self):
...@@ -326,23 +263,24 @@ class Marshaller: ...@@ -326,23 +263,24 @@ class Marshaller:
dispatch = {} dispatch = {}
def dumps(self, values): def dumps(self, values):
self.__out = []
self.write = write = self.__out.append
if isinstance(values, Fault): if isinstance(values, Fault):
# fault instance # fault instance
self.__out = ["<fault>\n"] write("<fault>\n")
self.__dump(vars(values)) self.__dump(vars(values))
self.write("</fault>\n") write("</fault>\n")
else: else:
# parameter block # parameter block
self.__out = ["<params>\n"] write("<params>\n")
for v in values: for v in values:
self.write("<param>\n") write("<param>\n")
self.__dump(v) self.__dump(v)
self.write("</param>\n") write("</param>\n")
self.write("</params>\n") write("</params>\n")
return string.join(self.__out, "") result = string.join(self.__out, "")
del self.__out, self.write # don't need this any more
def write(self, string): return result
self.__out.append(string)
def __dump(self, value): def __dump(self, value):
try: try:
...@@ -373,10 +311,11 @@ class Marshaller: ...@@ -373,10 +311,11 @@ class Marshaller:
def dump_array(self, value): def dump_array(self, value):
self.container(value) self.container(value)
self.write("<value><array><data>\n") write = self.write
write("<value><array><data>\n")
for v in value: for v in value:
self.__dump(v) self.__dump(v)
self.write("</data></array></value>\n") write("</data></array></value>\n")
dispatch[TupleType] = dump_array dispatch[TupleType] = dump_array
dispatch[ListType] = dump_array dispatch[ListType] = dump_array
...@@ -396,19 +335,166 @@ class Marshaller: ...@@ -396,19 +335,166 @@ class Marshaller:
def dump_instance(self, value): def dump_instance(self, value):
# check for special wrappers # check for special wrappers
write = self.write
if value.__class__ in WRAPPERS: if value.__class__ in WRAPPERS:
value.encode(self) value.encode(self)
else: else:
# store instance attributes as a struct (?) # store instance attributes as a struct (really?)
self.dump_struct(value.__dict__) self.dump_struct(value.__dict__)
dispatch[InstanceType] = dump_instance dispatch[InstanceType] = dump_instance
class Unmarshaller:
# unmarshal an XML-RPC response, based on incoming XML event
# messages (start, data, end). call close to get the resulting
# data structure
# note that this reader is fairly tolerant, and gladly accepts
# bogus XML-RPC data without complaining (but not bogus XML).
# and again, if you don't understand what's going on in here,
# that's perfectly ok.
def __init__(self):
self._type = None
self._stack = []
self._marks = []
self._data = []
self._methodname = None
self.append = self._stack.append
def close(self):
# return response code and the actual response
if self._type is None or self._marks:
raise ResponseError()
if self._type == "fault":
raise apply(Fault, (), self._stack[0])
return tuple(self._stack)
def getmethodname(self):
return self._methodname
#
# event handlers
def start(self, tag, attrs):
# prepare to handle this element
if tag in ("array", "struct"):
self._marks.append(len(self._stack))
self._data = []
self._value = (tag == "value")
def data(self, text):
self._data.append(text)
dispatch = {}
def end(self, tag):
# call the appropriate end tag handler
try:
f = self.dispatch[tag]
except KeyError:
pass # unknown tag ?
else:
return f(self)
#
# element decoders
def end_boolean(self, join=string.join):
value = join(self._data, "")
if value == "0":
self.append(False)
elif value == "1":
self.append(True)
else:
raise TypeError, "bad boolean value"
self._value = 0
dispatch["boolean"] = end_boolean
def end_int(self, join=string.join):
self.append(int(join(self._data, "")))
self._value = 0
dispatch["i4"] = end_int
dispatch["int"] = end_int
def end_double(self, join=string.join):
self.append(float(join(self._data, "")))
self._value = 0
dispatch["double"] = end_double
def end_string(self, join=string.join):
self.append(join(self._data, ""))
self._value = 0
dispatch["string"] = end_string
dispatch["name"] = end_string # struct keys are always strings
def end_array(self):
mark = self._marks[-1]
del self._marks[-1]
# map arrays to Python lists
self._stack[mark:] = [self._stack[mark:]]
self._value = 0
dispatch["array"] = end_array
def end_struct(self):
mark = self._marks[-1]
del self._marks[-1]
# map structs to Python dictionaries
dict = {}
items = self._stack[mark:]
for i in range(0, len(items), 2):
dict[items[i]] = items[i+1]
self._stack[mark:] = [dict]
self._value = 0
dispatch["struct"] = end_struct
def end_base64(self, join=string.join):
value = Binary()
value.decode(join(self._data, ""))
self.append(value)
self._value = 0
dispatch["base64"] = end_base64
def end_dateTime(self, join=string.join):
value = DateTime()
value.decode(join(self._data, ""))
self.append(value)
dispatch["dateTime.iso8601"] = end_dateTime
def end_value(self):
# if we stumble upon an value element with no internal
# elements, treat it as a string element
if self._value:
self.end_string()
dispatch["value"] = end_value
def end_params(self):
self._type = "params"
dispatch["params"] = end_params
def end_fault(self):
self._type = "fault"
dispatch["fault"] = end_fault
def end_methodName(self, join=string.join):
self._methodname = join(self._data, "")
dispatch["methodName"] = end_methodName
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# convenience functions # convenience functions
def getparser():
# get the fastest available parser, and attach it to an
# unmarshalling object. return both objects.
target = Unmarshaller()
if FastParser:
return FastParser(target), target
return SlowParser(target), target
def dumps(params, methodname=None, methodresponse=None): def dumps(params, methodname=None, methodresponse=None):
# convert a tuple or a fault object to an XML-RPC packet
assert type(params) == TupleType or isinstance(params, Fault),\ assert type(params) == TupleType or isinstance(params, Fault),\
"argument must be tuple or Fault instance" "argument must be tuple or Fault instance"
...@@ -437,77 +523,112 @@ def dumps(params, methodname=None, methodresponse=None): ...@@ -437,77 +523,112 @@ def dumps(params, methodname=None, methodresponse=None):
return data return data
def loads(data): def loads(data):
# returns data plus methodname (None if not present) # convert an XML-RPC packet to data plus a method name (None
p = ResponseParser() # if not present). if the XML-RPC packet represents a fault
# condition, this function raises a Fault exception.
p, u = getparser()
p.feed(data) p.feed(data)
return p.close(), p.getmethodname() p.close()
return u.close(), u.getmethodname()
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# request dispatcher # request dispatcher
class Method: class _Method:
# some magic to bind an XML-RPC method to an RPC server. # some magic to bind an XML-RPC method to an RPC server.
# supports "nested" methods (e.g. examples.getStateName) # supports "nested" methods (e.g. examples.getStateName)
def __init__(self, send, name): def __init__(self, send, name):
self.__send = send self.__send = send
self.__name = name self.__name = name
def __getattr__(self, name): def __getattr__(self, name):
return Method(self.__send, self.__name + "." + name) return _Method(self.__send, "%s.%s" % (self.__name, name))
def __call__(self, *args): def __call__(self, *args):
return self.__send(self.__name, args) return self.__send(self.__name, args)
class Server: class Transport:
"""Represents a connection XML-RPC server""" """Handles an HTTP transaction to an XML-RPC server"""
def __init__(self, uri): # client identifier (may be overridden)
# establish a "logical" server connection user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
type, uri = urllib.splittype(uri) def request(self, host, handler, request_body):
if type != "http": # issue XML-RPC request
raise IOError, "unsupported XML-RPC protocol"
self.__host, self.__handler = urllib.splithost(uri)
if not self.__handler:
self.__handler = "/RPC2"
def __request(self, methodname, params): import httplib
# call a method on the remote server h = httplib.HTTP(host)
h.putrequest("POST", handler)
request = dumps(params, methodname) # required by HTTP/1.1
h.putheader("Host", host)
# send the request # required by XML-RPC
h = httplib.HTTP(self.__host) h.putheader("User-Agent", self.user_agent)
h.putrequest("POST", self.__handler)
h.putheader("User-Agent", USER_AGENT)
h.putheader("Host", self.__host)
h.putheader("Content-Type", "text/xml") h.putheader("Content-Type", "text/xml")
h.putheader("Content-Length", str(len(request))) h.putheader("Content-Length", str(len(request_body)))
h.endheaders() h.endheaders()
if request: if request_body:
h.send(request) h.send(request_body)
errcode, errmsg, headers = h.getreply() errcode, errmsg, headers = h.getreply()
if errcode != 200: if errcode != 200:
raise ProtocolError( raise ProtocolError(
self.__host + self.__handler, host + handler,
errcode, errmsg, errcode, errmsg,
headers headers
) )
# parse the response return self.parse_response(h.getfile())
fp = h.getfile()
def parse_response(self, f):
# read response from input file, and parse it
p = ResponseParser() p, u = getparser()
while 1: while 1:
response = fp.read(1024) response = f.read(1024)
if not response: if not response:
break break
p.feed(response) p.feed(response)
response = p.close() f.close()
p.close()
return u.close()
class Server:
"""Represents a connection to an XML-RPC server"""
def __init__(self, uri, transport=None):
# establish a "logical" server connection
# get the url
type, uri = urllib.splittype(uri)
if type != "http":
raise IOError, "unsupported XML-RPC protocol"
self.__host, self.__handler = urllib.splithost(uri)
if not self.__handler:
self.__handler = "/RPC2"
if transport is None:
transport = Transport()
self.__transport = transport
def __request(self, methodname, params):
# call a method on the remote server
request = dumps(params, methodname)
response = self.__transport.request(
self.__host,
self.__handler,
request
)
if len(response) == 1: if len(response) == 1:
return response[0] return response[0]
...@@ -523,16 +644,16 @@ class Server: ...@@ -523,16 +644,16 @@ class Server:
__str__ = __repr__ __str__ = __repr__
def __getattr__(self, name): def __getattr__(self, name):
# method dispatcher # magic method dispatcher
return Method(self.__request, name) return _Method(self.__request, name)
if __name__ == "__main__": if __name__ == "__main__":
# simple test program (from the specification) # simple test program (from the XML-RPC specification)
# server = Server("http://localhost:8000") # local server # server = Server("http://localhost:8000") # local server
server = Server("http://nirvana.userland.com") server = Server("http://betty.userland.com")
print server print server
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment