[Zope-dev] RE: COOLEDIT: changing type

Michel Pelletier michel@digicool.com
Thu, 2 Dec 1999 11:42:41 -0500


This message is in MIME format. Since your mail reader does not understand
this format, some or all of this message may not be legible.

------ =_NextPart_000_01BF3CE4.42F95E60
Content-Type: text/plain

Paul, I'm cc:ing the zope-dev list on this message:

> -----Original Message-----
> From: Paul Sheer [mailto:psheer@obsidian.co.za]
> Sent: Thursday, December 02, 1999 10:16 AM
> To: michel@digicool.com
> Cc: cooledit@lava.obsidian.co.za
> Subject: Re: COOLEDIT: changing type
> 
> 
> On  1 Dec, Michel Pelletier did thusly spake:
> > Hi,
> > 
> > Just dicovered cooledit, and I allready have a sketchy interface for
> > opening, creating, and saving documents in Zope 
> (http://www.zope.org/)
> > via python's xmlrpclib.
> 
> > 
> > The one thing I haven't figured out how to do is to programatically
> > change the type of the document in python, I have a need to 
> force the
> > editor into my own document type so that I can change the 
> menus, like
> > 'File' so that open and save and whatnot do not work from the
> > filesystem, but rather via xml-rpc.  Right now I am overriding the
> > 'Util' menu and it's kinda klunky, as the editor has no 
> idea that it is
> > editing an external resource.
> > 
> 
> I have a few new python functions you might be interested in

Cool, for the benefit of the zope-dev list, cooledit is an X based text
editor that is scripted with python.  I have put together a simple set
of methods that allow cooledit to open, create, and save DTML Methods
and Documents in Zope using Fredrik Lundh's xmlrpclib module (I had to
patch the module to support HTTP Basic auth, the patched version is
included).

The global.py module that defines the zope methods is attached also.
I'm getting the impression, from reading through the coolpython.c file,
that much of cooledit's methods can be wrapped, possibly allowing more
high level control to python methods.  One example where this would help
with my little project, for example, is saving and opening remote
documents.  Currently, I can't 'name' the editor window because it
expects the name to be a file path.  If I can rewire some of the core,
then I might be able to do this.

> 
> check the changelog file and man page.
> 
> distribution being released in a couple of hours
> 
> send me your zope stuff soon

Thanks,

-Michel


------ =_NextPart_000_01BF3CE4.42F95E60
Content-Type: application/octet-stream;
	name="global.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="global.py"

# Zope extension for cooledit=0A=
#=0A=
=0A=
import sys=0A=
=0A=
sys.path.insert(0, "/home/michel/dev/Zope2/lib/python")=0A=
=0A=
import xmlrpclib=0A=
=0A=
zope_variables =3D {}=0A=
=0A=
zope_variables['resource_url'] =3D 'http://'=0A=
zope_variables['name'] =3D ''=0A=
zope_variables['passw'] =3D ''=0A=
=0A=
def prompt_open_resource():=0A=
    inputs, cbs =3D generic_dialog('Open Resource',=0A=
	(zope_variables['resource_url'],=0A=
         zope_variables['name'],=0A=
         zope_variables['passw']),=0A=
	('Resource URL', 'Username', 'Password'),=0A=
	('xmlrpc-resource-url', 'xmlrpc-username', 'xmlrpc-password'),=0A=
	('URL to the Resource to open', 'A valid userid', 'A valid =
password'),=0A=
	# there are no checkboxes...=0A=
	(), (), () )=0A=
    return inputs=0A=
=0A=
def prompt_new_resource():=0A=
    return generic_dialog('New Resource',=0A=
	(zope_variables['resource_url'],=0A=
         '',=0A=
         zope_variables['name'],=0A=
         zope_variables['passw']),=0A=
	('Resource Parent URL', 'Resource ID', 'Username', 'Password'),=0A=
	('xmlrpc-resource-url', 'xmlrpc-resource-id', 'xmlrpc-username',=0A=
         'xmlrpc-password'),=0A=
	('Parent URL for resource', 'Resource ID', 'A valid userid',=0A=
         'A valid password'),=0A=
	# there are no checkboxes...=0A=
	(0,), ('Create Method',), ('xmlrpc-create-method',) )=0A=
=0A=
def zope_open_resource():=0A=
    # url =3D input_dialog('Enter URL to open', 'URL', '')=0A=
    # user =3D input_dialog('Enter Userid', 'userid', '')=0A=
    # passw =3D input_dialog('Enter Password', 'password', '')=0A=
    =0A=
    url, user, passw =3D prompt_open_resource()=0A=
    zope_variables['resource_url'] =3D url=0A=
    zope_variables['user'] =3D user=0A=
    zope_variables['passw'] =3D passw=0A=
    conn =3D xmlrpclib.Server(url, user, passw)=0A=
    insert(conn.document_src())=0A=
    redraw_page()=0A=
    =0A=
def zope_save_resource():=0A=
    url, user, passw =3D prompt_open_resource()=0A=
    zope_variables['resource_url'] =3D url=0A=
    zope_variables['user'] =3D user=0A=
    zope_variables['passw'] =3D passw=0A=
    conn =3D xmlrpclib.Server(url, user, passw)=0A=
    src =3D get_text(0, buffer_size())=0A=
    conn.manage_edit(src, '')=0A=
    =0A=
def zope_new_resource():=0A=
    info, ismethod =3D prompt_new_resource()=0A=
    url, id, user, passw =3D info=0A=
    zope_variables['resource_url'] =3D url=0A=
    zope_variables['user'] =3D user=0A=
    zope_variables['passw'] =3D passw=0A=
    conn =3D xmlrpclib.Server(url, user, passw)=0A=
    try:=0A=
        if ismethod[0]:=0A=
	    conn.manage_addDTMLMethod(id)=0A=
	else:=0A=
	    conn.manage_addDTMLDocument(id)=0A=
    except xmlrpclib.ProtocolError:=0A=
        # because Zope responds with a redirect which xml-rpc turns=0A=
        # into an error=0A=
	pass=0A=
    =0A=
=0A=
# key bindings=0A=
=0A=
key("x", ControlMask, "open_zope_resource()")=0A=
=0A=
# this method is called when the file type changes=0A=
=0A=
def type_change(s):=0A=
    menu("Util")=0A=
    menu("Util", "Open Resource URL...", "zope_open_resource()")=0A=
    menu("Util", "New Resource...", "zope_new_resource()")=0A=
    menu("Util", "Save Resource...", "zope_save_resource()")=0A=

------ =_NextPart_000_01BF3CE4.42F95E60
Content-Type: application/octet-stream;
	name="xmlrpclib.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="xmlrpclib.py"

#=0A=
# XML-RPC CLIENT LIBRARY=0A=
# $Id: xmlrpclib.py,v 1.1.1.1 1999/06/11 15:04:49 emk Exp $=0A=
#=0A=
# an XML-RPC client interface for Python=0A=
#=0A=
# the marshalling and response parser code can also be used to=0A=
# implement XML-RPC servers=0A=
#=0A=
# History:=0A=
# 1999-01-14 fl  Created=0A=
# 1999-01-15 fl  Changed dateTime to use localtime=0A=
# 1999-01-16 fl  Added Binary/base64 element, default to RPC2 =
service=0A=
# 1999-01-19 fl  Fixed array data element (from Skip Montanaro)=0A=
# 1999-01-21 fl  Fixed dateTime constructor, etc.=0A=
# 1999-02-02 fl  Added fault handling, handle empty sequences, etc.=0A=
#=0A=
# written by Fredrik Lundh, January 1999.=0A=
#=0A=
# Copyright (c) 1999 by Secret Labs AB.=0A=
# Copyright (c) 1999 by Fredrik Lundh.=0A=
#=0A=
# fredrik@pythonware.com=0A=
# http://www.pythonware.com=0A=
#=0A=
# =
--------------------------------------------------------------------=0A=
# The XML-RPC client interface is=0A=
# =0A=
# Copyright (c) 1999 by Secret Labs AB=0A=
# Copyright (c) 1999 by Fredrik Lundh=0A=
# =0A=
# By obtaining, using, and/or copying this software and/or its=0A=
# associated documentation, you agree that you have read, =
understood,=0A=
# and will comply with the following terms and conditions:=0A=
# =0A=
# Permission to use, copy, modify, and distribute this software and =
its=0A=
# associated documentation for any purpose and without fee is hereby=0A=
# granted, provided that the above copyright notice appears in all=0A=
# copies, and that both that copyright notice and this permission =
notice=0A=
# appear in supporting documentation, and that the name of Secret =
Labs=0A=
# AB or the author not be used in advertising or publicity pertaining =
to=0A=
# distribution of the software without specific, written prior=0A=
# permission.=0A=
# =0A=
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD =
TO=0A=
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY =
AND=0A=
# FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE =
FOR=0A=
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES=0A=
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN =
AN=0A=
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING =
OUT=0A=
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.=0A=
# =
--------------------------------------------------------------------=0A=
=0A=
import operator, string, time=0A=
import httplib, urllib=0A=
import xmllib=0A=
from types import *=0A=
from cgi import escape=0A=
from base64 import encodestring=0A=
=0A=
__version__ =3D "0.9.5"=0A=
=0A=
USER_AGENT =3D "xmlrpclib.py/%s (by www.pythonware.com)" % =
__version__=0A=
=0A=
=0A=
# =
--------------------------------------------------------------------=0A=
# Exceptions=0A=
=0A=
class Error:=0A=
    # base class for client errors=0A=
    pass=0A=
=0A=
class ProtocolError(Error):=0A=
    # indicates an HTTP protocol error=0A=
    def __init__(self, url, errcode, errmsg, headers):=0A=
	self.url =3D url=0A=
	self.errcode =3D errcode=0A=
	self.errmsg =3D errmsg=0A=
	self.headers =3D headers=0A=
    def __repr__(self):=0A=
	return (=0A=
	    "<ProtocolError for %s: %s %s>" %=0A=
	    (self.url, self.errcode, self.errmsg)=0A=
	    )=0A=
=0A=
class ResponseError(Error):=0A=
    # indicates a broken response chunk=0A=
    pass=0A=
=0A=
class Fault(Error):=0A=
    # indicates a XML-RPC fault package=0A=
    def __init__(self, faultCode, faultString, **extra):=0A=
	self.faultCode =3D faultCode=0A=
	self.faultString =3D faultString=0A=
    def __repr__(self):=0A=
	return (=0A=
	    "<Fault %s: %s>" %=0A=
	    (self.faultCode, repr(self.faultString))=0A=
	    )=0A=
=0A=
# =
--------------------------------------------------------------------=0A=
# Special values=0A=
=0A=
# boolean wrapper=0A=
# (you must use True or False to generate a "boolean" XML-RPC value)=0A=
=0A=
class Boolean:=0A=
=0A=
    def __init__(self, value =3D 0):=0A=
	self.value =3D (value !=3D 0)=0A=
=0A=
    def encode(self, out):=0A=
	out.write("<value><boolean>%d</boolean></value>\n" % self.value)=0A=
=0A=
    def __repr__(self):=0A=
	if self.value:=0A=
	    return "<Boolean True at %x>" % id(self)=0A=
	else:=0A=
	    return "<Boolean False at %x>" % id(self)=0A=
=0A=
    def __int__(self):=0A=
	return self.value=0A=
=0A=
    def __nonzero__(self):=0A=
	return self.value=0A=
=0A=
True, False =3D Boolean(1), Boolean(0)=0A=
=0A=
#=0A=
# dateTime wrapper=0A=
# (wrap your iso8601 string or localtime tuple or time value in this=0A=
# class to generate a "dateTime.iso8601" XML-RPC value)=0A=
=0A=
class DateTime:=0A=
=0A=
    def __init__(self, value):=0A=
	t =3D type(value)=0A=
	if t is not StringType:=0A=
	    if t is not TupleType:=0A=
		value =3D time.localtime(value)=0A=
	    value =3D time.strftime("%Y%m%dT%H:%M:%S", value)=0A=
	self.value =3D value=0A=
=0A=
    def __repr__(self):=0A=
	return "<DateTime %s at %x>" % (self.value, id(self))=0A=
=0A=
    def decode(self, data):=0A=
	self.value =3D string.strip(data)=0A=
=0A=
    def encode(self, out):=0A=
	out.write("<value><dateTime.iso8601>")=0A=
	out.write(self.value)=0A=
	out.write("</dateTime.iso8601></value>\n")=0A=
=0A=
#=0A=
# binary data wrapper (NOTE: this is an extension to Userland's=0A=
# XML-RPC protocol! only for use with compatible servers!)=0A=
=0A=
class Binary:=0A=
=0A=
    def __init__(self, data=3DNone):=0A=
	self.data =3D data=0A=
=0A=
    def decode(self, data):=0A=
	import base64=0A=
	self.data =3D base64.decodestring(data)=0A=
=0A=
    def encode(self, out):=0A=
	import base64, StringIO=0A=
	out.write("<value><base64>\n")=0A=
	base64.encode(StringIO.StringIO(self.data), out)=0A=
	out.write("</base64></value>\n")=0A=
=0A=
WRAPPERS =3D DateTime, Binary, Boolean=0A=
=0A=
# =
--------------------------------------------------------------------=0A=
# XML-RPC response parser=0A=
=0A=
class ResponseParser(xmllib.XMLParser):=0A=
    """Parse an XML-RPC response into a Python data structure"""=0A=
=0A=
    # USAGE: create a parser instance, and call "feed" to add data =
to=0A=
    # it (in chunks or as a single string).  call "close" to flush =
the=0A=
    # internal buffers and return the resulting data structure.=0A=
=0A=
    # note that this reader is fairly tolerant, and gladly accepts=0A=
    # bogus XML-RPC data without complaining (but not bogus XML).=0A=
=0A=
    # this could of course be simplified by using an XML tree =
builder=0A=
    # (DOM, coreXML, or whatever), but this version works with any=0A=
    # standard installation of 1.5 or later (except 1.5.2b1, but=0A=
    # we're working on that)=0A=
=0A=
    # by the way, if you don't understand what's going on in here,=0A=
    # that's perfectly ok.=0A=
=0A=
    def __init__(self):=0A=
	self.__type =3D None=0A=
	self.__stack =3D []=0A=
        self.__marks =3D []=0A=
	self.__data =3D []=0A=
	self.__methodname =3D None=0A=
	xmllib.XMLParser.__init__(self)=0A=
=0A=
    def close(self):=0A=
	xmllib.XMLParser.close(self)=0A=
	# return response code and the actual response=0A=
	if self.__type is None or self.__marks:=0A=
	    raise ResponseError()=0A=
	if self.__type =3D=3D "fault":=0A=
	    raise apply(Fault, (), self.__stack[0])=0A=
	return tuple(self.__stack)=0A=
=0A=
    def getmethodname(self):=0A=
	return self.__methodname=0A=
=0A=
    #=0A=
    # container types (containers can be nested, so we use a =
separate=0A=
    # mark stack to keep track of the beginning of each container).=0A=
=0A=
    def start_array(self, attrs):=0A=
	self.__marks.append(len(self.__stack))=0A=
=0A=
    start_struct =3D start_array=0A=
=0A=
    def unknown_starttag(self, tag, attrs):=0A=
	self.__data =3D []=0A=
	self.__value =3D (tag =3D=3D "value")=0A=
=0A=
    def handle_data(self, text):=0A=
	self.__data.append(text)=0A=
=0A=
    def unknown_endtag(self, tag, join=3Dstring.join):=0A=
	# the standard dispatcher cannot handle tags with uncommon=0A=
	# characters in them, so we have to do this ourselves.=0A=
	if tag =3D=3D "dateTime.iso8601":=0A=
	    value =3D DateTime()=0A=
	    value.decode(join(self.__data, ""))=0A=
	    self.__stack.append(value)=0A=
=0A=
    #=0A=
    # add values to the stack on end tags=0A=
=0A=
    def end_boolean(self, join=3Dstring.join):=0A=
	value =3D join(self.__data, "")=0A=
	if value =3D=3D "0":=0A=
	    self.__stack.append(False)=0A=
	elif value =3D=3D "1":=0A=
	    self.__stack.append(True)=0A=
	else:=0A=
	    raise TypeError, "bad boolean value"=0A=
=0A=
    def end_int(self, join=3Dstring.join):=0A=
	self.__stack.append(int(join(self.__data, "")))=0A=
=0A=
    def end_double(self, join=3Dstring.join):=0A=
	self.__stack.append(float(join(self.__data, "")))=0A=
=0A=
    def end_string(self, join=3Dstring.join):=0A=
	self.__stack.append(join(self.__data, ""))=0A=
=0A=
    # aliases=0A=
    end_i4 =3D end_int=0A=
    end_name =3D end_string # struct keys are always strings=0A=
=0A=
    def end_array(self):=0A=
        mark =3D self.__marks[-1]=0A=
	del self.__marks[-1]=0A=
	# map arrays to Python lists=0A=
        self.__stack[mark:] =3D [self.__stack[mark:]]=0A=
=0A=
    def end_struct(self):=0A=
        mark =3D self.__marks[-1]=0A=
	del self.__marks[-1]=0A=
	# map structs to Python dictionaries=0A=
        dict =3D {}=0A=
        items =3D self.__stack[mark:]=0A=
        for i in range(0, len(items), 2):=0A=
            dict[items[i]] =3D items[i+1]=0A=
        self.__stack[mark:] =3D [dict]=0A=
=0A=
    def end_base64(self, join=3Dstring.join):=0A=
	value =3D Binary()=0A=
	value.decode(join(self.__data, ""))=0A=
	self.__stack.append(value)=0A=
=0A=
    def end_value(self):=0A=
	# if we stumble upon an value element with no=0A=
	# no internal elements, treat it as a string=0A=
	# element=0A=
	if self.__value:=0A=
	    self.end_string()=0A=
=0A=
    def end_params(self):=0A=
	self.__type =3D "params"=0A=
=0A=
    def end_fault(self):=0A=
	self.__type =3D "fault"=0A=
=0A=
    def end_methodName(self, join=3Dstring.join):=0A=
	self.__methodname =3D join(self.__data, "")=0A=
=0A=
=0A=
# =
--------------------------------------------------------------------=0A=
# XML-RPC marshaller=0A=
=0A=
class Marshaller:=0A=
    """Generate an XML-RPC params chunk from a Python data =
structure"""=0A=
=0A=
    # USAGE: create a marshaller instance for each set of =
parameters,=0A=
    # and use "dumps" to convert your data (represented as a tuple) =
to=0A=
    # a XML-RPC params chunk.  to write a fault response, pass a =
Fault=0A=
    # instance instead.=0A=
=0A=
    # again, this could of course be simplified by using an XML =
writer=0A=
    # (coreXML or whatever), but this version works with any =
standard=0A=
    # installation of 1.5 or later (except 1.5.2b1, but we're =
working=0A=
    # on that)=0A=
=0A=
    # and again, if you don't understand what's going on in here,=0A=
    # that's perfectly ok.=0A=
=0A=
    def __init__(self):=0A=
	self.memo =3D {}=0A=
	self.data =3D None=0A=
=0A=
    dispatch =3D {}=0A=
=0A=
    def dumps(self, values):=0A=
	if isinstance(values, Fault):=0A=
	    # fault instance=0A=
	    self.__out =3D ["<fault>\n"]=0A=
	    self.__dump(vars(values))=0A=
	    self.write("</fault>\n")=0A=
	else:=0A=
	    # parameter block=0A=
	    self.__out =3D ["<params>\n"]=0A=
	    for v in values:=0A=
		self.write("<param>\n")=0A=
		self.__dump(v)=0A=
		self.write("</param>\n")=0A=
	    self.write("</params>\n")=0A=
	return string.join(self.__out, "")=0A=
=0A=
    def write(self, string):=0A=
	self.__out.append(string)=0A=
=0A=
    def __dump(self, value):=0A=
	try:=0A=
	    f =3D self.dispatch[type(value)]=0A=
	except KeyError:=0A=
	    raise TypeError, "cannot marshal %s objects" % type(value)=0A=
	else:=0A=
	    f(self, value)=0A=
=0A=
    def dump_int(self, value):=0A=
	self.write("<value><int>%s</int></value>\n" % value)=0A=
    dispatch[IntType] =3D dump_int=0A=
=0A=
    def dump_double(self, value):=0A=
	self.write("<value><double>%s</double></value>\n" % value)=0A=
    dispatch[FloatType] =3D dump_double=0A=
=0A=
    def dump_string(self, value):=0A=
	self.write("<value><string>%s</string></value>\n" % escape(value))=0A=
    dispatch[StringType] =3D dump_string=0A=
=0A=
    def container(self, value):=0A=
	if value:=0A=
	    i =3D id(value)=0A=
	    if self.memo.has_key(i):=0A=
		raise TypeError, "cannot marshal recursive data structures"=0A=
	    self.memo[i] =3D None=0A=
=0A=
    def dump_array(self, value):=0A=
	self.container(value)=0A=
	self.write("<value><array><data>\n")=0A=
	for v in value:=0A=
	    self.__dump(v)=0A=
	self.write("</data></array></value>\n")=0A=
    dispatch[TupleType] =3D dump_array=0A=
    dispatch[ListType] =3D dump_array=0A=
=0A=
    def dump_struct(self, value):=0A=
	self.container(value)=0A=
	write =3D self.write=0A=
	write("<value><struct>\n")=0A=
	for k, v in value.items():=0A=
	    write("<member>\n")=0A=
	    if type(k) is not StringType:=0A=
		raise TypeError, "dictionary key must be string"=0A=
	    write("<name>%s</name>\n" % escape(k))=0A=
	    self.__dump(v)=0A=
	    write("</member>\n")=0A=
	write("</struct></value>\n")=0A=
    dispatch[DictType] =3D dump_struct=0A=
=0A=
    def dump_instance(self, value):=0A=
	# check for special wrappers=0A=
	write =3D self.write=0A=
	if value.__class__ in WRAPPERS:=0A=
	    value.encode(self)=0A=
	else:=0A=
	    # store instance attributes as a struct (?)=0A=
	    self.dump_struct(value.__dict__)=0A=
    dispatch[InstanceType] =3D dump_instance=0A=
=0A=
=0A=
# =
--------------------------------------------------------------------=0A=
# convenience functions=0A=
=0A=
def dumps(params, methodname=3DNone, methodresponse=3DNone):=0A=
=0A=
    assert type(params) =3D=3D TupleType or isinstance(params, =
Fault),\=0A=
	   "argument must be tuple or Fault instance"=0A=
=0A=
    m =3D Marshaller()=0A=
    data =3D m.dumps(params)=0A=
=0A=
    # standard XML-RPC wrappings=0A=
    if methodname:=0A=
	# a method call=0A=
	data =3D (=0A=
	    "<?xml version=3D'1.0'?>\n"=0A=
	    "<methodCall>\n"=0A=
	    "<methodName>%s</methodName>\n"=0A=
	    "%s\n"=0A=
	    "</methodCall>\n" % (methodname, data)=0A=
	    )=0A=
    elif methodresponse or isinstance(params, Fault):=0A=
	# a method response=0A=
	data =3D (=0A=
	    "<?xml version=3D'1.0'?>\n"=0A=
	    "<methodResponse>\n"=0A=
	    "%s\n"=0A=
	    "</methodResponse>\n" % data=0A=
	    )=0A=
    return data=0A=
=0A=
def loads(data):=0A=
    # returns data plus methodname (None if not present)=0A=
    p =3D ResponseParser()=0A=
    p.feed(data)=0A=
    return p.close(), p.getmethodname()=0A=
=0A=
# =
--------------------------------------------------------------------=0A=
# request dispatcher=0A=
=0A=
class Method:=0A=
    # some magic to bind an XML-RPC method to an RPC server.=0A=
    # supports "nested" methods (e.g. examples.getStateName)=0A=
    def __init__(self, send, name):=0A=
	self.__send =3D send=0A=
	self.__name =3D name=0A=
    def __getattr__(self, name):=0A=
	return Method(self.__send, self.__name + "." + name)=0A=
    def __call__(self, *args):=0A=
	return self.__send(self.__name, args)=0A=
=0A=
=0A=
class Server:=0A=
    """Represents a connection XML-RPC server"""=0A=
=0A=
    def __init__(self, uri, username=3DNone, password=3DNone):=0A=
	# establish a "logical" server connection=0A=
=0A=
	type, uri =3D urllib.splittype(uri)=0A=
	if type !=3D "http":=0A=
	    raise IOError, "unsupported XML-RPC protocol"=0A=
	self.__host, self.__handler =3D urllib.splithost(uri)=0A=
	if not self.__handler:=0A=
	    self.__handler =3D "/RPC2"=0A=
=0A=
        self.username=3Dusername=0A=
        self.password=3Dpassword=0A=
      =0A=
    def __request(self, methodname, params):=0A=
	# call a method on the remote server=0A=
=0A=
	request =3D dumps(params, methodname)=0A=
=0A=
	# send the request=0A=
	h =3D httplib.HTTP(self.__host)=0A=
	h.putrequest("POST", self.__handler)=0A=
	h.putheader("User-Agent", USER_AGENT)=0A=
	h.putheader("Host", self.__host)=0A=
	h.putheader("Content-Type", "text/xml")=0A=
	h.putheader("Content-Length", str(len(request)))=0A=
=0A=
        if (self.username and self.password):=0A=
            h.putheader('Authorization', ("Basic %s" %=0A=
                string.replace(encodestring('%s:%s' % =
(self.username,self.password)),=0A=
				     '\012','')))=0A=
	h.endheaders()=0A=
=0A=
	if request:=0A=
	    h.send(request)=0A=
=0A=
	errcode, errmsg, headers =3D h.getreply()=0A=
=0A=
	if errcode !=3D 200:=0A=
	    raise ProtocolError(=0A=
		self.__host + self.__handler,=0A=
		errcode, errmsg,=0A=
		headers=0A=
		)=0A=
=0A=
	# parse the response=0A=
	fp =3D h.getfile()=0A=
=0A=
	p =3D ResponseParser()=0A=
=0A=
	while 1:=0A=
	    response =3D fp.read(1024)=0A=
	    if not response:=0A=
		break=0A=
	    p.feed(response)=0A=
=0A=
	response =3D p.close()=0A=
=0A=
	if len(response) =3D=3D 1:=0A=
	    return response[0]=0A=
=0A=
	return response=0A=
=0A=
    def __repr__(self):=0A=
	return (=0A=
	    "<Server proxy for %s%s>" %=0A=
	    (self.__host, self.__handler)=0A=
	    )=0A=
=0A=
    __str__ =3D __repr__=0A=
=0A=
    def __getattr__(self, name):=0A=
	# method dispatcher=0A=
	return Method(self.__request, name)=0A=
=0A=
=0A=
if __name__ =3D=3D "__main__":=0A=
=0A=
    # simple test program (from the specification)=0A=
    # server =3D Server("http://localhost:8000") # local server=0A=
=0A=
    server =3D Server("http://nirvana.userland.com")=0A=
=0A=
    print server=0A=
=0A=
    try:=0A=
	print server.examples.getStateName(41)=0A=
    except Error, v:=0A=
	print "ERROR", v=0A=

------ =_NextPart_000_01BF3CE4.42F95E60--