4.3. Python ZSI SOAP client for JWSDP and .NET web service.

I couldn't talk about SOAP client with Python without mentionning ZSI. This wonderfull library is probably much more powerfull than the others, but also more complicated to use. I will show how to consume a Java WSDP and a .NET web service with ZSI in this page. Like usually, make sure you have the last ZSI package installed on your system, see ZSI at sourceforge.

4.3.1. Interoperability between a Java WSDP web service and a Python ZSI client.

In this first exemple I will just consume two methods: readLS (taking and returning a string) and readTemp (taking an integer and returning a float). We will see the complexity of what should have been a simple call returning simple type. The Java WSDP web service needs named parameters, so we have to register a class for each parameter and also for each return type. Let's see how to handle simple call with ZSI.

Example 4-7. ZSI SOAP client for my Java WSDP web service.


# rcx web service client with ZSI

import sys

from ZSI import TC
from ZSI.client import Binding

serverPath = '/rcx-ws/rcx'
myns = 'http://phonedirlux.homeip.net/types'

mybind = Binding(url=serverPath, nsdict={'ns1':myns}, host='www.pascalbotte.be', port=80,
tracefile=sys.stdout)

# we define a class mapping 
# the parameter for the function readLS
class ReadLSRequest:
  def __init__(self, String_1):
    self.String_1 = String_1

ReadLSRequest.typecode = TC.Struct(ReadLSRequest,
[TC.String('String_1')],
'ns1:readLS',
inline=1)

# same for the parameter of
# the readTemp function
class ReadTempRequest:
  def __init__(self, int_1):
    self.int_1 = int_1

ReadTempRequest.typecode = TC.Struct(ReadTempRequest,
[TC.Integer('int_1')],
'ns1:readTemp',
inline=1)

# we have to define also a class
# for the return type of the functions
class ReadLSResponse:
  def __init__(self, result):
    self.result = result

  def __str__(self):
    return self.result

ReadLSResponse.typecode = TC.Struct(ReadLSResponse,
[TC.String('result')], 'readLSResponse')

class ReadTempResponse:
  def __init__(self, result):
    self.result = result

  def __str__(self):
    return self.result

ReadTempResponse.typecode = TC.Struct(ReadTempResponse,
[TC.Decimal('result')], 'readTempResponse')

try:

  # call readLS
  mybind.Send(serverPath, 'ns1:readLS', ReadLSRequest('your message or e-mail'))
  result = mybind.Receive(ReadLSResponse.typecode)

  # call readTemp
  mybind.Send(serverPath, 'ns1:readTemp', ReadTempRequest(1))
  temp = mybind.Receive(ReadTempResponse.typecode)

  # print the results
  print 'Light sensor simulation: %s' % result
  print 'temperature: %.1f' % temp.result

except:
  raise
  print 'reply=', mybind.reply_code
  print 'reply_msg=', mybind.reply_msg
  print 'headers=', mybind.reply_headers
  print 'data=', mybind.data

4.3.2. Deserialize complex type object returned by a Java WSDP web service using ZSI.

The remote procedure status() return my rcxResponse object. This object contains some internal values of the RCX like the internal clock, memory, etc... The best way to build the necessary classes, by hand, to unmarshal the complex response returned by the web service, is to start from a SOAP excerpt of the message received.

...
<ns1:statusResponse>
  <response>
    <battery>6.2</battery>
    <currentTime>123</currentTime>
    <memory>12335</memory>
    <memoryTot>15600</memoryTot>
    <status>Rcx on-line</status>
  </response>
</ns1:statusResponse>
...

We will need a structure of two classes, one to tell ZSI how to deserialize <statusResponse>, and one for the <response>. The last one containing the values requested. Find below the code needed to consume the status() remote procedure.

Example 4-8. Accessing internal value of my RCX with Python an ZSI.


import sys

from ZSI import TC
from ZSI.client import Binding

serverPath = '/rcx-ws/rcx'
myns = 'http://phonedirlux.homeip.net/types'
mybind = Binding(url=serverPath, nsdict={'ns1':myns}, host='www.pascalbotte.be', port=80)

# status request
class StatusRequest:
  def __init__(self):
    None 
StatusRequest.typecode = TC.Struct(StatusRequest,
[],
'ns1:status',
inline=1)

# status element response (<result> level)
class StatusElement:
  def __init__(self, battery=0.0, currentTime=0, memory=0, memoryTot=0, status=None):
    self.battery = battery
    self.currentTime = currentTime
    self.memory = memory
    self.memoryTot = memoryTot
    self.status = status
# in bold here, will be used later
StatusElement.typecode = TC.Struct(StatusElement,
[TC.Decimal('battery'),
TC.Integer('currentTime'),
TC.Integer('memory'),
TC.Integer('memoryTot'),
TC.String('status')],
'result')

# status response (<statusResponse> level)
class StatusResponse:
  def __init__(self, result):
    self.result = result

StatusResponse.typecode = TC.Struct(StatusResponse,
[StatusElement.typecode], 'statusResponse')

try:

  mybind.Send(serverPath, 'ns1:status', StatusRequest())

  statusresp = mybind.Receive(StatusResponse.typecode)

  #rawres = mybind.ReceiveRaw()

  #dir could sometime be very helpfull
  #print dir(statusresp)

  #print dir(statusresp.result)

  resultobj = statusresp.result

  print 'Battery level: %.2f' % resultobj.battery
  print 'Internal clock: %d' % resultobj.currentTime
  print 'Free memory: %d' % resultobj.memory
  print 'Rcx message: %s' % resultobj.status

  #want to view the SOAP message receive?
  #print rawres

except:
  raise
  print 'reply=', mybind.reply_code
  print 'reply_msg=', mybind.reply_msg
  print 'headers=', mybind.reply_headers
  print 'data=', mybind.data

4.3.3. Use WSDL2py to generate the class needed to unmarshall array of simple type, or array of object returned by a JWSDP web service.

For my remote procedure collInt() returning an array of integer, we will use the tool wsdl2py that comes with the ZSI package. Go to your_Python_install_directory/Scripts and you will find the script wsdl2py. You can invoke the tool:

your_prompt:\> python wsdl2py -u url_path_to_wsdl

The command will generate two files, one contains the stub class to help invoke the web service, and we won't use it. The other is the class type needed to serialize/deserialize the SOAP message. Note here my Java WSDP web service needs namespace prefix, and we have to make a little modification in the class generated to specify the use of this prefix (ns1). The file we have to modify is: MyRcxService_services_types.py, and more specifically we will have to modify the class collInt_Def and collInt_Dec. Find below these classes with the modification in bold:

class collInt_Def(ZSI.TCcompound.Struct):
        schema = 'http://phonedirlux.homeip.net/types'
        type = 'collInt'

        def __init__(self, name=None, ns=None, **kw):
            # internal vars

            TClist = []

            oname = name

            if name:
                aname = '_%s' % name
                if ns:
                    oname += ' xmlns:ns1="%s"' % ns
                else:
                    oname += ' xmlns="%s"' % self.__class__.schema
            else:
                aname = None

            ZSI.TCcompound.Struct.__init__(self, self.__class__, TClist,
                                           pname=name, inorder=0,
                                           aname=aname, oname=oname,
                                           **kw)



    class collInt_Dec(collInt_Def):
        literal = "ns1:collInt"
        schema = "http://phonedirlux.homeip.net/types"

        def __init__(self, name=None, ns=None, **kw):
            name = name or self.__class__.literal
            ns = ns or self.__class__.schema

            ns1.collInt_Def.__init__(self, name=name, ns=ns, **kw)
            self.typecode = ns1.collInt_Def(name=name, ns=ns, **kw)

Now we can consume the remote procedure collInt() returning an array of integer.

Similarily, we are going to use the same technique for the remote procedure collPos of my Java WSDP web service. This remote procedure return an array of object PosCol containing two integer variable (XPos and YPos). Modify the class collPos_Dec and collPos_Def by adding the namespace prefix the same way than for the collInt remote procedure. Here is the code allowing to call this function:


from MyRcxService_services import RcxReadLS_collPosWrapper,\
RcxReadLS_collPosResponseWrapper

from ZSI.client import Binding
import sys

serverPath = '/rcx-ws/rcx'
mybind = Binding(url=serverPath, host='www.pascalbotte.be', port=80)

msg = RcxReadLS_collPosWrapper()
msgresp = RcxReadLS_collPosResponseWrapper()
	
mybind.Send(serverPath, 'ns1:collPos', msg)

cpresp = mybind.Receive(msgresp)

collObj = cpresp._result
for obj in collObj:
  print 'Object XPos: %d, YPos: %d' % (obj._XPos, obj._YPos)

4.3.4. Use WSDL2py to consume an advanced .NET web service with Python and ZSI.

We continue here our interoperability demo cases by showing how to consume the .NET Infobel web service with ZSI and wsdl2py.

Go to the Scripts directory under your Python install and use the following command line to generate the class needed to serialize/deserialize the SOAP messages exchanged between your Python client and the .NET web service.

your_prompt:\> python wsdl2py -u http://hal.kapitol.com/infobelservices/service1.asmx?WSDL

Copy the two files: InfobelSearchEngines_services.py and InfobelSearchEngines_services_types.py in your working directory. Then create your client:

Example 4-10. Python/ZSI client for the Infobel .NET web service using wsdl2py.

The .NET web service accept namespace without prefix, so no need to modify the classes generated by wsdl2py.

# Infobel client

from InfobelSearchEngines_services import SearchSoapInWrapper,\
SearchSoapOutWrapper

# we need this import, see below in bold
from InfobelSearchEngines_services_types import www_infobel_com_WebService as ns2

from ZSI.client import Binding
import sys

serverPath = '/infobelservices/service1.asmx'

mybind = Binding(url=serverPath, host='hal.kapitol.com', port=80)

msg = SearchSoapInWrapper()
msgresp = SearchSoapOutWrapper()

iq = ns2.CQuery_Def()

iq._login = 'infobel'
iq._password = 'test'
iq._City = 'bruxelles'
iq._CoordType = "aeCTWGS"
iq._Language = "aeLangFrench"
iq._Name = "dupont"
iq._PageStep = 5
iq._Range = 0
iq._XCoord = 0
iq._YCoord = 0
iq._Zip = ""
iq._country = "aeCountryBE"
iq._service = "aeSrvStandard"

# pass the CQuery object to the
# inputQuery attribute of the Wrapper
msg._inputQuery = iq

# Usual call with soapAction mention
mybind.Send(serverPath, 'Search', msg, soapaction="http://www.infobel.com/WebService/Search")

response = mybind.Receive(msgresp)

# Use a print dir(response)
# to list all the attributes
searchResult = response._SearchResult

# Old message returned by the web service
print "Web service message: %s" % searchResult._Status

result = searchResult._Result

print "Records found: %d" % result._NumRecs

collrecord = result._collRecord

# access the collection of CRecord object
crecord = collrecord._CRecord

for rec in crecord:
  #print dir(rec)
  print "name: " + rec._Name
  print "phone: " + rec._Phone
  print "address: " + rec._Address
  print "city: " + rec._City
  print "zip: " + rec._Zip
  collcat = rec._collCategory
  if (collcat != None):
    #print dir(collcat)
    ccat = collcat._CCategory
    print "\tCategory:"
    for cat in ccat:
      #print dir(cat)
      print "\t%s %s" % (cat._code, cat._description)
  print

We see here how it is relatively easy to access a .NET web service from the ZSI, provided you use the wsdl2py script!.