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 Name getQuote Endpoint URL http://66.28.98.121:9090/soap SOAPAction urn:xmethods-delayed-quotes#getQuote Method Namespace URI urn:xmethods-delayed-quotes Input Parameters symbol (string) Output Parameters Result (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