Tuesday, December 2, 2014

SOAP Web Service Client in Python 2.7 Using suds Module

Recently, a former colleague of mine asked me to build him a Web Service Client. Fortunately, I have done this before with C# on .NET 2.0 and later versions with WCF, and it was a piece of cake. So, the project didn't worry me much as I know first hand that it is doable. Though, I started programming with C and Pascal, when I was a sophomore, and spent most of my carrier building applications in C# and Java; lately, Python and Ruby became my favorite languages. Now, probably, you can guess how I ended up with this blog post: I decided to use Python for this project and I stumbled upon an easy but tough problem of technology obscurity! I tried every hack suggested on the internet without success. Thanks to the thanksgiving holiday break, I end up having a working code after may trials. If you have faced with a similar problem, please read on. Before going further, if you are an experienced programmer stuck in a line or two of your code, just skip the notes and look for what you need in the code.

Background 

For this project, getting the wsdl requires authentication. Please note this is not a standard case. Some services require no authentication to get the wsdl but require authentication to consume the service, and other free public services may not need any authentication at all.  The other important background information relevant to this project was the WS-Security Header format. The Web Service owner (or publisher) organization already provided the correct WS-Security Header format for potential developers as shown in Fig. 1. This format is very important as this is the only WS-Security header format that the Service Provider understands. 

Fig. 1  The correct Security Header Format the provider expects (for this particular project)









Conceptual framework for developing the Web Service Client application in Python

For an experienced programmer in the SOA space, any Web Service Client application development is probably a trivial problem. However, the security layer of the SOAP Web Service implementation on the provider side may complicate the whole game depending on what language and related modules you are using. For this reason, before you begin coding, you need to look for a sample of the correct WS-Security Header format from the service provider, which is similar to the one depicted in Fig. 1. 

Basic constituent code blocks needed

The following are the required basic code blocks of your client application
  1. Session request section: request a session with the provider
  2. Session authentication section: provide credentials to the provider
  3. Client section: create the Client
  4. Security Header section: add the WS-Security Header to the Client
  5. Consumption section: consume available operations (or methods) as needed

What modules do you need?

Many suggested, either at stack-overflow or their own blogs, to use Python modules such as urllib2 ; however, none of the modules work-at least for this particular project.

So, here is the list of the modules you need to get. First of all, you need to download and install the latest version of suds from the following link:
https://pypi.python.org/pypi/suds-jurko/0.4.1.jurko.2
 Additionally, you need to download and install requests and suds_requests modules from the following links respectively.
https://pypi.python.org/pypi/requests
https://pypi.python.org/pypi/suds_requests/0.1
Once you successfully download and install these modules, you are good to go.

The code

Following the steps outlined earlier, my code looks like the following:

Imports:

import logging
from suds.client import Client
from suds.wsse import *
from datetime import timedelta,date,datetime,tzinfo
import requests
from requests.auth import HTTPBasicAuth
import suds_requests


Session request and authentication:

username=input('Username:')
password=input('password:')
session = requests.session()
session.auth=(username, password)

Create the Client:

client = Client(WSDL_URL, faults=False, cachingpolicy=1, location=WSDL_URL, transport=suds_requests.RequestsTransport(session))

Add WS-Security Header:

...
addSecurityHeader(client,username,password)
....
def addSecurityHeader(client,username,password):
    security=Security()
    userNameToken=UsernameToken(username,password)
    timeStampToken=Timestamp(validity=600)
    security.tokens.append(userNameToken)
    security.tokens.append(timeStampToken)
    client.set_options(wsse=security)
Please note that this method creates the security header depicted in Fig.1. So, your implementation may vary depending on the correct security header format provided by the owner of the service you are consuming.

Consume the relevant method (or operation) :

result=client.service.methodName(Inputs)

Logging:

One of the best practices in such implementations as this one is logging to see how the communication is executed. In case there is some issue, it makes debugging easy. The following code does basic logging. However, you can log many aspects of the communication in addition to the ones depicted in the code.

logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) logging.getLogger('suds.transport').setLevel(logging.DEBUG)

Result:

Here is the result in my case. Note that the server returned HTTP 200. This is the standard success code for HTTP request-response.

(200, (collectionNodeLmp){
   timestamp = 2014-12-03 00:00:00-05:00
   nodeLmp[] = 
      (nodeLmp){
         pnodeId = 35010357
         name = "YADKIN"
         mccValue = -0.19
         mlcValue = -0.13
         price = 36.46
         type = "500 KV"
         timestamp = 2014-12-03 01:00:00-05:00
         errorCodeId = 0
      },
      (nodeLmp){
         pnodeId = 33138769
         name = "ZION 1"
         mccValue = -0.18
         mlcValue = -1.86
         price = 34.75
         type = "Aggregate"
         timestamp = 2014-12-03 01:00:00-05:00
         errorCodeId = 0
      },
 })

Conclusion

For those Python enthusiasts who tried every piece of hack suggested on the net, to do SOAP Web Service Client,  and couldn't make it work, hopefully, this post gets you on the way and takes you where you want to go. If you have questions or suggestions, please do not hesitate to say something. If your client couldn't work even if you follow the steps depicted here, please let me know. We can figure it out together. Enjoy!

4 comments:

  1. Thanks for step by step code...
    Using the same code, I am getting error
    "AttributeError: 'NoneType' object has no attribute 'promotePrefixes'"
    I don't know where I am doing wrong...
    Please help me on this

    ReplyDelete
  2. hi,
    why does this only work for python 2?????

    ReplyDelete
  3. hi,
    why does this only work for python 2?????

    ReplyDelete
  4. Thanks for the solution, but I wanted to call async soap api using SUDs, It is working fine but I don't know how should I catch its response, can you please help me in that. hr is my question in stackoverflaw http://stackoverflow.com/questions/39227719/asynchronous-soap-api-call-using-python

    ReplyDelete