«

»

Jan 24

How to Call a SOAP Web Service from Google App Engine using Python

In the .NET world, we take a lot of things for granted. Having recently started a new project with Google App Engine (GAE) and Python, I came to realize that one of those things is SOAP web services. Creating and consuming SOAP services in the MS/corporate space is second nature, even a must, and it’s been made SO easy. Not so much elsewhere…

Being reminded of how much is happening behind the scenes, or how much has been hidden from us in terms of the complexity of a SOAP communication, is a good thing. It forces you to dive a little deeper and broaden your knowledge┬á – and it makes you appreciate the time people have invested so that you don’t have to deal with that complexity on a daily basis. You’re probably here because you don’t care for that complexity right now, so I’ll get straight onto the good stuff:

Python and SOAP are not good friends, unless you’re happy to hand-craft everything. GAE and SOAP are even worse friends. Finding a module to do web service calls in Python is one thing. Finding a module to do web service calls in Python, which actually works on GAE, is another entirely. Many of the “traditional” solutions are a no-go in the sandbox of a GAE application. Google’s engineers say it’s not supported. However, I eventually found something that DOES work: elementsoap (and a guide, thanks to Fredrik Lundh).

Using elementsoap in your own project

I’m assuming you know your way around GAE and Python already. If not, see the Python tutorial and the GAE Getting Started Guide. I use Eclipse with Pydev as my IDE, but there are many alternatives.

First things firstdownload elementsoap. I used the zip release of version 0.6 (the Windows installer didn’t work so great on Windows 7). Extract the archive and copy the included “elementsoap” directory into your project. An __init__.py is already included so that the directory will be recognized as a package.

Let’s assume for our purposes that you have a simple form which, when posted, makes the web service call using some form inputs as parameters. In the file containing the RequestHandler for that page you’d import the elementsoap modules as follows:

from elementsoap.ElementSOAP import *

 

The next step is to create a client wrapper class for the web service that you will be calling. elementsoap provides the SoapService base class for this purpose:

#SOAP client wrapper
class MyServiceClient(SoapService):
    url = "http://some.service.url/"
    #stuff goes here

Inside of your wrapper class you’ll want to create a function for the remote function that you are trying to call. This is where you build the SoapRequest and make a call to SoapService‘s call function. The action string and top-level SoapRequest object are required, while the structure of the SoapRequest (ie: its child SoapElement elements) depends on the target web service. This is where the service’s WSDL and/or a Fiddler/Wireshark log of the actual XML sent and received with a working test client become useful.

#SOAP client wrapper
class MyServiceClient(SoapService):
    url = "http://some.service.url/"

    def get_vouchers(self, network, sell_value, count):
        action = self.url + "#getVouchers"
        request = SoapRequest("{" + self.url + "}getVouchers")
        getVouchersIn = SoapElement(request, "getVouchersIn", "{" + self.url + "}getVouchersIn")
        SoapElement(getVouchersIn, "refno", "string", str(uuid.uuid4()))
        SoapElement(getVouchersIn, "network", "string", network)
        SoapElement(getVouchersIn, "sellvalue", "int", sell_value)
        SoapElement(getVouchersIn, "count", "int", count)
        response = self.call(action, request)

        return response.find("reply")

The get_vouchers function above was written for a web service which defines a function “getVouchers” and exposes a complex type “getVouchersIn” that encapsulates the parameters to the service call (“refno”, “network”, “sellvalue” and “count”). Note the creation of a hierarchical “getVouchersIn” element using SoapElement‘s initializer. The first part of Fredrik’s guide has a simpler example where the request does not contain any complex types. You should definitely familiarize yourself with SoapElement and its initializer signature.

If you look at the return statement of the get_vouchers function above, you will notice that I’m not simply returning the response object. That’s because, when elementsoap receives the response from the service call, it basically takes the contents of the SOAP body element from the XML and gives it to you as a new object. In our case that was the element <reply> which, in turn, contained everything else. Once you have the outgoing call working I would again suggest that you use Wireshark or similar to determine what exactly you are receiving as a response.

You need to use the find function to pull out complex types, collections etc. – basically any elements with children. When a child element is a simple value type, you can extract it using findtext. Below is an example of a function that handles a form post, makes a web service call and displays the results (using Django). Note the use of find and findtext.

def post(self):
    def add_voucher_row(html, voucher):
        return html + '<tr><td>' + voucher.findtext("pin") + '</td><td>' + voucher.findtext("serial") + '</td><td>' + voucher.findtext("costprice") + '</td></tr>'

    form_network = self.request.get('form_network')
    form_amount = self.request.get('form_amount')
    form_num_vouchers = self.request.get('form_num_vouchers')

    voucher_service = VoucherService()
    response = voucher_service.get_vouchers(form_network, form_amount, form_num_vouchers)

    status = int(response.findtext('status'))
    errors = ''

    if status != 1:
        errors = 'Voucher purchase failed!'
    else:
        vouchers_html = '<table><tr><th>Voucher PIN</th><th>Serial No.</th><th>Cost Price</th></tr>'
        vouchers = response.find('vouchers')
        for voucher in vouchers:
            vouchers_html = add_voucher_row(vouchers_html, voucher)
        vouchers_html = vouchers_html + '</table>'

    template_values = {
        'header': 'Voucher Purchase Test Page',
        'errors': errors,
        'error_code': response.findtext("errorcode"),
        'message': response.findtext("message"),
        'balance': response.findtext("balance"),
        'vouchers': vouchers_html
        }

    path = os.path.join(os.path.dirname(__file__), 'template/purchase.html')
    self.response.out.write(template.render(path, template_values))

That’s it! Good luck and feel free to post with questions/comments.
Many thanks to the participants of this discussion, and any others I failed to remember. If you’re more interested in hosting a web service on GAE, this is a good starting point.

Update:
They’ve just anounced support for SOAP in version 1.4.2 of the GAE SDK. However it looks like it’s only Java for now; see http://code.google.com/appengine/articles/soap.html

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>