This month we continue our look at SOAP implementations for Python. In fact, we will take a close look at one we completely neglected to mention last time: the Zolera SOAP Infrastructure (ZSI). ZSI is the brain-child of Rich Salz, an active contributor to the Python/XML package, and veteran of several important standards efforts in Internet infrastructure and security. ZSI has been added as part of the Python Web Services project on SourceForge, which also hosts SOAPy, another Python/SOAP package we covered in the previous installment. We expect this confusing state of affairs will be resolved soon, perhaps with a merger of the SOAP packages.
ZSI is a purist's SOAP library. In particular, it is less concerned about handling any low-level protocol details: It deals with a stream of data that must be a properly-formatted SOAP message, and dispatches events to Python event handlers based on the parsed data. The upside of this strict focus is that ZSI is probably the most complete and compliant SOAP library for Python. The downside is that the ZSI user may have to do more work to actually put his or her Web services into play.
To install ZSI 1.1, the latest version as of our writing, you need Python 2.0 or later. The instructions say you need PyXML 0.6 or later, but actually, you need version 0.6 or later, but earlier than 0.7. There were several significant API changes that appeared in PyXML 0.7 that break ZSI 1.1. This is a general problem issue while dealing with packages that depend on PyXML: if you get strange errors when using a package that states a dependency on PyXML 0.6, you might have understandably grabbed the latest available version of PyXML, which, unfortunately, will not work. The next release of ZSI will probably work with PyXML 0.7 and later, as the recent CVS activity seems to indicate. Grab PyXML 0.6.6 from SourceForge and unpack it, then build it:
$ python setup.py install |
Download the ZSI package, unpack it, and build it the same way:
$ python setup.py install |
This is the consistency that Python's distribution utilities (AKA distutils) brings to installing add-on Python packages.
Now that it's all installed, check out the doc directory, which has excellent documentation in HTML, PDF, postscript, and TeX format, using the same LaTeX tools as used in the Python documentation project.
First of all, we looked at using ZSI as a SOAP client. In the last installment, we wrote a SOAP.py client that accesses a toy Web service that returns curses by Captain Haddock (ever popular to Tin Tin comic fans). We will write a client for the same server using ZSI. Unfortunately, we ran into a bit of a kink doing this. The most straightforward approach is the code in Listing 1, but it turns out that this requires the server to handle message parameters by their order as well as their name, but the Borland Delphi implementation with which the Captain Haddock server is implemented isn't so flexible, so it gets confused.
Listing 1: Attempt at a ZSI program to access the Captain Haddock SOAP service
#http://xmethods.net/detail.html?id=175
import sys
#Import the ZSI client
from ZSI.client import Binding
u = '/scripts/Haddock.exe/soap/IHaddock'
n = 'urn:HaddockIntf-IHaddock'
b = Binding(url=u, ns=n, host='www.tankebolaget.se', port=80,
tracefile=sys.stdout)
try:
lang = sys.argv[1]
except IndexError:
lang = 'us'
result = b.Curse(lang)
print 'What captain Haddock had to say: "%s"'%result
|
Because this simple approach would work with many SOAP 1.1 servers, let's have
a look, even though it doesn't give us the expected result. First of all
we import the ZSI client module, and the Binding class, which
implements the machinery for setting up and invoking SOAP requests.
Next we set up the components of the address of the SOAP end point. Note
that we set up the host, the URL path, and the port separately. You cannot
use a single URL, as you can with SOAP.py. These components are used to
create a binding to the remote server.
Finally, we call the Curse method on the binding object to
send the remote request, and we get back the result in the method return.
But as we mentioned, this simple approach doesn't work with the Captain
Haddock server. We must use a more complex structure to communicate the
LangCode parameter to the server. The code in Listing 2 does the trick.
Listing 2: Working ZSI program to access the Captain Haddock SOAP service
import sys
#Import the ZSI client
from ZSI import TC
from ZSI.client import Binding
u = '/scripts/Haddock.exe/soap/IHaddock'
n = 'urn:HaddockIntf-IHaddock'
b = Binding(url=u, host='www.tankebolaget.se', port=80, ns=n)
try:
lang = sys.argv[1]
except IndexError:
lang = 'us'
class CurseRequest:
def __init__(self, langCode):
self.LangCode = langCode
CurseRequest.typecode=TC.Struct(CurseRequest,
[TC.String('LangCode')],
'Curse',
inline=1)
try:
result_list = b.RPC(u, 'Curse', CurseRequest(lang), TC.Any(aslist=1))
#Extract the first returned parameter
result = result_list[0]
print 'What captain Haddock had to say: "%s"'%result
except:
raise
print 'reply=', b.reply_code
print 'reply_msg=', b.reply_msg
print 'headers=', b.reply_headers
print 'data=', b.data
|
The TypeCode module allows a precise and extensible mapping
between Python data types and SOAP data types. It has a set of built-in
classes for String, Integer, etc. It also defines
a Struct class for defining arbitrary data aggregations, and
the special class Any which can be used to represent any built-in
or derived type and thus is the basis of dynamic typing. These latter two
classes are the key to navigating around the many interoperability problems
in SOAP encoding that stem from different marshalling and unmarshalling of typed data.
Even though we had to go through some contortions to get ZSI to align
with the Delphi server we're addressing, at least ZSI gives you all the
machinery to do so. With the other Python SOAP implementations
we have had to hack at the client library code to sort out interoperability problems.
We then create a class, CurseRequest, which will be marshalled into the
form required by the Captain Haddock server. We define an instance attribute
for the required LangCode parameter. Then we assign the class
a ZSI type code that marshals the LangCode value into a string.
One note is the inline=1 specification, which disables marshalling using multi-reference values. By default, ZSI marshals structures
using multi-reference, which means that you can
express a value in one part of the SOAP message and refer to that same
value by reference in another part of the SOAP message, in order to avoid
duplication of the values. SOAP encoding multi-reference values are much
less commonly used than just simple values, so it's odd that the default
in ZSI's structure types is to be marshalled as multi-reference.
This detail and others seem to reinforce a comment Rich Salz, author of ZSI, made to us: that ZSI is actually designed primarily for complex Web services. He promises to make it a bit easier soon to use ZSI against the toys that seem to be the majority of published services.
The following illustrates the use of the example client code
$ python curse-zsi.py What captain Haddock had to say: "Polygraphs!" |
It is quite easy to enable debugging and tracing in ZSI. Just pass a
tracefile=sys.stdout parameter to the Binding
initializer. After making this modification, the sample ZSI session looks like:
$ python curse-zsi-trace.py _________________________________ Thu Jan 31 10:25:09 2002 REQUEST: <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ZSI="http://www.zolera.com/schemas/ZSI/" > <SOAP-ENV:Body> <Curse id="822b6dc"> <LangCode id="81089d0" xsi:type="xsd:string">us</LangCode> </Curse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> _________________________________ Thu Jan 31 10:25:10 2002 RESPONSE: Server: Microsoft-IIS/5.0 Date: Thu, 31 Jan 2002 17:18:37 GMT Content-Type: text/xml Content-Length: 524 Content: <?xml version="1.0" encoding='UTF-8'?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <NS1:CurseResponse xmlns:NS1="urn:HaddockIntf-IHaddock" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <NS1:return xsi:type="xsd:string">Sea-lice!</NS1:return> </NS1:CurseResponse> </SOAP-ENV:Body></SOAP-ENV:Envelope> What captain Haddock had to say: "Sea-lice!" |
Setting up a ZSI server is simple enough. Listing 3 presents the same trivial calendar SOAP server we used as an example for SOAP.py in the last column. (See Resources.)
Listing 3: ZSI calendar SOAP server
#!/usr/bin/env python import sys, calendar #Import the ZSI machinery from ZSI import dispatch def getMonth(year, month): return calendar.month(year, month) def getYear(year): return calendar.calendar(year) print "Starting server..." dispatch.AsServer(port=8080) |
Note that this is even easier than with SOAP.py. All you do is define
a function for each method. With the required parameters, variable number
and keyword arguments can be used as well for positional and named parameters.
The dispatch.AsServer() call simply registers all the
defined functions as SOAP methods and starts up the HTTP server on the
specified port.
One problem is that ZSI's server code doesn't seem to have an easy way of
aligning the namespace used in the request. The documents claim there
is a dispatch.GetNS() function that returns the namespace that
was used in the request element, but this doesn't seem to be the case.
This is a pretty serious omission as the namespace used in the request is an
essential part of that request.
The following ZSI client code exercises the calendar we just wrote:
Listing 4: ZSI calendar SOAP client
#http://xmethods.net/detail.html?id=175 import sys #Import the ZSI client from ZSI.client import Binding u = '' n = 'http://uche.ogbuji.net/eg/ws/simple-cal' b = Binding(url=u, ns=n, host='localhost', port=8080) result = b.getMonth(2002, 2) print result[0] result = b.getYear(2002) print result[0] |
To test it, just start the server using "$ python calendar-zsi.py" in one console, then in another:
$ python2.1 cal-client.py
February 2002
Mo Tu We Th Fr Sa Su
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
2002
January February March
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 6 1 2 3 1 2 3
7 8 9 10 11 12 13 4 5 6 7 8 9 10 4 5 6 7 8 9 10
14 15 16 17 18 19 20 11 12 13 14 15 16 17 11 12 13 14 15 16 17
21 22 23 24 25 26 27 18 19 20 21 22 23 24 18 19 20 21 22 23 24
28 29 30 31 25 26 27 28 25 26 27 28 29 30 31
April May June
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 6 7 1 2 3 4 5 1 2
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9
15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23
29 30 27 28 29 30 31 24 25 26 27 28 29 30
July August September
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 6 7 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 2 3 4 5 6 7 8
15 16 17 18 19 20 21 12 13 14 15 16 17 18 9 10 11 12 13 14 15
22 23 24 25 26 27 28 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 29 30 31 23 24 25 26 27 28 29
30
October November December
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 5 6 1 2 3 1
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8
14 15 16 17 18 19 20 11 12 13 14 15 16 17 9 10 11 12 13 14 15
21 22 23 24 25 26 27 18 19 20 21 22 23 24 16 17 18 19 20 21 22
28 29 30 31 25 26 27 28 29 30 23 24 25 26 27 28 29
30 31
|
ZSI, which we completely missed in the last column, turns out to be the most mature and useable SOAP library for Python. The author expects to have version 1.2 out soon, which addresses some of the complaints we've made in this article.
Now that we have surveyed Python SOAP packages, and looked closely at a couple of them, in the next installment, we will look at how Python SOAP implementations interoperate with each other.
- Participate in the discussion forum.
-
The previous installment of this column, in which we made a survey of Python SOAP implementations and took a closer look at SOAP.py
-
Python Web Services on SourceForge, which is the project under which ZSI is currently hosted.
-
PyXML on SourceForge, version 0.6.6 of which is required by ZSI.

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 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 cont act Mr. Ogbuji at uche@ogbuji.net.
Comments (Undergoing maintenance)





