Thursday, June 16, 2011

Sudsy Python Web Service Integration

I've recently started doing fulltime software development work for BrightLink Technologies, a software consulting firm specializing in software as a service for the testing and certification industries. One of our current clients needed our software to integrate with several of their legacy systems through web service interfaces, which led me to explore the state of SOAP in the Python world. At the moment, there appear to be several libraries which are functional but, er, perhaps not maintained with the same enthusiasm they once were...

I first tried SOAPpy (not to be confused with SOAPy... sigh...), but a couple of quick tests did not leave me with the feeling that I would be able to quickly create web service clients with it. While it was able to parse my service's WSDL, it didn't reveal very much about the exposed services, particularly about the parameters to the service calls. Did I mention that the service is a Microsoft product? It is, and, as you might expect, it's a behemoth. The less guessing I have to do the better...

Next up I tried suds, which is almost awesome. Although its development seems to have slowed down, suds has some very nice documentation that is replete with examples and descriptions of its various options. It turned the service's mammoth WSDL into something almost legible:

Service (Dynamics_x0020_GP) tns="http://schemas.microsoft.com/dynamics/gp/2006/01"
Prefixes (4)
   ns0 = "http://microsoft.com/wsdl/types/"
   ns1 = "http://schemas.microsoft.com/dynamics/2006/01"
   ns2 = "http://schemas.microsoft.com/dynamics/gp/2006/01"
   ns3 = "http://schemas.microsoft.com/dynamics/security/2006/01"
Ports (2):
   (Dynamics_x0020_GPSoap)
      Methods (265):
         CreateBackOfficeRoleAssignment(BackOfficeRoleAssignment...
         CreateBusinessObjectUserAssignment(BusinessObjectUserAssignment...
         CreateCashReceipt(CashReceipt cashReceipt, ns1:Context Context, ...
         ...

      Types (1104):
         ABCCode
         ns1:AXCompanyKey
         Address
         AddressBase
         AddressKey
         AgingAmounts
         Amount
         ...

Once you've obtained a service description, creating objects and invoking service methods is easy. Suds includes a factory method that lets you create objects from the types defined in the WSDL:

context = self.client.factory.create('ns1:Context')
context.CultureName = "en-US"
context.CurrencyType = "Transactional"

company_key = self.client.factory.create('ns1:CompanyKey')
company_key.Id = 18

context.OrganizationKey = company_key

Even better from a Pythonista's perspective is that suds will also translate a dict into the appropriate SOAP object as long as the keys match up with the corresponding SOAP type names:

customer = {
    'Name': name,
    'Key': {'Id': id},
}

# invoke the service to create a customer
self.client.service.CreateCustomer(customer, context, customerPolicy)

Ahhh, much better! And, just to rub it in, suds makes Windows NTLM authentication easier to implement than it is in C#.

So then why almost awesome? Turns out there is a small but devastating bug that causes suds to occasionally conflate element namespaces when it is creating SOAP requests. This subtle problem is especially difficult to track down when the service includes type definitions from several related namespaces. And, of course, the services themselves produce singularly unhelpful error messages with titles like "Server Error" when you send these malformed messages. Thanks.

Fortunately the remedy is easy: some kind soul has posted a minuscule patch that eliminates suds' namespace confusion, smoothing out the one rough spot in an otherwise excellent tool.

2 comments:

  1. I love this tutorial, you've seriously saved us from our deadline (although we probably won't finish anyways).

    ReplyDelete
  2. Awesome. Been working on this for hours. Now it works thanks!

    ReplyDelete