With and without WSDL
Continuing their look at Python SOAP implementations, Mike Olson and Uche Ogbuji put the Zolera SOAP Infrastructure (ZSI) through its paces as a SOAP client and server library. They find that ZSI is possibly the best tool for SOAP use under Python right now.
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.
Setting up ZSI
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:
Download the ZSI package, unpack it, and build it the same way:
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.
Invoking client privilege
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
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.
TypeCode module allows a precise and extensible mapping
between Python data types and SOAP data types. It has a set of built-in
Integer, etc. It also defines
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
It is quite easy to enable debugging and tracing in ZSI. Just pass a
tracefile=sys.stdout parameter to the
initializer. After making this modification, the sample ZSI session looks like: