Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

The Python web services developer: Python SOAP libraries, Part 3

SOAP.py and ZSI can swap roles and prove Python's versatility

Mike Olson, Principal Consultant, Fourthought, Inc.
Photo of Mike Olson
Mike Olson is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. You can contact Mr. Olson at mike.olson@fourthought.com.
Uche Ogbuji (uche@ogbuji.net), Principal Consultant, Fourthought, Inc.
Photo of Uche Ogbuji
Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. Mr. Ogbuji is a Computer Engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.

Summary:  In this installment of The Python web services developer, Mike Olson gets back to writing some Python code. This column will revisit the example code from the fifth and sixth columns in this series, in which Mike and Uche Ogbuji talked about SOAP.py and ZSI, two available SOAP implementations in Python. Mike will continue the examination of these libraries and see how they interact with each other.

Date:  30 May 2002
Level:  Introductory

Comments:  

One of the promises of web services is interoperability. As we explained in previous columns, web services built with different programming languages and on different platforms should work together just as well as services provided by the same programming language. In this installment of The Python web services developer, I will take the client and server for the calendar web service that we implemented in previous articles in this column and see how they interoperate with each other.

To use the samples within this article, follow the installation steps for SOAP.py in the fifth installment in this series and for ZSI in the sixth. (You should review both those articles for background on SOAP.py and ZSI; you can find links to them in the Resources section.) As a side note, a newer version of ZSI has recently become available: version 1.2 was released in March of this year. The samples presented in this column do work with both ZSI 1.1 and 1.2. For those interested in upgrading, see the Resources section below for a URL where you can download the latest version. Installation uses standard distutils commands. If you have a previous version of ZSI installed, you should remove it first to avoid conflicts. To install the new version, untar the distribution and change into the ZSI-1.2 directory. Execute the install command as follows:

[molson@penny ZSI-1.2]# python setup.py install

ZSI client to SOAP.py server

As the first part of the analysis, you will run the ZSI client against the SOAP.py server. The first thing that you need to do is change the ports that the client and the server communicate on. The SOAP.py example listens on port 8888, and the ZSI client listens on port 8080. Either port is fine, and you'll have all examples set to port 8888.

Initially, you will try to run the scripts exactly as they were originally presented (except for the port change, of course). Remember, to start the server, in one window, run the script soapy-server.py:

python soapy-server.py

In a second window, run the script zsi-client.py:

python zsi-client.py

In the client window, you should get an error message that says:

Traceback (most recent call last):
  File "zsi-client.py", line 21, in ?
    print b.Receive(TC.Any(aslist = b.aslist))
  File "/usr/local/lib/python2.1/site-packages/ZSI/client.py", line 227, in Receive
    raise TypeError, "Unexpected SOAP fault: " + msg.string
TypeError: Unexpected SOAP fault: No method getMonth found

Examining the debugging messages that you get using the tips shown in the previous columns (see the Resources for the fifth column on SOAP.py), you can see the problem straightaway. The namespace of the method being invoked is incorrect. The method is referenced in the SOAP body without a prefix, and there is no default namespace defined. The SOAP.py server defined getMonth in the http://uche.ogbuji.net/eg/ws/simple-cal namespace, so you need to adjust the ZSI client so it makes the request for the proper method in the proper namespace.

You cannot solve this by simply calling getMonth on the ZSI Binding object; you need to use some of the lower-level interfaces so that you can add key arguments where needed. What you need to specify is the namespace of the method being called. To do this, you need to change the name of the method being called (add a prefix to the name) and specify the prefix-to-namespace mapping in the nsdict argument. Listing 1 shows what the ZSI client looks with the changes:


Listing 1. Updated ZSI client

#zsi-client.py
import sys

#Import the ZSI client
from ZSI.client import Binding
from ZSI import TC


u = ''
n = 'http://uche.ogbuji.net/eg/ws/simple-cal'
b = Binding(url=u, ns=n, host='localhost', port=8888)

b.Send(None,
       "ns1:getMonth",
       (2002,2),
       requesttypecode=TC.Any('ns1:getMonth', aslist=b.aslist),
       nsdict={'ns1':n},
       )

res = b.Receive(TC.Any(aslist = b.aslist))
print res[0]

With these changes, the ZSI client has no problems printing out the calendar for February of 2002 returned by the SOAP.py client. It looks like you have achieved success with the first half of the analysis. Now let's see how communication works going the other direction.


Flipping it around: ZSI server, SOAP.py client

The first thing you need to do to in order to test a SOAP.py client with the ZSI server is to write the SOAP.py client. If you remember, we wrote a very low-level client to test the SOAP.py server using httplib (see the Resources for the sixth column on ZSI). Since you want to see how SOAP.py interacts with ZSI, this will not do. A quick client can be written to use your calendar web service, as shown in Listing 2.


Listing 2. Updated ZSI client

#!/usr/bin/env python

import sys

#Import the SOAP.py machinery
from WebServices import SOAP

CAL_NS = "http://uche.ogbuji.net/eg/ws/simple-cal"


remote = SOAP.SOAPProxy(
    "localhost:8888",
    #namespace=CAL_NS,
    soapaction=""
)


print remote.getMonth(2002,2)

This client is implemented similarly to the curses client from the fifth column. One thing to note is that the namespace argument to the SOAPProxy is commented out. Just as the default implementation of the ZSI client was not namespace aware, the server is not either.

Again, to run the sample scripts, start the SOAP.py server in one window:

python zsi-server.py

And the ZSI client in a second window:

python soappy-client.py

As expected (since this client was written specifically to communicate with the SOAP.py server) you get back a calendar for February 2002.


ZSI and namespaces

As the next test, you will expand the ZSI client so that it does support methods inside of a namespace. There are some hurdles to overcome to do this; but Rich Salz, the author of the ZSI package, says that support for namespaces on methods is coming soon. He says that his intended approach is to use a new feature in Python 2.2 that allows you to define arbitrary attributes to functions. You would add a namespace attribute on a function definition, and then the ZSI-dispatching code would query for this attribute to determine which namespace the method is defined in. For future reference, you can add any attribute to a function by setting it on the function object. Below is a short example:

>>> def foo(a):
...    pass
...
>>> foo.namespace = "http://foo.com"
>>> print foo.namespace
http://foo.com
>>>

Salz also mentioned that he might add support for other attributes on functions besides namespace information, such as argument typing information. This would allow ZSI to typecheck incoming arguments before the function is called.

At any rate, to make the ZSI server namespace aware, you need to add a bit of logic to the getMonth method (and any other method that we would like to be in a namespace). The first thing that you can try is to get a ClientBinding for the current invocation. This contains all of the relevant information about the call, including its namespace. (By default, ZSI ignores the namespace.) You can then compare the namespace from the ClientBinding to the namespace that you are expecting. If there is a match, then you can return the proper results; if not, you can throw an exception to say that the method was not defined. Listing 3 shows an example of how the refactored getMonth method would look.


Listing 3. Refactored getMonth method

def getMonth(year, month):
  cb = dispatch.GetClientBinding()
  if cb.GetNS() != CAL_NS:
    raise TypeError, "Unimplemented method %s %s" % (cb.GetNS(),name)
  return calendar.month(year, month)

Unfortunately, to use this technique, you would need to add this logic to every function that is defined in the module. Listing 4 takes a more general approach that will allow all of the functions that you define in a module to be easily defined inside of a namespace. You can even easily define the same method in multiple namespaces, or multiple methods with different namespaces (but the same method names). To accomplish this, the function called _functionMap will generically map a request using a mapping dictionary. The dictionary is keyed off a namespace, and each entry references a function to be invoked when a request in the corresponding namespace is received. Then, to give the appearance of top-level functions in the module (as required by ZSI), you use a simple lambda that will stand in the place of the actual function definition. In the case of getMonth, the getMonth lambda is called; this calls _functionMap, which looks at the namespace of the call, as well as the mapping of functions available. If a match is found, the function is invoked; if not, an exception is raised. Now, to add new functions to the module, you would define a function and a corresponding lambda.

One thing to note: you need to put a reference to _functionMap in the defaulted arguments of each lambda definition. This is because when you are done creating the lambdas, you want to delete the module's reference to the _functionMap function; if you don't, this function would be exposed over your web services interface, as ZSI allows any method defined at the top level to be called.

Listing 4 shows the file zsi-ns-server.py, which implements the namespace-aware server in ZSI.


Listing 4. zsi-ns-server.py: A more general approach

#!/usr/bin/env python

import sys, calendar

#Import the ZSI machinery
from ZSI import dispatch

CAL_NS = "http://uche.ogbuji.net/eg/ws/simple-cal"

#The actual implementations
def getMonth(year, month):
    return calendar.month(year, month)

def getYear(year):
  return calendar.calendar(year)

#Generic function to check the namespace
def _functionMap(name,mapping,*args):
  cb = dispatch.GetClientBinding()
  func = mapping.get(cb.GetNS())
  if func is None:
    raise TypeError, "Unimplemented method %s %s" % (cb.GetNS(),name)
  return apply(func,args)

#Publicly defined methods
getMonthMap = {CAL_NS:getMonth,
               }
getMonth = lambda year,month,_functionMap=_functionMap,map=getMonthMap:_functionMap
("getMonth",map,year,month)


getYearMap = {CAL_NS:getYear,
              }
getYear = lambda year,_functionMap=_functionMap,map=getYearMap:_functionMap("getYear",map,year)


#Delete this so it is not available as a service.
del _functionMap

  
print "Starting server..."
dispatch.AsServer(port=8888)


Coming soon

In the next installment of this column, we will compare some different distributed programming technologies and look at some of the performance characteristics of each. We will compare SOAP, CORBA (Common Object Request Broker Architecture), XML-RPC, and a roll-your-own implementation written with the pickle library. We will look at the message overhead, application memory footprint, message transmission times, and relative source code size needed when implementing each of these methods.


Resources

About the authors

Photo of Mike Olson

Mike Olson is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. You can contact Mr. Olson at mike.olson@fourthought.com.

Photo of Uche Ogbuji

Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management applications. Fourthought develops 4Suite, and 4Suite Server, open source platforms for XML middleware. Mr. Ogbuji is a Computer Engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in

If you don't have an IBM ID and password, register here.


Forgot your IBM ID?


Forgot your password?
Change your password


By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)


By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and web services
ArticleID=11666
ArticleTitle=The Python web services developer: Python SOAP libraries, Part 3
publish-date=05302002
author1-email=mike.olson@fourthought.com
author1-email-cc=
author2-email=uche@ogbuji.net
author2-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).