Wednesday 4 January 2012

Python SUDS (SOAP API) full example with WSSE and complex types


I began this task in Perl originally, then decided to switch to Python and have an easier ride. I will upload the Perl example when I sort out a few minor issues with the SOAP::Lite library.

Anyway, in this example, you can specify a WSDL and WSSE (Web Service Security Extensions) username and password (sent in clear text btw), and it will send a SOAP request out and get a sample response back.

I have purposely consumed a service that has some complex types available (basically not just strings and ints). You can see how it is to work with the library and consume your own methods with this example.


Here are some useful things to note
- There have been problems with SUDS generating empty tags for optional properties for complex types. If this is the case, you will receive this error in your SOAP Body's response... "Server was unable to read request. ---> There is an error in the XML document. ---> Instance validation error: '' is not a valid value for PROPERTY_HERE."... To get around this, simply specify those properties (example below)

- client.factory.create() is used to let Python know about the complex types.

- "print client" (Using the example below) will tell you everything you need to know about your service (namespaces, types, methods, properties etc).

- The logger is your friend! Don't be a hero! Start out small and go big! The technique is to analyze the SOAP response and query the errors. If you can get a hold of what the correct SOAP envelope should look like, then compare this against the SOAP request you are sending out. This is the easiest way to solve any problems.

- Coming from a .NET background, I added a service reference and made a simple call with C#. You can then write more code to analyze the SOAP Request, or simply install Fiddler2 (If you haven't got it already, only 600kb and very useful!) to get the correct SOAP envelope to compare against.


Code Snippet
  1. #!/usr/bin/python
  2. #
  3. # Sean Greasley. TutorialGenius.com 2012.
  4. #
  5. # Creates a portfolio object using the exacttarget SOAP API. An image must exist at the specified URN
  6. # before alerting the system that the image is ready to be processed,
  7. #
  8. # USAGE:
  9. #    -portfolio <Display Name> <URN> <File Name> <Optional: CustomerKey>
  10. #    -portfoliowsdl <WSDL Address> <WSSE Username> <WSSE Password> <Display Name> <URN> <File Name> <Optional: CustomerKey>
  11. #
  12. #
  13. # Imports
  14. from suds.client import Client
  15. from suds.wsse import *
  16.  
  17. # Logging Options
  18. import logging
  19. logging.basicConfig(level=logging.INFO)
  20. logging.getLogger('suds.client').setLevel(logging.DEBUG)
  21. logging.getLogger('suds.wsdl').setLevel(logging.DEBUG)
  22. logging.getLogger('suds.wsse').setLevel(logging.DEBUG)
  23.  
  24.  
  25. # Define usage options
  26. def printUsage():
  27.     print ""
  28.     print "[USAGE]"
  29.     print "------------------------------------------------------------------------"
  30.     print "    " + sys.argv[0] + " -portfolio <Display Name> <URN> <File Name> <Optional: CustomerKey>"
  31.     print "    " + sys.argv[0] + " -portfoliowsdl <WSDL Address> <WSSE Username> <WSSE Password> <Display Name> <URN> <File Name> <Optional: CustomerKey>"
  32.     print ""
  33.     return
  34.  
  35.  
  36. # Validate argument input
  37. if (len(sys.argv) <= 1):
  38.     print "Invalid usage options..."
  39.     printUsage()
  40.     sys.exit(1)
  41. elif (sys.argv[1] == "-portfolio" and (len(sys.argv) == 5 or len(sys.argv) == 6)):
  42.     print "Setting up a portfolio"
  43. elif (sys.argv[1] == "-portfoliowsdl" and (len(sys.argv) == 8 or len(sys.argv) == 9)):
  44.     print "Setting up a portfolio with WSDL options"
  45. else:
  46.     print "Invalid usage options..."
  47.     printUsage()
  48.     sys.exit(1)
  49.  
  50.  
  51.  
  52. # Setup variables
  53. WSDL_URL = "https://webservice.s4.exacttarget.com/etframework.wsdl"
  54. WSSE_USERNAME = "Username here!"
  55. WSSE_PASSWORD = "Password here!"
  56. PORTFOLIO_DISPLAYNAME = "Test Sean Display Name1"
  57. PORTFOLIO_URN = "http://www.ct4me.net/images/dmbtest.gif"
  58. PORTFOLIO_FILENAME = "dmbtest.gif"
  59. PORTFOLIO_CUSTOMERKEY = ""
  60.  
  61. if (sys.argv[1] == "-portfoliowsdl"):
  62.     WSDL_URL = sys.argv[2]
  63.     WSSE_USERNAME = sys.argv[3]
  64.     WSSE_PASSWORD = sys.argv[4]
  65.     PORTFOLIO_DISPLAYNAME = sys.argv[5]
  66.     PORTFOLIO_URN = sys.argv[6]
  67.     PORTFOLIO_FILENAME = sys.argv[7]
  68.    
  69.     try:
  70.         PORTFOLIO_CUSTOMERKEY = sys.argv[8]
  71.     except:
  72.         print "No Customer key specified. Using default..."
  73. elif (sys.argv[1] == "-portfolio"):
  74.     PORTFOLIO_DISPLAYNAME = sys.argv[2]
  75.     PORTFOLIO_URN = sys.argv[3]
  76.     PORTFOLIO_FILENAME = sys.argv[4]
  77.    
  78.     try:
  79.         PORTFOLIO_CUSTOMERKEY = sys.argv[5]
  80.     except:
  81.         print "No Customer key specified. Using default..."
  82.  
  83.  
  84. # URL Detail
  85. client = Client(WSDL_URL)
  86.  
  87.  
  88. # WSSE Security
  89. security = Security()
  90. token = UsernameToken(WSSE_USERNAME, WSSE_PASSWORD)
  91. security.tokens.append(token)
  92. client.set_options(wsse=security)
  93.  
  94.  
  95. # Build up portfolio
  96. # 'Portfolio' is a complex type... so we use the create method to expose the properties to us. We can then populate the properties as normal.
  97. portfolio = client.factory.create('Portfolio')
  98. portfolio.DisplayName = PORTFOLIO_DISPLAYNAME
  99. portfolio.CustomerKey = PORTFOLIO_CUSTOMERKEY
  100. portfolio.Source = client.factory.create('ResourceSpecification')
  101. portfolio.Source.URN = PORTFOLIO_URN
  102. portfolio.FileName = PORTFOLIO_FILENAME
  103.  
  104.  
  105. # For some reason the SUDS library tends to generate empty SOAP tags for optional properties. Here I have manually specified the defaults here. Just be aware of that!
  106. createOptions = client.factory.create('CreateOptions')
  107. createOptions.RequestType = "Synchronous"
  108. createOptions.QueuePriority= "High"
  109.  
  110.  
  111. # Attach Portfolio to array - Need to set at pos 0, as it returns 1 by default.
  112. apiObject = [client.factory.create('APIObject')]  # Remember [ ], its an array!
  113. apiObject[0] = portfolio
  114.  
  115.  
  116. # Create portfolio
  117. # This method also had 'out' parameters exposed
  118. print client.service.Create(createOptions, apiObject)
  119.  
  120.  
  121. # Uncomment this next line to find out useful information about your service.
  122. # print client
End of Code Snippet

1 comment:

Anonymous said...

Thank you so much! Exactly what I was looking for! :)