back next

Using the ElementTree Module to Generate SOAP Messages, Part 1: Talking to a Stock Quote Server

November 16, 2003 | Fredrik Lundh

Note: A distribution kit containing the source code for this article is available from the effbot.org downloads site (look for ElementSOAP).

The Simple Object Access Protocol (SOAP) is an XML-based protocol for “information exchange in distributed environments”. SOAP can be used in many different ways, but the most common approach is to use it for RPC requests over HTTP, where a client application sends a SOAP request message to a remote server, and the server returns a SOAP response message to the client.

A simple SOAP request can look something like this (somewhat simplified; namespace definitions not shown):

<soap:Envelope>
  <soap:Body>
    <this:method soap:encodingStyle="...">
      <argument xsd:type="xsi:string">value</argument>
      ...
    </this:method>
  </soap:Body>
</soap:Envelope>

The above request refers to a method named this:method (where the this prefix represents some namespace), and passes in a single string argument. The server might return a SOAP response looking something like:

<soap:Envelope>
  <soap:Body>
    <this:methodResponse soap:encodingStyle="...">
      <value xsd:type="xsi:float">result</value>
    </this:methodResponse>
  </soap:Body>
</soap:Envelope>

Talking to a Stock Quote Server #

In this element article, I’m going to use the ElementTree module to talk to a public SOAP service; the Delayed Stock Quote service from XMethods.

A complete description of this service is available as a WSDL file, which is a rather verbose XML format that contains everything you’ll ever need to know about this SOAP service, in a machine readable format. To make things a little bit easier, we’re going to work from an “RPC profile” that’s available from the service description page:

Method NamegetQuote
Endpoint URLhttp://66.28.98.121:9090/soap
SOAPActionurn:xmethods-delayed-quotes#getQuote
Method Namespace URIurn:xmethods-delayed-quotes
Input Parameterssymbol (string)
Output ParametersResult (float)

Translated to english, this tells you that to issue a getQuote request, you need to send an HTTP request to the given endpoint URL, include a SOAPAction field in the HTTP request header, and provide a request body named {urn:xmethods-delayed-quotes}getQuote which contains a single input parameter, symbol. If successful, the server will return a response body containing a Result value.

Building SOAP requests with the Element module is straightforward. Let’s start with some definitions and helper functions:

 
from elementtree.ElementTree import Element, SubElement, QName

# namespaces (SOAP 1.1)
NS_SOAP_ENV = "{http://schemas.xmlsoap.org/soap/envelope/}"
NS_XSI = "{http://www.w3.org/1999/XMLSchema-instance}"
NS_XSD = "{http://www.w3.org/1999/XMLSchema}"

def SoapRequest(method):
    # create a SOAP request element
    request = Element(method)
    request.set(
        NS_SOAP_ENV + "encodingStyle",
        "http://schemas.xmlsoap.org/soap/encoding/"
        )
    return request

def SoapElement(parent, name, type=None, text=None):
    # add a typed SOAP element to a request structure
    elem = SubElement(parent, name)
    if type:
        if not isinstance(type, QName):
            type = QName("http://www.w3.org/1999/XMLSchema", type)
        elem.set(NS_XSI + "type", type)
    elem.text = text
    return elem

The SoapRequest function creates a SOAP request element from the full method name (method namespace plus method name). The SoapElement function adds a typed element to a parent element. Note the use of the QName element class; this is used to tell the ElementTree module that the attribute value contains an XML namespace.

The next step is to write some glue code for the quote service. The following class handles calls to the getQuote method, and builds a request body which it passes to the call method on the parent class:

 
class QuoteService(SoapService):
    url = "http://66.28.98.121:9090/soap"
    def getQuote(self, symbol):
        action = "urn:xmethods-delayed-quotes#getQuote"
        request = SoapRequest("{urn:xmethods-delayed-quotes}getQuote")
        SoapElement(request, "symbol", "string", symbol)
        response = self.call(action, request)
        return float(response.findtext("Result"))

And here’s the parent:

 
from HTTPClient import HTTPClient
from elementtree.ElementTree import Element, SubElement, tostring

class SoapService:
    def __init__(self, url=None):
        self.__client = HTTPClient(url or self.url)
    def call(self, action, request):
        # build SOAP envelope
        envelope = Element(NS_SOAP_ENV + "Envelope")
        body = SubElement(envelope, NS_SOAP_ENV + "Body")
        body.append(request)
        # call the server
        response = self.__client.do_request(
            tostring(envelope),
            extra_headers=[("SOAPAction", action)]
            )
        return response.getroot().find(body.tag)[0]

The call method wraps the request element in SOAP Envelope and Body elements, and uses the HTTPClient library to send the request to the server. The method then extracts the response element from the returned body, and returns it to the calling application.

Finally, here’s a snippet that uses the QuoteService to fetch the current stock price for Red Hat (LNUX):

>>> q = QuoteService()
>>> q.getQuote("LNUX")
4.78
 

A Django site. rendered by a django application. hosted by webfaction.