You Can Never Have Too Many Stock Tickers!
Updated July 17, 2002 | July 13, 2002 | Fredrik Lundh
Any site containing material on web services (dead link) isn’t really complete without a stock ticker example. So here’s a really simple one, using a SOAP service provided by xmethods.com and my little XML-over-HTTP helper class.
Let’s start with the code:
from xmltoys import HTTPClient # "Delayed Stock Quote" service from # http://www.xmethods.com/ service_uri = "http://66.28.98.121:9090/soap" action = "urn:xmethods-delayed-quotes#getQuote" request = """\ <env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema"> <env:Body> <ns1:getQuote xmlns:ns1="urn:xmethods-delayed-quotes" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <symbol xsi:type="xsd:string">%s</symbol> </ns1:getQuote> </env:Body> </env:Envelope> """ ticker_symbol = "LNUX" client = HTTPClient.HTTPClient(service_uri) response = client.do_request( request % ticker_symbol, extra_headers=[("SOAPAction", action)] ) if 0: from xmltoys import ElementTree ElementTree.dump(response) for elem in response.getiterator("Result"): print elem.text break
I’ve copied the SOAP action, a sample response body, and the host identifier directly from the method description available from the service provider (see their site for details).
You’ll need the xmltoys library and the http_xml helper to use this script. If you have, just run the script to see what it does.
Turning this into a real ticker is left as an exercise.
:::
The SOAP request body contains an Envelope element, which contains a Body subelement holding the request. This is similar to XML-RPC’s methodCall and params elements.
To see what the server returns, change the if-statement before the ElementTree.dump call to “if 1:”, and run the script. This time, it will produce something like (slightly edited, for readability):
<ns0:Envelope ns0:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"> <ns0:Body> <ns1:getQuoteResponse xmlns:ns1="urn:xmethods-delayed-quotes"> <Result ns2:type="xsd:float" xmlns:ns2="http://www.w3.org/1999/XMLSchema-instance"> 0.85 </Result> </ns1:getQuoteResponse> </ns0:Body> </ns0:Envelope>
In this case, we can ignore the namespace stuff (ns prefixes and xmlns attributes) and the Element and Body elements as well; the return value we’re looking for is returned as the text content of an undecorated element called Result. The getiterator method returns an iterator matching all such elements, and we print the text attribute for the first one.
Note that you don’t really need to parse XML to find the response in this case. If you remove all tags, all that’s left is whitespace and the value we’re looking for. If we get the raw response instead of a tree, we can get rid of the junk with a simple RE substitution: re.sub(“<[^>]*>|\s+”, “”, response). Makes you wonder why they didn’t just put up a service that accepted a HTTP GET request, and returned the value as a text/plain response…
By the way, when I write this, the script prints 0.85. Your mileage may vary.
Notes:
Today, it prints 0.66.
This example will be added to the next xmltoys release, as demostockticker.py.
The upcoming version of xmltoys can be used to build the SOAP request envelope. Here’s an outline (untested):
class NS: # helper to manufacture decorated tags def __init__(self, uri): self.uri = uri def __call__(self, tag): return "{%s}%s" % (self.uri, tag) # standard namespaces and stuff ns_env = NS("http://schemas.xmlsoap.org/soap/envelope/") schema_instance = NS("http://www.w3.org/1999/XMLSchema-instance") schema_data = NS("http://www.w3.org/1999/XMLSchema") soap_encoding_uri = "http://schemas.xmlsoap.org/soap/encoding/" def marshal_request(method_uri, method_tag, params, encoding_uri=soap_encoding_uri): envelope = Element(ns_env("Envelope")) body = SubElement(envelope, ns_env("Body")) method = SubElement( body, NS(method_uri)(method_tag), **{ns_env("encodingStyle"): encoding_uri} ) for param in params: method.append(param) return envelope params = Element("") # container element # FIXME: add type annotations (not required by this service) SubElement(params, "symbol").text = ticker_symbol request = marshal_request(method_uri, method_tag, params)
Move this to element-soap!