The "Web Services for Python" project, which forms an umbrella for ZSI and SOAPpy, provides the best-maintained and most usable web services tools for Python. We have looked closely at these packages previously in this column:
Since we wrote those articles, these packages have made important improvements and added features. In this article we will look at what's new with ZSI, looking to put it to work against a more industrial strength public web service.
We had a look at ZSI 1.4.1, testing it on Python 2.2.2. The most significant update in ZSI is support for WSDL. There is a new ZSI class ServiceProxy, which you initialize with the URL of a WSDL file. The documentation says it "uses a WSDL instance to send and receive messages." If you've followed our coverage of ZSI in this column you'll remember that ZSI has been an excellent library for web services that follow certain narrow rules, but unfortunately requires a number of obscure tricks and tweaks (and more code than you might expect) to get it to handle the general case.
Certainly it seems that ZSI can handle any web service out there. It's just that it doesn't take much variation before you find yourself writing all sorts of complex code to process structures with type codes. The root of the problem is that SOAP message structure tries to combine the traditional rigors of RPC with the flexibility of XML. One of the purposes of WSDL is to provide a structured-enough description of a service's message protocol so that it allows applications to automatically figure out things the developer would have to sort out. The addition of WSDL support to ZSI offers the promise of reducing the need for the developer to be too clever about interpreting the SOAP message. Whether the web service uses positional parameters, named parameters, or both, and whether it uses simple, complex, or custom data types, the WSDL file should provide the needed intelligence. As such, it was with some anticipation that we tried ServiceProxy not on the sorts of toy web services (such as the Captain Haddock curser or the simple calendar service) we have used with ZSI in the past, but rather one of the more useful public SOAP-based web services that are finally beginning to make their appearance.
Richard Hastings' Air Fare Quote Search is implemented in Apache Axis and searches some airline web sites in real time to find the best available flight prices for a given itinerary (see Resources). It aggregates and returns the results, sorted by price. The WSDL is at http://wavendon.dsdata.co.uk:8080/axis/services/SBGGetAirFareQuote?wsdl. It defines two operations: getAirFareQuote and getAirlines. The former is used to execute the price search and takes 4 parameters: two W3C XML Schema Language Data Types (WXSDT) dateTime values giving the approximate start of the departure and end of the return flights and two WXSDT string values giving the three-letter airport codes between which the travel is to take place. In interactive Python, we tried the following code:
>>> from ZSI import ServiceProxy >>> wsdl = 'http://wavendon.dsdata.co.uk:8080\ ... /axis/services/SBGGetAirFareQuote?wsdl' >>> proxy = ServiceProxy(wsdl) |
The examples in the ZSI manual also show the use of a typesmodule keyword parameter to the ServiceProxy initializer but doesn't really explain this parameter properly, so we ignored it.
When dates are a real struggle
We need two date/time objects for the service invocation, and the ZSI manual says that "SOAP dates and times are Python time tuples in UTC (GMT), as documented in the Python time module." We tried the following code to see what it would cost to get us to the XML 2003 conference in Philadelphia:
>>> import time
>>> ISO_8601_DATETIME = '%Y-%m-%dT%H:%M:%S'
>>> dep = time.strptime('2003-12-06T12:30:59', ISO_8601_DATETIME)
>>> ret = time.strptime('2003-12-12T12:30:59', ISO_8601_DATETIME)
>>> proxy.getAirFareQuote(dep, ret, 'den', 'phl')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "/usr/lib/python2.2/site-packages/ZSI/ServiceProxy.py", line 82, in __call__
return self.parent()._call(self.__name__, *args, **kwargs)
File "/usr/lib/python2.2/site-packages/ZSI/ServiceProxy.py", line 65, in _call
apply(getattr(binding, callinfo.methodName), args)
File "/usr/lib/python2.2/site-packages/ZSI/client.py", line 28, in __call__
requesttypecode=TC.Any(self.name, aslist=1))
File "/usr/lib/python2.2/site-packages/ZSI/client.py", line 131, in RPC
self.Send(url, opname, obj, **kw)
File "/usr/lib/python2.2/site-packages/ZSI/client.py", line 169, in Send
sw.serialize(obj, tc, typed=0)
File "/usr/lib/python2.2/site-packages/ZSI/writer.py", line 73, in serialize
typecode.serialize(self, pyobj, **kw)
File "/usr/lib/python2.2/site-packages/ZSI/TC.py", line 282, in serialize
Any().serialize(sw, val)
File "/usr/lib/python2.2/site-packages/ZSI/TC.py", line 315, in serialize
raise EvaluateException('''Any can't serialize ''' + \
ZSI.EvaluateException: Any can't serialize (2003, 12, 6, 12, 30, 59, 5, 340, 0)
|
After a lot of vain experimentation (including with ZSI's gDateTime class), poring over the documentation, and even some Google searches on the topic we couldn't figure how to get ZSI to pass a date/time as a parameter without writing a special class for date/time objects using the gDateTime class as a type code. This seems an amazing hurdle to make a programmer jump over for such a common data type.
ZSI also now comes with a script, wsdl2py, which is undocumented as far as we can tell, but the Python source carries the doc string:
"A utility for automatically generating client interface code from a wsdl definition, and a set of classes representing element declarations and type definitions. This will produce two files in the current working directory named after the wsdl definition name."
This sounds promising, and we thought it might see the WXSDT dateTime parameters defined in the WSDL file and generate all the magic we needed to marshall these values from Python. The script can't handle WSDL URLs, so we had to download the definition to a local file. Here is an example of how to use the wget command on UNIX:
wget -O SBGGetAirFareQuote.wsdl \ http://wavendon.dsdata.co.uk:8080/axis/services/SBGGetAirFareQuote?wsdl |
Unfortunately, we didn't get very far with the wsdl2py script, as the following listing shows:
wsdl2py -f SBGGetAirFareQuote.wsdl
Traceback (most recent call last):
... Part of traceback omitted for brevity ...
File "/usr/lib/python2.2/site-packages/ZSI/wsdl2python.py", line 1058,
in _fromComplexType
self._complexTypeComplexContent(tp)
File "/usr/lib/python2.2/site-packages/ZSI/wsdl2python.py", line 1147,
in _complexTypeComplexContent
arrayinfo = dt.getArrayType()
File "/usr/lib/python2.2/site-packages/ZSI/wsdlInterface.py", line 1712,
in getArrayType
raise WsdlInterfaceError, 'could not determine array type'
ZSI.wsdlInterface.WsdlInterfaceError: could not determine array type
|
It appears the script cannot handle the array of complex types in the search return structure for the getAirFareQuote operation, as represented in the WSDL types section:
<complexType name="ArrayOfAirFareQuote">
<complexContent>
<restriction base="soapenc:Array">
<attribute wsdl:arrayType="impl:AirFareQuote[]" ref=
"soapenc:arrayType"/>
</restriction>
</complexContent>
</complexType>
<element nillable="true" type="impl:ArrayOfAirFareQuote" name=
"ArrayOfAirFareQuote"/>
<complexType name="ArrayOf_xsd_string">
<complexContent>
<restriction base="soapenc:Array">
<attribute wsdl:arrayType="xsd:string[]" ref=
"soapenc:arrayType"/>
</restriction>
</complexContent>
</complexType>
<element nillable="true" type="impl:ArrayOf_xsd_string" name=
"ArrayOf_xsd_string"/>
|
To be fair to ZSI, WSDL array types are perhaps the most notable exemplar of everything that is poor about encoding RPC payloads in XML, but that's a topic for another article. After commenting out the above type definitions -- and replacing them in the WSDL with dummy WXSDT string types -- wsdl2py succeeded in producing two modules packed with impressive-looking code. Unfortunately squinting at all this code didn't shed much light into how to properly marshall the input date parameters, so we sadly called it a defeat. To be sure we weren't completely off the rails, we asked one of the ZSI developers about the trouble marshalling date types. His response indicates that it probably is indeed a bug in ZSI, but it is also likely to be fixed soon. If we learn more about this problem we'll make sure it's posted to the forum for this column (see Resources).
A wrapper for ZSI and a wrap-up
At least one developer trying to use ZSI has decided that there has to be an easier way to take advantage of the richness underlying ZSI's difficult interface. OSE is a framework for distributed and web-based applications which offers SOAP support, building on ZSI. Part of OSE is the zsirpc package which provides a more friendly wrapper for ZSI, although it only handles web services that are encoded in similar fashion to XML-RPC. Most usefully, it provides straightforward wrappers for passing boolean, binary (encoded as BASE64), date, dateTime, time and duration types, which ZSI supports, but as we have learned, only with a lot of extra work by the programmer writing specialized structures.
We hope that some of the code from zsirpc makes it into ZSI itself. This would make life a lot easier for developers of RPC-style web services. Then again, perhaps it is better to continue to encourage developers towards the document-literal style of web services. We have long been advocates of such style and it is good to see that the mainstream of web services finally tend in this direction. It will finally allow developers to express messages in terms of the dynamics of XML itself, rather than bewildering layers of conventions to try to extend a single data model for communications across platforms and languages.
ZSI isn't the only Python web services library to go through significant development. SOAPpy is also in active development, and WSDL is also a large part of this work. We shall have a look at what's new in SOAPpy in the near future.
- Participate in the discussion forum.
-
Check out Python web Services on SourceForge, which is the project under which ZSI and SOAPpy are currently hosted.
- See Richard Hastings' Air Fare Quote Search, which is implemented in Apache Axis and searches some airline web sites in real time to find the best available flight prices for a given itinerary.
-
OSE is the distributed applications framework project of which zsirpc is a component. zsirpc is available as a separate package among the downloads.
- Follow up on document/literal style for SOAP in the article "Reap the benefits of document style web services" by James McCarthy (developerWorks, June 2002).
- Read the previous column of this series on SOAP over SMTP or check out all the Python web services developer columns in this series.
Scott Archer is a software architect and co-founder of GlowingOrb, Inc., a software tools developer focusing on model-driven solutions and their integration into core business processes. Mr. Archer holds an M.Phil in Computational Molecular Biology from the University of Hong Kong. You can contact Mr. Archer at scott.archer at glowingorb.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, 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.