|
|
|
@@ -5801,660 +5801,6 @@ def fetch(source, etag=None, last_modified=None, agent=USER_AGENT):
|
|
|
|
|
|
|
|
|
|
<li>Supporting <a href="#oa.gzip" title="11.8. Handling compressed data">gzip compression</a> to reduce bandwidth even when data <em>has</em> changed.
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<div class=chapter>
|
|
|
|
|
<h2 id="soap">Chapter 12. <acronym>SOAP</acronym> Web Services</h2>
|
|
|
|
|
<p><a href="#oa">Chapter 11</a> focused on document-oriented web services over HTTP. The “input parameter” was the <acronym>URL</acronym>, and the “return value” was an actual XML document which it was your responsibility to parse.
|
|
|
|
|
<p>This chapter will focus on <acronym>SOAP</acronym> web services, which take a more structured approach. Rather than dealing with HTTP requests and XML documents directly,
|
|
|
|
|
<acronym>SOAP</acronym> allows you to simulate calling functions that return native data types. As you will see, the illusion is almost perfect;
|
|
|
|
|
you can “call” a function through a <acronym>SOAP</acronym> library, with the standard Python calling syntax, and the function appears to return Python objects and values. But under the covers, the <acronym>SOAP</acronym> library has actually performed a complex transaction involving multiple XML documents and a remote server.
|
|
|
|
|
<p><acronym>SOAP</acronym> is a complex specification, and it is somewhat misleading to say that <acronym>SOAP</acronym> is all about calling remote functions. Some people would pipe up to add that <acronym>SOAP</acronym> allows for one-way asynchronous message passing, and document-oriented web services. And those people would be correct;
|
|
|
|
|
<acronym>SOAP</acronym> can be used that way, and in many different ways. But this chapter will focus on so-called “RPC-style” <acronym>SOAP</acronym> -- calling a remote function and getting results back.
|
|
|
|
|
<h2 id="soap.divein">12.1. Diving In</h2>
|
|
|
|
|
<p>You use Google, right? It's a popular search engine. Have you ever wished you could programmatically access Google search
|
|
|
|
|
results? Now you can. Here is a program to search Google from Python.
|
|
|
|
|
<div class=example><h3>Example 12.1. <code>search.py</code></h3><pre><code>from SOAPpy import WSDL
|
|
|
|
|
|
|
|
|
|
# you'll need to configure these two values;
|
|
|
|
|
# see http://www.google.com/apis/
|
|
|
|
|
WSDLFILE = '/path/to/copy/of/GoogleSearch.wsdl'
|
|
|
|
|
APIKEY = 'YOUR_GOOGLE_API_KEY'
|
|
|
|
|
|
|
|
|
|
_server = WSDL.Proxy(WSDLFILE)
|
|
|
|
|
def search(q):
|
|
|
|
|
"""Search Google and return list of {title, link, description}"""
|
|
|
|
|
results = _server.doGoogleSearch(
|
|
|
|
|
APIKEY, q, 0, 10, False, "", False, "", "utf-8", "utf-8")
|
|
|
|
|
return [{"title": r.title.encode("utf-8"),
|
|
|
|
|
"link": r.URL.encode("utf-8"),
|
|
|
|
|
"description": r.snippet.encode("utf-8")}
|
|
|
|
|
for r in results.resultElements]
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
import sys
|
|
|
|
|
for r in search(sys.argv[1])[:5]:
|
|
|
|
|
print r['title']
|
|
|
|
|
print r['link']
|
|
|
|
|
print r['description']
|
|
|
|
|
print</pre><p>You can import this as a module and use it from a larger program, or you can run the script from the command line. On the
|
|
|
|
|
command line, you give the search query as a command-line argument, and it prints out the URL, title, and description of the
|
|
|
|
|
top five Google search results.
|
|
|
|
|
<p>Here is the sample output for a search for the word “python”.
|
|
|
|
|
<div class=example><h3>Example 12.2. Sample Usage of <code>search.py</code></h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>C:\diveintopython3\common\py></samp> python search.py "python"
|
|
|
|
|
<samp><b>Python</b> Programming Language
|
|
|
|
|
http://www.python.org/
|
|
|
|
|
Home page for <b>Python</b>, an interpreted, interactive, object-oriented,
|
|
|
|
|
extensible<br> programming language. <b>...</b> <b>Python</b>
|
|
|
|
|
is OSI Certified Open Source: OSI Certified.
|
|
|
|
|
|
|
|
|
|
<b>Python</b> Documentation Index
|
|
|
|
|
http://www.python.org/doc/
|
|
|
|
|
<b>...</b> New-style classes (aka descrintro). Regular expressions. Database
|
|
|
|
|
API. Email Us.<br> docs@<b>python</b>.org. (c) 2004. <b>Python</b>
|
|
|
|
|
Software Foundation. <b>Python</b> Documentation. <b>...</b>
|
|
|
|
|
|
|
|
|
|
Download <b>Python</b> Software
|
|
|
|
|
http://www.python.org/download/
|
|
|
|
|
Download Standard <b>Python</b> Software. <b>Python</b> 2.3.3 is the
|
|
|
|
|
current production<br> version of <b>Python</b>. <b>...</b>
|
|
|
|
|
<b>Python</b> is OSI Certified Open Source:
|
|
|
|
|
|
|
|
|
|
Pythonline
|
|
|
|
|
http://www.pythonline.com/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dive Into <b>Python</b>
|
|
|
|
|
http://diveintopython3.org/
|
|
|
|
|
Dive Into <b>Python</b>. <b>Python</b> from novice to pro. Find:
|
|
|
|
|
<b>...</b> It is also available in multiple<br> languages. Read
|
|
|
|
|
Dive Into <b>Python</b>. This book is still being written. <b>...</b></span>
|
|
|
|
|
</pre><div class=itemizedlist>
|
|
|
|
|
<h3>Further Reading on <acronym>SOAP</acronym></h3>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><a href="http://www.xmethods.net/">http://www.xmethods.net/</a> is a repository of public access <acronym>SOAP</acronym> web services.
|
|
|
|
|
|
|
|
|
|
<li>The <a href="http://www.w3.org/TR/soap/"><acronym>SOAP</acronym> specification</a> is surprisingly readable, if you like that sort of thing.
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<h2 id="soap.install">12.2. Installing the SOAP Libraries</h2>
|
|
|
|
|
<p>Unlike the other code in this book, this chapter relies on libraries that do not come pre-installed with Python.
|
|
|
|
|
<p>Before you can dive into <acronym>SOAP</acronym> web services, you'll need to install three libraries: PyXML, fpconst, and SOAPpy.
|
|
|
|
|
<h3>12.2.1. Installing PyXML</h3>
|
|
|
|
|
<p>The first library you need is PyXML, an advanced set of <acronym>XML</acronym> libraries that provide more functionality than the built-in <acronym>XML</acronym> libraries we studied in <a href="#kgp">Chapter 9</a>.
|
|
|
|
|
<div class=procedure>
|
|
|
|
|
<h3>Procedure 12.1. </h3>
|
|
|
|
|
<p>Here is the procedure for installing PyXML:
|
|
|
|
|
<ol>
|
|
|
|
|
<li>
|
|
|
|
|
<p>Go to <a href="http://pyxml.sourceforge.net/">http://pyxml.sourceforge.net/</a>, click Downloads, and download the latest version for your operating system.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>If you are using Windows, there are several choices. Make sure to download the version of PyXML that matches the version of Python you are using.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Double-click the installer. If you download PyXML 0.8.3 for Windows and Python 2.3, the installer program will be <code>PyXML-0.8.3.win32-py2.3.exe</code>.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Step through the installer program.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>After the installation is complete, close the installer. There will not be any visible indication of success (no programs
|
|
|
|
|
installed on the Start Menu or shortcuts installed on the desktop). PyXML is simply a collection of <acronym>XML</acronym> libraries used by other programs.
|
|
|
|
|
|
|
|
|
|
</ol>
|
|
|
|
|
<p>To verify that you installed PyXML correctly, run your Python <acronym>IDE</acronym> and check the version of the <acronym>XML</acronym> libraries you have installed, as shown here.
|
|
|
|
|
<div class=example><h3>Example 12.3. Verifying PyXML Installation</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>import xml</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>xml.__version__</kbd>
|
|
|
|
|
'0.8.3'
|
|
|
|
|
</pre><p>This version number should match the version number of the PyXML installer program you downloaded and ran.
|
|
|
|
|
<h3>12.2.2. Installing fpconst</h3>
|
|
|
|
|
<p>The second library you need is fpconst, a set of constants and functions for working with IEEE754 double-precision special values. This provides support for the
|
|
|
|
|
special values Not-a-Number (NaN), Positive Infinity (Inf), and Negative Infinity (-Inf), which are part of the <acronym>SOAP</acronym> datatype specification.
|
|
|
|
|
<div class=procedure>
|
|
|
|
|
<h3>Procedure 12.2. </h3>
|
|
|
|
|
<p>Here is the procedure for installing fpconst:
|
|
|
|
|
<ol>
|
|
|
|
|
<li>
|
|
|
|
|
<p>Download the latest version of fpconst from <a href="http://www.analytics.washington.edu/statcomp/projects/rzope/fpconst/">http://www.analytics.washington.edu/statcomp/projects/rzope/fpconst/</a>.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>There are two downloads available, one in <code>.tar.gz</code> format, the other in <code>.zip</code> format. If you are using Windows, download the <code>.zip</code> file; otherwise, download the <code>.tar.gz</code> file.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Decompress the downloaded file. On Windows XP, you can right-click on the file and choose Extract All; on earlier versions
|
|
|
|
|
of Windows, you will need a third-party program such as WinZip. On Mac OS X, you can double-click the compressed file to decompress it with Stuffit Expander.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Open a command prompt and navigate to the directory where you decompressed the fpconst files.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Type <kbd>python setup.py install</kbd> to run the installation program.
|
|
|
|
|
|
|
|
|
|
</ol>
|
|
|
|
|
<p>To verify that you installed fpconst correctly, run your Python <acronym>IDE</acronym> and check the version number.
|
|
|
|
|
<div class=example><h3>Example 12.4. Verifying fpconst Installation</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>import fpconst</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>fpconst.__version__</kbd>
|
|
|
|
|
'0.6.0'
|
|
|
|
|
</pre><p>This version number should match the version number of the fpconst archive you downloaded and installed.
|
|
|
|
|
<h3>12.2.3. Installing SOAPpy</h3>
|
|
|
|
|
<p>The third and final requirement is the <acronym>SOAP</acronym> library itself: SOAPpy.
|
|
|
|
|
<div class=procedure>
|
|
|
|
|
<h3>Procedure 12.3. </h3>
|
|
|
|
|
<p>Here is the procedure for installing SOAPpy:
|
|
|
|
|
<ol>
|
|
|
|
|
<li>
|
|
|
|
|
<p>Go to <a href="http://pywebsvcs.sourceforge.net/">http://pywebsvcs.sourceforge.net/</a> and select Latest Official Release under the SOAPpy section.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>There are two downloads available. If you are using Windows, download the <code>.zip</code> file; otherwise, download the <code>.tar.gz</code> file.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Decompress the downloaded file, just as you did with fpconst.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Open a command prompt and navigate to the directory where you decompressed the SOAPpy files.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Type <kbd>python setup.py install</kbd> to run the installation program.
|
|
|
|
|
|
|
|
|
|
</ol>
|
|
|
|
|
<p>To verify that you installed SOAPpy correctly, run your Python <acronym>IDE</acronym> and check the version number.
|
|
|
|
|
<div class=example><h3>Example 12.5. Verifying SOAPpy Installation</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>import SOAPpy</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>SOAPpy.__version__</kbd>
|
|
|
|
|
'0.11.4'
|
|
|
|
|
</pre><p>This version number should match the version number of the SOAPpy archive you downloaded and installed.
|
|
|
|
|
<h2 id="soap.firststeps">12.3. First Steps with <acronym>SOAP</acronym></h2>
|
|
|
|
|
<p>The heart of <acronym>SOAP</acronym> is the ability to call remote functions. There are a number of public access <acronym>SOAP</acronym> servers that provide simple functions for demonstration purposes.
|
|
|
|
|
<p>The most popular public access <acronym>SOAP</acronym> server is <a href="http://www.xmethods.net/">http://www.xmethods.net/</a>. This example uses a demonstration function that takes a United States zip code and returns the current temperature in that
|
|
|
|
|
region.
|
|
|
|
|
<div class=example><h3>Example 12.6. Getting the Current Temperature</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import SOAPProxy</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>namespace = 'urn:xmethods-Temperature'</kbd> <span>②</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = SOAPProxy(url, namespace)</kbd> <span>③</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.getTemp('27502')</kbd> <span>④</span>
|
|
|
|
|
80.0
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>You access the remote <acronym>SOAP</acronym> server through a proxy class, <code>SOAPProxy</code>. The proxy handles all the internals of <acronym>SOAP</acronym> for you, including creating the XML request document out of the function name and argument list, sending the request over
|
|
|
|
|
HTTP to the remote <acronym>SOAP</acronym> server, parsing the XML response document, and creating native Python values to return. You'll see what these XML documents look like in the next section.
|
|
|
|
|
<li>Every <acronym>SOAP</acronym> service has a <acronym>URL</acronym> which handles all the requests. The same <acronym>URL</acronym> is used for all function calls. This particular service only has a single function, but later in this chapter you'll see
|
|
|
|
|
examples of the Google <acronym>API</acronym>, which has several functions. The service <acronym>URL</acronym> is shared by all functions.Each <acronym>SOAP</acronym> service also has a namespace, which is defined by the server and is completely arbitrary. It's simply part of the configuration
|
|
|
|
|
required to call <acronym>SOAP</acronym> methods. It allows the server to share a single service <acronym>URL</acronym> and route requests between several unrelated services. It's like dividing Python modules into <a href="#kgp.packages" title="9.2. Packages">packages</a>.
|
|
|
|
|
<li>You're creating the <code>SOAPProxy</code> with the service <acronym>URL</acronym> and the service namespace. This doesn't make any connection to the <acronym>SOAP</acronym> server; it simply creates a local Python object.
|
|
|
|
|
<li>Now with everything configured properly, you can actually call remote <acronym>SOAP</acronym> methods as if they were local functions. You pass arguments just like a normal function, and you get a return value just
|
|
|
|
|
like a normal function. But under the covers, there's a heck of a lot going on.
|
|
|
|
|
<p>Let's peek under those covers.
|
|
|
|
|
<h2 id="soap.debug">12.4. Debugging <acronym>SOAP</acronym> Web Services</h2>
|
|
|
|
|
<p>The <acronym>SOAP</acronym> libraries provide an easy way to see what's going on behind the scenes.
|
|
|
|
|
<p>Turning on debugging is a simple matter of setting two flags in the <code>SOAPProxy</code>'s configuration.
|
|
|
|
|
<div class=example><h3>Example 12.7. Debugging <acronym>SOAP</acronym> Web Services</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import SOAPProxy</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>n = 'urn:xmethods-Temperature'</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = SOAPProxy(url, namespace=n)</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.config.dumpSOAPOut = 1</kbd> <span>②</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.config.dumpSOAPIn = 1</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>temperature = server.getTemp('27502')</kbd> <span>③</span>
|
|
|
|
|
<samp>*** Outgoing SOAP ******************************************************
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
|
|
|
|
|
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
|
|
|
|
|
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
|
|
|
|
|
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
|
|
|
|
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
|
|
|
|
|
<SOAP-ENV:Body>
|
|
|
|
|
<ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1">
|
|
|
|
|
<v1 xsi:type="xsd:string">27502</v1>
|
|
|
|
|
</ns1:getTemp>
|
|
|
|
|
</SOAP-ENV:Body>
|
|
|
|
|
</SOAP-ENV:Envelope>
|
|
|
|
|
************************************************************************
|
|
|
|
|
*** Incoming SOAP ******************************************************
|
|
|
|
|
<?xml version='1.0' encoding='UTF-8'?>
|
|
|
|
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
|
|
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
|
|
|
<SOAP-ENV:Body>
|
|
|
|
|
<ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature"
|
|
|
|
|
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
|
|
|
<return xsi:type="xsd:float">80.0</return>
|
|
|
|
|
</ns1:getTempResponse>
|
|
|
|
|
|
|
|
|
|
</SOAP-ENV:Body>
|
|
|
|
|
</SOAP-ENV:Envelope>
|
|
|
|
|
************************************************************************
|
|
|
|
|
</samp>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>temperature</kbd>
|
|
|
|
|
80.0
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>First, create the <code>SOAPProxy</code> like normal, with the service <acronym>URL</acronym> and the namespace.
|
|
|
|
|
<li>Second, turn on debugging by setting <var>server.config.dumpSOAPIn</var> and <var>server.config.dumpSOAPOut</var>.
|
|
|
|
|
<li>Third, call the remote <acronym>SOAP</acronym> method as usual. The <acronym>SOAP</acronym> library will print out both the outgoing XML request document, and the incoming XML response document. This is all the hard
|
|
|
|
|
work that <code>SOAPProxy</code> is doing for you. Intimidating, isn't it? Let's break it down.
|
|
|
|
|
<p>Most of the XML request document that gets sent to the server is just boilerplate. Ignore all the namespace declarations;
|
|
|
|
|
they're going to be the same (or similar) for all <acronym>SOAP</acronym> calls. The heart of the “function call” is this fragment within the <code><Body></code> element:
|
|
|
|
|
<pre><code>
|
|
|
|
|
<ns1:getTemp <span>①</span>
|
|
|
|
|
xmlns:ns1="urn:xmethods-Temperature" <span>②</span>
|
|
|
|
|
SOAP-ENC:root="1">
|
|
|
|
|
<v1 xsi:type="xsd:string">27502</v1> <span>③</span>
|
|
|
|
|
</ns1:getTemp>
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>The element name is the function name, <code>getTemp</code>. <code>SOAPProxy</code> uses <a href="#kgp.handler" title="10.5. Creating separate handlers by node type"><code>getattr</code> as a dispatcher</a>. Instead of calling separate local methods based on the method name, it actually uses the method name to construct the XML
|
|
|
|
|
request document.
|
|
|
|
|
<li>The function's XML element is contained in a specific namespace, which is the namespace you specified when you created the
|
|
|
|
|
<code>SOAPProxy</code> object. Don't worry about the <code>SOAP-ENC:root</code>; that's boilerplate too.
|
|
|
|
|
<li>The arguments of the function also got translated into XML. <code>SOAPProxy</code> introspects each argument to determine its datatype (in this case it's a string). The argument datatype goes into the <code>xsi:type</code> attribute, followed by the actual string value.
|
|
|
|
|
<p>The XML return document is equally easy to understand, once you know what to ignore. Focus on this fragment within the <code><Body></code>:
|
|
|
|
|
<pre><code>
|
|
|
|
|
<ns1:getTempResponse <span>①</span>
|
|
|
|
|
xmlns:ns1="urn:xmethods-Temperature" <span>②</span>
|
|
|
|
|
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
|
|
|
<return xsi:type="xsd:float">80.0</return> <span>③</span>
|
|
|
|
|
</ns1:getTempResponse>
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>The server wraps the function return value within a <code><getTempResponse></code> element. By convention, this wrapper element is the name of the function, plus <code>Response</code>. But it could really be almost anything; the important thing that <code>SOAPProxy</code> notices is not the element name, but the namespace.
|
|
|
|
|
<li>The server returns the response in the same namespace we used in the request, the same namespace we specified when we first
|
|
|
|
|
create the <code>SOAPProxy</code>. Later in this chapter we'll see what happens if you forget to specify the namespace when creating the <code>SOAPProxy</code>.
|
|
|
|
|
<li>The return value is specified, along with its datatype (it's a float). <code>SOAPProxy</code> uses this explicit datatype to create a Python object of the correct native datatype and return it.
|
|
|
|
|
<h2 id="soap.wsdl">12.5. Introducing <acronym>WSDL</acronym></h2>
|
|
|
|
|
<p>The <code>SOAPProxy</code> class proxies local method calls and transparently turns then into invocations of remote <acronym>SOAP</acronym> methods. As you've seen, this is a lot of work, and <code>SOAPProxy</code> does it quickly and transparently. What it doesn't do is provide any means of method introspection.
|
|
|
|
|
<p>Consider this: the previous two sections showed an example of calling a simple remote <acronym>SOAP</acronym> method with one argument and one return value, both of simple data types. This required knowing, and keeping track of, the
|
|
|
|
|
service <acronym>URL</acronym>, the service namespace, the function name, the number of arguments, and the datatype of each argument. If any of these is
|
|
|
|
|
missing or wrong, the whole thing falls apart.
|
|
|
|
|
<p>That shouldn't come as a big surprise. If I wanted to call a local function, I would need to know what package or module
|
|
|
|
|
it was in (the equivalent of service <acronym>URL</acronym> and namespace). I would need to know the correct function name and the correct number of arguments. Python deftly handles datatyping without explicit types, but I would still need to know how many argument to pass, and how many
|
|
|
|
|
return values to expect.
|
|
|
|
|
<p>The big difference is introspection. As you saw in <a href="#apihelper">Chapter 4</a>, Python excels at letting you discover things about modules and functions at runtime. You can list the available functions within
|
|
|
|
|
a module, and with a little work, drill down to individual function declarations and arguments.
|
|
|
|
|
<p><acronym>WSDL</acronym> lets you do that with <acronym>SOAP</acronym> web services. <acronym>WSDL</acronym> stands for “Web Services Description Language”. Although designed to be flexible enough to describe many types of web services, it is most often used to describe <acronym>SOAP</acronym> web services.
|
|
|
|
|
<p>A <acronym>WSDL</acronym> file is just that: a file. More specifically, it's an XML file. It usually lives on the same server you use to access the
|
|
|
|
|
<acronym>SOAP</acronym> web services it describes, although there's nothing special about it. Later in this chapter, we'll download the <acronym>WSDL</acronym> file for the Google API and use it locally. That doesn't mean we're calling Google locally; the <acronym>WSDL</acronym> file still describes the remote functions sitting on Google's server.
|
|
|
|
|
<p>A <acronym>WSDL</acronym> file contains a description of everything involved in calling a <acronym>SOAP</acronym> web service:
|
|
|
|
|
<div class=itemizedlist>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>The service <acronym>URL</acronym> and namespace
|
|
|
|
|
|
|
|
|
|
<li>The type of web service (probably function calls using <acronym>SOAP</acronym>, although as I mentioned, <acronym>WSDL</acronym> is flexible enough to describe a wide variety of web services)
|
|
|
|
|
|
|
|
|
|
<li>The list of available functions
|
|
|
|
|
<li>The arguments for each function
|
|
|
|
|
<li>The datatype of each argument
|
|
|
|
|
<li>The return values of each function, and the datatype of each return value
|
|
|
|
|
</ul>
|
|
|
|
|
<p>In other words, a <acronym>WSDL</acronym> file tells you everything you need to know to be able to call a <acronym>SOAP</acronym> web service.
|
|
|
|
|
<h2 id="soap.introspection">12.6. Introspecting <acronym>SOAP</acronym> Web Services with <acronym>WSDL</acronym></h2>
|
|
|
|
|
<p>Like many things in the web services arena, <acronym>WSDL</acronym> has a long and checkered history, full of political strife and intrigue. I will skip over this history entirely, since it
|
|
|
|
|
bores me to tears. There were other standards that tried to do similar things, but <acronym>WSDL</acronym> won, so let's learn how to use it.
|
|
|
|
|
<p>The most fundamental thing that <acronym>WSDL</acronym> allows you to do is discover the available methods offered by a <acronym>SOAP</acronym> server.
|
|
|
|
|
<div class=example><h3>Example 12.8. Discovering The Available Methods</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import WSDL</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl')</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = WSDL.Proxy(wsdlFile)</kbd> <span>②</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.methods.keys()</kbd> <span>③</span>
|
|
|
|
|
[u'getTemp']
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>SOAPpy includes a <acronym>WSDL</acronym> parser. At the time of this writing, it was labeled as being in the early stages of development, but I had no problem parsing
|
|
|
|
|
any of the <acronym>WSDL</acronym> files I tried.
|
|
|
|
|
<li>To use a <acronym>WSDL</acronym> file, you again use a proxy class, <code>WSDL.Proxy</code>, which takes a single argument: the <acronym>WSDL</acronym> file. Note that in this case you are passing in the <acronym>URL</acronym> of a <acronym>WSDL</acronym> file stored on the remote server, but the proxy class works just as well with a local copy of the <acronym>WSDL</acronym> file. The act of creating the <acronym>WSDL</acronym> proxy will download the <acronym>WSDL</acronym> file and parse it, so it there are any errors in the <acronym>WSDL</acronym> file (or it can't be fetched due to networking problems), you'll know about it immediately.
|
|
|
|
|
<li>The <acronym>WSDL</acronym> proxy class exposes the available functions as a Python dictionary, <var>server.methods</var>. So getting the list of available methods is as simple as calling the dictionary method <code>keys()</code>.
|
|
|
|
|
<p>Okay, so you know that this <acronym>SOAP</acronym> server offers a single method: <code>getTemp</code>. But how do you call it? The <acronym>WSDL</acronym> proxy object can tell you that too.
|
|
|
|
|
<div class=example><h3>Example 12.9. Discovering A Method's Arguments</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo = server.methods['getTemp']</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo.inparams</kbd> <span>②</span>
|
|
|
|
|
[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AD0>]
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo.inparams[0].name</kbd> <span>③</span>
|
|
|
|
|
u'zipcode'
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo.inparams[0].type</kbd> <span>④</span>
|
|
|
|
|
(u'http://www.w3.org/2001/XMLSchema', u'string')
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>The <var>server.methods</var> dictionary is filled with a SOAPpy-specific structure called <code>CallInfo</code>. A <code>CallInfo</code> object contains information about one specific function, including the function arguments.
|
|
|
|
|
<li>The function arguments are stored in <var>callInfo.inparams</var>, which is a Python list of <code>ParameterInfo</code> objects that hold information about each parameter.
|
|
|
|
|
<li>Each <code>ParameterInfo</code> object contains a <var>name</var> attribute, which is the argument name. You are not required to know the argument name to call the function through <acronym>SOAP</acronym>, but <acronym>SOAP</acronym> does support calling functions with named arguments (just like Python), and <code>WSDL.Proxy</code> will correctly handle mapping named arguments to the remote function if you choose to use them.
|
|
|
|
|
<li>Each parameter is also explicitly typed, using datatypes defined in XML Schema. You saw this in the wire trace in the previous
|
|
|
|
|
section; the XML Schema namespace was part of the “boilerplate” I told you to ignore. For our purposes here, you may continue to ignore it. The <var>zipcode</var> parameter is a string, and if you pass in a Python string to the <code>WSDL.Proxy</code> object, it will map it correctly and send it to the server.
|
|
|
|
|
<p><acronym>WSDL</acronym> also lets you introspect into a function's return values.
|
|
|
|
|
<div class=example><h3>Example 12.10. Discovering A Method's Return Values</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo.outparams</kbd> <span>①</span>
|
|
|
|
|
[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AF8>]
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo.outparams[0].name</kbd> <span>②</span>
|
|
|
|
|
u'return'
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo.outparams[0].type</kbd>
|
|
|
|
|
(u'http://www.w3.org/2001/XMLSchema', u'float')
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>The adjunct to <var>callInfo.inparams</var> for function arguments is <var>callInfo.outparams</var> for return value. It is also a list, because functions called through <acronym>SOAP</acronym> can return multiple values, just like Python functions.
|
|
|
|
|
<li>Each <code>ParameterInfo</code> object contains <var>name</var> and <var>type</var>. This function returns a single value, named <var>return</var>, which is a float.
|
|
|
|
|
<p>Let's put it all together, and call a <acronym>SOAP</acronym> web service through a <acronym>WSDL</acronym> proxy.
|
|
|
|
|
<div class=example><h3>Example 12.11. Calling A Web Service Through A <acronym>WSDL</acronym> Proxy</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import WSDL</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl')</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = WSDL.Proxy(wsdlFile)</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.getTemp('90210')</kbd> <span>②</span>
|
|
|
|
|
66.0
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.soapproxy.config.dumpSOAPOut = 1</kbd> <span>③</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.soapproxy.config.dumpSOAPIn = 1</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>temperature = server.getTemp('90210')</kbd>
|
|
|
|
|
<samp>*** Outgoing SOAP ******************************************************
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
|
|
|
|
|
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
|
|
|
|
|
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
|
|
|
|
|
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
|
|
|
|
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
|
|
|
|
|
<SOAP-ENV:Body>
|
|
|
|
|
<ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" SOAP-ENC:root="1">
|
|
|
|
|
<v1 xsi:type="xsd:string">90210</v1>
|
|
|
|
|
</ns1:getTemp>
|
|
|
|
|
</SOAP-ENV:Body>
|
|
|
|
|
</SOAP-ENV:Envelope>
|
|
|
|
|
************************************************************************
|
|
|
|
|
*** Incoming SOAP ******************************************************
|
|
|
|
|
<?xml version='1.0' encoding='UTF-8'?>
|
|
|
|
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
|
|
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
|
|
|
<SOAP-ENV:Body>
|
|
|
|
|
<ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature"
|
|
|
|
|
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
|
|
|
<return xsi:type="xsd:float">66.0</return>
|
|
|
|
|
</ns1:getTempResponse>
|
|
|
|
|
|
|
|
|
|
</SOAP-ENV:Body>
|
|
|
|
|
</SOAP-ENV:Envelope>
|
|
|
|
|
************************************************************************
|
|
|
|
|
</samp>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>temperature</kbd>
|
|
|
|
|
66.0
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>The configuration is simpler than calling the <acronym>SOAP</acronym> service directly, since the <acronym>WSDL</acronym> file contains the both service <acronym>URL</acronym> and namespace you need to call the service. Creating the <code>WSDL.Proxy</code> object downloads the <acronym>WSDL</acronym> file, parses it, and configures a <code>SOAPProxy</code> object that it uses to call the actual <acronym>SOAP</acronym> web service.
|
|
|
|
|
<li>Once the <code>WSDL.Proxy</code> object is created, you can call a function as easily as you did with the <code>SOAPProxy</code> object. This is not surprising; the <code>WSDL.Proxy</code> is just a wrapper around the <code>SOAPProxy</code> with some introspection methods added, so the syntax for calling functions is the same.
|
|
|
|
|
<li>You can access the <code>WSDL.Proxy</code>'s <code>SOAPProxy</code> with <var>server.soapproxy</var>. This is useful to turning on debugging, so that when you can call functions through the <acronym>WSDL</acronym> proxy, its <code>SOAPProxy</code> will dump the outgoing and incoming XML documents that are going over the wire.
|
|
|
|
|
<h2 id="soap.google">12.7. Searching Google</h2>
|
|
|
|
|
<p>Let's finally turn to the sample code that you saw that the beginning of this chapter, which does something more useful and
|
|
|
|
|
exciting than get the current temperature.
|
|
|
|
|
<p>Google provides a <acronym>SOAP</acronym> <acronym>API</acronym> for programmatically accessing Google search results. To use it, you will need to sign up for Google Web Services.
|
|
|
|
|
<div class=procedure>
|
|
|
|
|
<h3>Procedure 12.4. Signing Up for Google Web Services</h3>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>
|
|
|
|
|
<p>Go to <a href="http://www.google.com/apis/">http://www.google.com/apis/</a> and create a Google account. This requires only an email address. After you sign up you will receive your Google API license
|
|
|
|
|
key by email. You will need this key to pass as a parameter whenever you call Google's search functions.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Also on <a href="http://www.google.com/apis/">http://www.google.com/apis/</a>, download the Google Web APIs developer kit. This includes some sample code in several programming languages (but not Python), and more importantly, it includes the <acronym>WSDL</acronym> file.
|
|
|
|
|
|
|
|
|
|
<li>
|
|
|
|
|
<p>Decompress the developer kit file and find <code>GoogleSearch.wsdl</code>. Copy this file to some permanent location on your local drive. You will need it later in this chapter.
|
|
|
|
|
|
|
|
|
|
</ol>
|
|
|
|
|
<p>Once you have your developer key and your Google <acronym>WSDL</acronym> file in a known place, you can start poking around with Google Web Services.
|
|
|
|
|
<div class=example><h3>Example 12.12. Introspecting Google Web Services</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import WSDL</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = WSDL.Proxy('/path/to/your/GoogleSearch.wsdl')</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.methods.keys()</kbd> <span>②</span>
|
|
|
|
|
[u'doGoogleSearch', u'doGetCachedPage', u'doSpellingSuggestion']
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>callInfo = server.methods['doGoogleSearch']</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>for arg in callInfo.inparams:</kbd> <span>③</span>
|
|
|
|
|
<samp class=prompt>... </samp>print arg.name.ljust(15), arg.type
|
|
|
|
|
<samp>key (u'http://www.w3.org/2001/XMLSchema', u'string')
|
|
|
|
|
q (u'http://www.w3.org/2001/XMLSchema', u'string')
|
|
|
|
|
start (u'http://www.w3.org/2001/XMLSchema', u'int')
|
|
|
|
|
maxResults (u'http://www.w3.org/2001/XMLSchema', u'int')
|
|
|
|
|
filter (u'http://www.w3.org/2001/XMLSchema', u'boolean')
|
|
|
|
|
restrict (u'http://www.w3.org/2001/XMLSchema', u'string')
|
|
|
|
|
safeSearch (u'http://www.w3.org/2001/XMLSchema', u'boolean')
|
|
|
|
|
lr (u'http://www.w3.org/2001/XMLSchema', u'string')
|
|
|
|
|
ie (u'http://www.w3.org/2001/XMLSchema', u'string')
|
|
|
|
|
oe (u'http://www.w3.org/2001/XMLSchema', u'string')</span>
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>Getting started with Google web services is easy: just create a <code>WSDL.Proxy</code> object and point it at your local copy of Google's <acronym>WSDL</acronym> file.
|
|
|
|
|
<li>According to the <acronym>WSDL</acronym> file, Google offers three functions: <code>doGoogleSearch</code>, <code>doGetCachedPage</code>, and <code>doSpellingSuggestion</code>. These do exactly what they sound like: perform a Google search and return the results programmatically, get access to the
|
|
|
|
|
cached version of a page from the last time Google saw it, and offer spelling suggestions for commonly misspelled search words.
|
|
|
|
|
<li>The <code>doGoogleSearch</code> function takes a number of parameters of various types. Note that while the <acronym>WSDL</acronym> file can tell you what the arguments are called and what datatype they are, it can't tell you what they mean or how to use
|
|
|
|
|
them. It could theoretically tell you the acceptable range of values for each parameter, if only specific values were allowed,
|
|
|
|
|
but Google's <acronym>WSDL</acronym> file is not that detailed. <code>WSDL.Proxy</code> can't work magic; it can only give you the information provided in the <acronym>WSDL</acronym> file.
|
|
|
|
|
<p>Here is a brief synopsis of all the parameters to the <code>doGoogleSearch</code> function:
|
|
|
|
|
<div class=itemizedlist>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><var>key</var> - Your Google API key, which you received when you signed up for Google web services.
|
|
|
|
|
|
|
|
|
|
<li><var>q</var> - The search word or phrase you're looking for. The syntax is exactly the same as Google's web form, so if you know any
|
|
|
|
|
advanced search syntax or tricks, they all work here as well.
|
|
|
|
|
|
|
|
|
|
<li><var>start</var> - The index of the result to start on. Like the interactive web version of Google, this function returns 10 results at a
|
|
|
|
|
time. If you wanted to get the second “page” of results, you would set <var>start</var> to 10.
|
|
|
|
|
|
|
|
|
|
<li><var>maxResults</var> - The number of results to return. Currently capped at 10, although you can specify fewer if you are only interested in
|
|
|
|
|
a few results and want to save a little bandwidth.
|
|
|
|
|
|
|
|
|
|
<li><var>filter</var> - If <code>True</code>, Google will filter out duplicate pages from the results.
|
|
|
|
|
|
|
|
|
|
<li><var>restrict</var> - Set this to <code>country</code> plus a country code to get results only from a particular country. Example: <code>countryUK</code> to search pages in the United Kingdom. You can also specify <code>linux</code>, <code>mac</code>, or <code>bsd</code> to search a Google-defined set of technical sites, or <code>unclesam</code> to search sites about the United States government.
|
|
|
|
|
|
|
|
|
|
<li><var>safeSearch</var> - If <code>True</code>, Google will filter out porn sites.
|
|
|
|
|
|
|
|
|
|
<li><var>lr</var> (“language restrict”) - Set this to a language code to get results only in a particular language.
|
|
|
|
|
|
|
|
|
|
<li><var>ie</var> and <var>oe</var> (“input encoding” and “output encoding”) - Deprecated, both must be <code>utf-8</code>.
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<div class=example><h3>Example 12.13. Searching Google</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import WSDL</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = WSDL.Proxy('/path/to/your/GoogleSearch.wsdl')</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>key = 'YOUR_GOOGLE_API_KEY'</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results = server.doGoogleSearch(key, 'mark', 0, 10, False, "",</kbd>
|
|
|
|
|
<samp class=prompt>... </samp>False, "", "utf-8", "utf-8") <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>len(results.resultElements)</kbd><span>②</span>
|
|
|
|
|
10
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results.resultElements[0].URL</kbd> <span>③</span>
|
|
|
|
|
'http://diveintomark.org/'
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results.resultElements[0].title</kbd>
|
|
|
|
|
'dive into <b>mark</b>'
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>After setting up the <code>WSDL.Proxy</code> object, you can call <code>server.doGoogleSearch</code> with all ten parameters. Remember to use your own Google API key that you received when you signed up for Google web services.
|
|
|
|
|
<li>There's a lot of information returned, but let's look at the actual search results first. They're stored in <var>results.resultElements</var>, and you can access them just like a normal Python list.
|
|
|
|
|
<li>Each element in the <var>resultElements</var> is an object that has a <var>URL</var>, <var>title</var>, <var>snippet</var>, and other useful attributes. At this point you can use normal Python introspection techniques like <kbd>dir(results.resultElements[0])</kbd> to see the available attributes. Or you can introspect through the <acronym>WSDL</acronym> proxy object and look through the function's <var>outparams</var>. Each technique will give you the same information.
|
|
|
|
|
<p>The <var>results</var> object contains more than the actual search results. It also contains information about the search itself, such as how long
|
|
|
|
|
it took and how many results were found (even though only 10 were returned). The Google web interface shows this information,
|
|
|
|
|
and you can access it programmatically too.
|
|
|
|
|
<div class=example><h3>Example 12.14. Accessing Secondary Information From Google</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results.searchTime</kbd> <span>①</span>
|
|
|
|
|
0.224919
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results.estimatedTotalResultsCount</kbd> <span>②</span>
|
|
|
|
|
29800000
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results.directoryCategories</kbd> <span>③</span>
|
|
|
|
|
<samp>[<SOAPpy.Types.structType item at 14367400>:
|
|
|
|
|
{'fullViewableName':
|
|
|
|
|
'Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark',
|
|
|
|
|
'specialEncoding': ''}]</samp>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results.directoryCategories[0].fullViewableName</kbd>
|
|
|
|
|
'Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark'
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>This search took 0.224919 seconds. That does not include the time spent sending and receiving the actual <acronym>SOAP</acronym> XML documents. It's just the time that Google spent processing your request once it received it.
|
|
|
|
|
<li>In total, there were approximately 30 million results. You can access them 10 at a time by changing the <var>start</var> parameter and calling <code>server.doGoogleSearch</code> again.
|
|
|
|
|
<li>For some queries, Google also returns a list of related categories in the <a href="http://directory.google.com/">Google Directory</a>. You can append these URLs to <a href="http://directory.google.com/">http://directory.google.com/</a> to construct the link to the directory category page.
|
|
|
|
|
<h2 id="soap.troubleshooting">12.8. Troubleshooting <acronym>SOAP</acronym> Web Services</h2>
|
|
|
|
|
<p>Of course, the world of <acronym>SOAP</acronym> web services is not all happiness and light. Sometimes things go wrong.
|
|
|
|
|
<p>As you've seen throughout this chapter, <acronym>SOAP</acronym> involves several layers. There's the HTTP layer, since <acronym>SOAP</acronym> is sending XML documents to, and receiving XML documents from, an HTTP server. So all the debugging techniques you learned
|
|
|
|
|
in <a href="#oa" title="Chapter 11. HTTP Web Services">Chapter 11, <i>HTTP Web Services</i></a> come into play here. You can <kbd>import httplib</kbd> and then set <kbd>httplib.HTTPConnection.debuglevel = 1</kbd> to see the underlying HTTP traffic.
|
|
|
|
|
<p>Beyond the underlying HTTP layer, there are a number of things that can go wrong. SOAPpy does an admirable job hiding the <acronym>SOAP</acronym> syntax from you, but that also means it can be difficult to determine where the problem is when things don't work.
|
|
|
|
|
<p>Here are a few examples of common mistakes that I've made in using <acronym>SOAP</acronym> web services, and the errors they generated.
|
|
|
|
|
<div class=example><h3>Example 12.15. Calling a Method With an Incorrectly Configured Proxy</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import SOAPProxy</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = SOAPProxy(url)</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server.getTemp('27502')</kbd> <span>②</span>
|
|
|
|
|
<samp class=traceback><Fault SOAP-ENV:Server.BadTargetObjectURI:
|
|
|
|
|
Unable to determine object id from call: is the method element namespaced?>
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in ?
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__
|
|
|
|
|
return self.__r_call(*args, **kw)
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call
|
|
|
|
|
self.__hd, self.__ma)
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call
|
|
|
|
|
raise p
|
|
|
|
|
SOAPpy.Types.faultType: <Fault SOAP-ENV:Server.BadTargetObjectURI:
|
|
|
|
|
Unable to determine object id from call: is the method element namespaced?></span>
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>Did you spot the mistake? You're creating a <code>SOAPProxy</code> manually, and you've correctly specified the service <acronym>URL</acronym>, but you haven't specified the namespace. Since multiple services may be routed through the same service <acronym>URL</acronym>, the namespace is essential to determine which service you're trying to talk to, and therefore which method you're really
|
|
|
|
|
calling.
|
|
|
|
|
<li>The server responds by sending a <acronym>SOAP</acronym> Fault, which SOAPpy turns into a Python exception of type <code>SOAPpy.Types.faultType</code>. All errors returned from any <acronym>SOAP</acronym> server will always be <acronym>SOAP</acronym> Faults, so you can easily catch this exception. In this case, the human-readable part of the <acronym>SOAP</acronym> Fault gives a clue to the problem: the method element is not namespaced, because the original <code>SOAPProxy</code> object was not configured with a service namespace.
|
|
|
|
|
<p>Misconfiguring the basic elements of the <acronym>SOAP</acronym> service is one of the problems that <acronym>WSDL</acronym> aims to solve. The <acronym>WSDL</acronym> file contains the service <acronym>URL</acronym> and namespace, so you can't get it wrong. Of course, there are still other things you can get wrong.
|
|
|
|
|
<div class=example><h3>Example 12.16. Calling a Method With the Wrong Arguments</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = WSDL.Proxy(wsdlFile)</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>temperature = server.getTemp(27502)</kbd> <span>①</span>
|
|
|
|
|
<samp class=traceback><Fault SOAP-ENV:Server: Exception while handling service request:
|
|
|
|
|
services.temperature.TempService.getTemp(int) -- no signature match> <span>②</span>
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in ?
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__
|
|
|
|
|
return self.__r_call(*args, **kw)
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call
|
|
|
|
|
self.__hd, self.__ma)
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call
|
|
|
|
|
raise p
|
|
|
|
|
SOAPpy.Types.faultType: <Fault SOAP-ENV:Server: Exception while handling service request:
|
|
|
|
|
services.temperature.TempService.getTemp(int) -- no signature match></span>
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>Did you spot the mistake? It's a subtle one: you're calling <code>server.getTemp</code> with an integer instead of a string. As you saw from introspecting the <acronym>WSDL</acronym> file, the <code>getTemp()</code> <acronym>SOAP</acronym> function takes a single argument, <var>zipcode</var>, which must be a string. <code>WSDL.Proxy</code> will <em>not</em> coerce datatypes for you; you need to pass the exact datatypes that the server expects.
|
|
|
|
|
<li>Again, the server returns a <acronym>SOAP</acronym> Fault, and the human-readable part of the error gives a clue as to the problem: you're calling a <code>getTemp</code> function with an integer value, but there is no function defined with that name that takes an integer. In theory, <acronym>SOAP</acronym> allows you to <em>overload</em> functions, so you could have two functions in the same <acronym>SOAP</acronym> service with the same name and the same number of arguments, but the arguments were of different datatypes. This is why
|
|
|
|
|
it's important to match the datatypes exactly, and why <code>WSDL.Proxy</code> doesn't coerce datatypes for you. If it did, you could end up calling a completely different function! Good luck debugging
|
|
|
|
|
that one. It's much easier to be picky about datatypes and fail as quickly as possible if you get them wrong.
|
|
|
|
|
<p>It's also possible to write Python code that expects a different number of return values than the remote function actually returns.
|
|
|
|
|
<div class=example><h3>Example 12.17. Calling a Method and Expecting the Wrong Number of Return Values</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = WSDL.Proxy(wsdlFile)</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>(city, temperature) = server.getTemp(27502)</kbd> <span>①</span>
|
|
|
|
|
<samp class=traceback>Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in ?
|
|
|
|
|
TypeError: unpack non-sequence</span>
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>Did you spot the mistake? <code>server.getTemp</code> only returns one value, a float, but you've written code that assumes you're getting two values and trying to assign them
|
|
|
|
|
to two different variables. Note that this does not fail with a <acronym>SOAP</acronym> fault. As far as the remote server is concerned, nothing went wrong at all. The error only occurred <em>after</em> the <acronym>SOAP</acronym> transaction was complete, <code>WSDL.Proxy</code> returned a float, and your local Python interpreter tried to accomodate your request to split it into two different variables. Since the function only returned
|
|
|
|
|
one value, you get a Python exception trying to split it, not a <acronym>SOAP</acronym> Fault.
|
|
|
|
|
<p>What about Google's web service? The most common problem I've had with it is that I forget to set the application key properly.
|
|
|
|
|
<div class=example><h3>Example 12.18. Calling a Method With An Application-Specific Error</h3><pre class=screen>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>from SOAPpy import WSDL</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>server = WSDL.Proxy(r'/path/to/local/GoogleSearch.wsdl')</kbd>
|
|
|
|
|
<samp class=prompt>>>> </samp><kbd>results = server.doGoogleSearch('foo', 'mark', 0, 10, False, "",</kbd> <span>①</span>
|
|
|
|
|
<samp class=prompt>... </samp>False, "", "utf-8", "utf-8")
|
|
|
|
|
<samp class=traceback><Fault SOAP-ENV:Server: <span>②</span>
|
|
|
|
|
Exception from service object: Invalid authorization key: foo:
|
|
|
|
|
<SOAPpy.Types.structType detail at 14164616>:
|
|
|
|
|
{'stackTrace':
|
|
|
|
|
'com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo
|
|
|
|
|
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
|
|
|
|
|
QueryLimits.java:220)
|
|
|
|
|
at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127)
|
|
|
|
|
at com.google.soap.search.GoogleSearchService.doPublicMethodChecks(
|
|
|
|
|
GoogleSearchService.java:825)
|
|
|
|
|
at com.google.soap.search.GoogleSearchService.doGoogleSearch(
|
|
|
|
|
GoogleSearchService.java:121)
|
|
|
|
|
at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source)
|
|
|
|
|
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
|
|
|
|
|
at java.lang.reflect.Method.invoke(Unknown Source)
|
|
|
|
|
at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
|
|
|
|
|
at org.apache.soap.providers.RPCJavaProvider.invoke(
|
|
|
|
|
RPCJavaProvider.java:129)
|
|
|
|
|
at org.apache.soap.server.http.RPCRouterServlet.doPost(
|
|
|
|
|
RPCRouterServlet.java:288)
|
|
|
|
|
at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
|
|
|
|
|
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
|
|
|
|
|
at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237)
|
|
|
|
|
at com.google.gse.HttpConnection.run(HttpConnection.java:195)
|
|
|
|
|
at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201)
|
|
|
|
|
Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size.
|
|
|
|
|
at com.google.soap.search.UserKey.<init>(UserKey.java:59)
|
|
|
|
|
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
|
|
|
|
|
QueryLimits.java:217)
|
|
|
|
|
... 14 more
|
|
|
|
|
'}>
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in ?
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 453, in __call__
|
|
|
|
|
return self.__r_call(*args, **kw)
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 475, in __r_call
|
|
|
|
|
self.__hd, self.__ma)
|
|
|
|
|
File "c:\python23\Lib\site-packages\SOAPpy\Client.py", line 389, in __call
|
|
|
|
|
raise p
|
|
|
|
|
SOAPpy.Types.faultType: <Fault SOAP-ENV:Server: Exception from service object:
|
|
|
|
|
Invalid authorization key: foo:
|
|
|
|
|
<SOAPpy.Types.structType detail at 14164616>:
|
|
|
|
|
{'stackTrace':
|
|
|
|
|
'com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo
|
|
|
|
|
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
|
|
|
|
|
QueryLimits.java:220)
|
|
|
|
|
at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127)
|
|
|
|
|
at com.google.soap.search.GoogleSearchService.doPublicMethodChecks(
|
|
|
|
|
GoogleSearchService.java:825)
|
|
|
|
|
at com.google.soap.search.GoogleSearchService.doGoogleSearch(
|
|
|
|
|
GoogleSearchService.java:121)
|
|
|
|
|
at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source)
|
|
|
|
|
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
|
|
|
|
|
at java.lang.reflect.Method.invoke(Unknown Source)
|
|
|
|
|
at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
|
|
|
|
|
at org.apache.soap.providers.RPCJavaProvider.invoke(
|
|
|
|
|
RPCJavaProvider.java:129)
|
|
|
|
|
at org.apache.soap.server.http.RPCRouterServlet.doPost(
|
|
|
|
|
RPCRouterServlet.java:288)
|
|
|
|
|
at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
|
|
|
|
|
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
|
|
|
|
|
at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237)
|
|
|
|
|
at com.google.gse.HttpConnection.run(HttpConnection.java:195)
|
|
|
|
|
at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201)
|
|
|
|
|
Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size.
|
|
|
|
|
at com.google.soap.search.UserKey.<init>(UserKey.java:59)
|
|
|
|
|
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
|
|
|
|
|
QueryLimits.java:217)
|
|
|
|
|
... 14 more
|
|
|
|
|
'}></span>
|
|
|
|
|
</pre><div class=calloutlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>Can you spot the mistake? There's nothing wrong with the calling syntax, or the number of arguments, or the datatypes. The
|
|
|
|
|
problem is application-specific: the first argument is supposed to be my application key, but <code>foo</code> is not a valid Google key.
|
|
|
|
|
<li>The Google server responds with a <acronym>SOAP</acronym> Fault and an incredibly long error message, which includes a complete Java stack trace. Remember that <em>all</em> <acronym>SOAP</acronym> errors are signified by <acronym>SOAP</acronym> Faults: errors in configuration, errors in function arguments, and application-specific errors like this. Buried in there
|
|
|
|
|
somewhere is the crucial piece of information: <code>Invalid authorization key: foo</code>.
|
|
|
|
|
<div class=itemizedlist>
|
|
|
|
|
<h3>Further Reading on Troubleshooting <acronym>SOAP</acronym></h3>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><a href="http://www-106.ibm.com/developerworks/webservices/library/ws-pyth17.html">New developments for SOAPpy</a> steps through trying to connect to another <acronym>SOAP</acronym> service that doesn't quite work as advertised.
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<h2 id="soap.summary">12.9. Summary</h2>
|
|
|
|
|
<p><acronym>SOAP</acronym> web services are very complicated. The specification is very ambitious and tries to cover many different use cases for web
|
|
|
|
|
services. This chapter has touched on some of the simpler use cases.
|
|
|
|
|
<div class=highlights>
|
|
|
|
|
<p>Before diving into the next chapter, make sure you're comfortable doing all of these things:
|
|
|
|
|
<div class=itemizedlist>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>Connecting to a <acronym>SOAP</acronym> server and calling remote methods
|
|
|
|
|
|
|
|
|
|
<li>Loading a <acronym>WSDL</acronym> file and introspecting remote methods
|
|
|
|
|
|
|
|
|
|
<li>Debugging <acronym>SOAP</acronym> calls with wire traces
|
|
|
|
|
|
|
|
|
|
<li>Troubleshooting common <acronym>SOAP</acronym>-related errors
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<div class=chapter>
|
|
|
|
|
<h2 id="roman">Chapter 13. Unit Testing</h2>
|
|
|
|
@@ -6503,265 +5849,6 @@ numerals. You saw the mechanics of constructing and validating Roman numerals in
|
|
|
|
|
<li><a href="http://www.wilkiecollins.demon.co.uk/roman/front.htm">This site</a> has more on Roman numerals, including a fascinating <a href="http://www.wilkiecollins.demon.co.uk/roman/intro.htm">history</a> of how Romans and other civilizations really used them (short answer: haphazardly and inconsistently).
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<h2 id="roman.divein">13.2. Diving in</h2>
|
|
|
|
|
<p>Now that you've completely defined the behavior you expect from your conversion functions, you're going to do something a
|
|
|
|
|
little unexpected: you're going to write a test suite that puts these functions through their paces and makes sure that they
|
|
|
|
|
behave the way you want them to. You read that right: you're going to write code that tests code that you haven't written
|
|
|
|
|
yet.
|
|
|
|
|
<p>This is called unit testing, since the set of two conversion functions can be written and tested as a unit, separate from
|
|
|
|
|
any larger program they may become part of later. Python has a framework for unit testing, the appropriately-named <code>unittest</code> module.
|
|
|
|
|
<table id="note.unittest" class=note border="0" summary="">
|
|
|
|
|
|
|
|
|
|
<td rowspan="2" align="center" valign="top" width="1%"><img src="images/note.png" alt="Note" title="" width="24" height="24"><td colspan="2" align="left" valign="top" width="99%"><code>unittest</code> is included with Python 2.1 and later. Python 2.0 users can download it from <a href="http://pyunit.sourceforge.net/"><code>pyunit.sourceforge.net</code></a>.
|
|
|
|
|
<p>Unit testing is an important part of an overall testing-centric development strategy. If you write unit tests, it is important
|
|
|
|
|
to write them early (preferably before writing the code that they test), and to keep them updated as code and requirements
|
|
|
|
|
change. Unit testing is not a replacement for higher-level functional or system testing, but it is important in all phases
|
|
|
|
|
of development:
|
|
|
|
|
<div class=itemizedlist>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>Before writing code, it forces you to detail your requirements in a useful fashion.
|
|
|
|
|
<li>While writing code, it keeps you from over-coding. When all the test cases pass, the function is complete.
|
|
|
|
|
<li>When refactoring code, it assures you that the new version behaves the same way as the old version.
|
|
|
|
|
<li>When maintaining code, it helps you cover your ass when someone comes screaming that your latest change broke their old code.
|
|
|
|
|
(“But <em>sir</em>, all the unit tests passed when I checked it in...”)
|
|
|
|
|
|
|
|
|
|
<li>When writing code in a team, it increases confidence that the code you're about to commit isn't going to break other peoples'
|
|
|
|
|
code, because you can run their unittests first. (I've seen this sort of thing in code sprints. A team breaks up the assignment,
|
|
|
|
|
everybody takes the specs for their task, writes unit tests for it, then shares their unit tests with the rest of the team.
|
|
|
|
|
That way, nobody goes off too far into developing code that won't play well with others.)
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<h2 id="roman.romantest">13.3. Introducing <code>romantest.py</code></h2>
|
|
|
|
|
<p>This is the complete test suite for your Roman numeral conversion functions, which are yet to be written but will eventually
|
|
|
|
|
be in <code>roman.py</code>. It is not immediately obvious how it all fits together; none of these classes or methods reference any of the others.
|
|
|
|
|
There are good reasons for this, as you'll see shortly.
|
|
|
|
|
<div class=example><h3>Example 13.1. <code>romantest.py</code></h3>
|
|
|
|
|
<p>If you have not already done so, you can <a href="http://diveintopython3.org/download/diveintopython3-examples-5.4.zip" title="Download example scripts">download this and other examples</a> used in this book.
|
|
|
|
|
<pre><code>
|
|
|
|
|
"""Unit test for roman.py"""
|
|
|
|
|
|
|
|
|
|
import roman
|
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
class KnownValues(unittest.TestCase):
|
|
|
|
|
knownValues = ( (1, 'I'),
|
|
|
|
|
(2, 'II'),
|
|
|
|
|
(3, 'III'),
|
|
|
|
|
(4, 'IV'),
|
|
|
|
|
(5, 'V'),
|
|
|
|
|
(6, 'VI'),
|
|
|
|
|
(7, 'VII'),
|
|
|
|
|
(8, 'VIII'),
|
|
|
|
|
(9, 'IX'),
|
|
|
|
|
(10, 'X'),
|
|
|
|
|
(50, 'L'),
|
|
|
|
|
(100, 'C'),
|
|
|
|
|
(500, 'D'),
|
|
|
|
|
(1000, 'M'),
|
|
|
|
|
(31, 'XXXI'),
|
|
|
|
|
(148, 'CXLVIII'),
|
|
|
|
|
(294, 'CCXCIV'),
|
|
|
|
|
(312, 'CCCXII'),
|
|
|
|
|
(421, 'CDXXI'),
|
|
|
|
|
(528, 'DXXVIII'),
|
|
|
|
|
(621, 'DCXXI'),
|
|
|
|
|
(782, 'DCCLXXXII'),
|
|
|
|
|
(870, 'DCCCLXX'),
|
|
|
|
|
(941, 'CMXLI'),
|
|
|
|
|
(1043, 'MXLIII'),
|
|
|
|
|
(1110, 'MCX'),
|
|
|
|
|
(1226, 'MCCXXVI'),
|
|
|
|
|
(1301, 'MCCCI'),
|
|
|
|
|
(1485, 'MCDLXXXV'),
|
|
|
|
|
(1509, 'MDIX'),
|
|
|
|
|
(1607, 'MDCVII'),
|
|
|
|
|
(1754, 'MDCCLIV'),
|
|
|
|
|
(1832, 'MDCCCXXXII'),
|
|
|
|
|
(1993, 'MCMXCIII'),
|
|
|
|
|
(2074, 'MMLXXIV'),
|
|
|
|
|
(2152, 'MMCLII'),
|
|
|
|
|
(2212, 'MMCCXII'),
|
|
|
|
|
(2343, 'MMCCCXLIII'),
|
|
|
|
|
(2499, 'MMCDXCIX'),
|
|
|
|
|
(2574, 'MMDLXXIV'),
|
|
|
|
|
(2646, 'MMDCXLVI'),
|
|
|
|
|
(2723, 'MMDCCXXIII'),
|
|
|
|
|
(2892, 'MMDCCCXCII'),
|
|
|
|
|
(2975, 'MMCMLXXV'),
|
|
|
|
|
(3051, 'MMMLI'),
|
|
|
|
|
(3185, 'MMMCLXXXV'),
|
|
|
|
|
(3250, 'MMMCCL'),
|
|
|
|
|
(3313, 'MMMCCCXIII'),
|
|
|
|
|
(3408, 'MMMCDVIII'),
|
|
|
|
|
(3501, 'MMMDI'),
|
|
|
|
|
(3610, 'MMMDCX'),
|
|
|
|
|
(3743, 'MMMDCCXLIII'),
|
|
|
|
|
(3844, 'MMMDCCCXLIV'),
|
|
|
|
|
(3888, 'MMMDCCCLXXXVIII'),
|
|
|
|
|
(3940, 'MMMCMXL'),
|
|
|
|
|
(3999, 'MMMCMXCIX'))
|
|
|
|
|
|
|
|
|
|
def testToRomanKnownValues(self):
|
|
|
|
|
"""to_roman should give known result with known input"""
|
|
|
|
|
for integer, numeral in self.knownValues:
|
|
|
|
|
result = roman.to_roman(integer)
|
|
|
|
|
self.assertEqual(numeral, result)
|
|
|
|
|
|
|
|
|
|
def testFromRomanKnownValues(self):
|
|
|
|
|
"""from_roman should give known result with known input"""
|
|
|
|
|
for integer, numeral in self.knownValues:
|
|
|
|
|
result = roman.from_roman(numeral)
|
|
|
|
|
self.assertEqual(integer, result)
|
|
|
|
|
|
|
|
|
|
class ToRomanBadInput(unittest.TestCase):
|
|
|
|
|
def testTooLarge(self):
|
|
|
|
|
"""to_roman should fail with large input"""
|
|
|
|
|
self.assertRaises(roman.OutOfRangeError, roman.to_roman, 4000)
|
|
|
|
|
|
|
|
|
|
def testZero(self):
|
|
|
|
|
"""to_roman should fail with 0 input"""
|
|
|
|
|
self.assertRaises(roman.OutOfRangeError, roman.to_roman, 0)
|
|
|
|
|
|
|
|
|
|
def testNegative(self):
|
|
|
|
|
"""to_roman should fail with negative input"""
|
|
|
|
|
self.assertRaises(roman.OutOfRangeError, roman.to_roman, -1)
|
|
|
|
|
|
|
|
|
|
def testNonInteger(self):
|
|
|
|
|
"""to_roman should fail with non-integer input"""
|
|
|
|
|
self.assertRaises(roman.NotIntegerError, roman.to_roman, 0.5)
|
|
|
|
|
|
|
|
|
|
class FromRomanBadInput(unittest.TestCase):
|
|
|
|
|
def testTooManyRepeatedNumerals(self):
|
|
|
|
|
"""from_roman should fail with too many repeated numerals"""
|
|
|
|
|
for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
|
|
|
|
|
self.assertRaises(roman.InvalidRomanNumeralError, roman.from_roman, s)
|
|
|
|
|
|
|
|
|
|
def testRepeatedPairs(self):
|
|
|
|
|
"""from_roman should fail with repeated pairs of numerals"""
|
|
|
|
|
for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):
|
|
|
|
|
self.assertRaises(roman.InvalidRomanNumeralError, roman.from_roman, s)
|
|
|
|
|
|
|
|
|
|
def testMalformedAntecedent(self):
|
|
|
|
|
"""from_roman should fail with malformed antecedents"""
|
|
|
|
|
for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
|
|
|
|
|
'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
|
|
|
|
|
self.assertRaises(roman.InvalidRomanNumeralError, roman.from_roman, s)
|
|
|
|
|
|
|
|
|
|
class SanityCheck(unittest.TestCase):
|
|
|
|
|
def testSanity(self):
|
|
|
|
|
"""from_roman(to_roman(n))==n for all n"""
|
|
|
|
|
for integer in range(1, 4000):
|
|
|
|
|
numeral = roman.to_roman(integer)
|
|
|
|
|
result = roman.from_roman(numeral)
|
|
|
|
|
self.assertEqual(integer, result)
|
|
|
|
|
|
|
|
|
|
class CaseCheck(unittest.TestCase):
|
|
|
|
|
def testToRomanCase(self):
|
|
|
|
|
"""to_roman should always return uppercase"""
|
|
|
|
|
for integer in range(1, 4000):
|
|
|
|
|
numeral = roman.to_roman(integer)
|
|
|
|
|
self.assertEqual(numeral, numeral.upper())
|
|
|
|
|
|
|
|
|
|
def testFromRomanCase(self):
|
|
|
|
|
"""from_roman should only accept uppercase input"""
|
|
|
|
|
for integer in range(1, 4000):
|
|
|
|
|
numeral = roman.to_roman(integer)
|
|
|
|
|
roman.from_roman(numeral.upper())
|
|
|
|
|
self.assertRaises(roman.InvalidRomanNumeralError,
|
|
|
|
|
roman.from_roman, numeral.lower())
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main() </pre><div class=itemizedlist>
|
|
|
|
|
<h3>Further reading</h3>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><a href="http://pyunit.sourceforge.net/">The PyUnit home page</a> has an in-depth discussion of <a href="http://pyunit.sourceforge.net/pyunit.html">using the <code>unittest</code> framework</a>, including advanced features not covered in this chapter.
|
|
|
|
|
|
|
|
|
|
<li><a href="http://pyunit.sourceforge.net/pyunit.html">The PyUnit <acronym>FAQ</acronym></a> explains <a href="http://pyunit.sourceforge.net/pyunit.html#WHERE">why test cases are stored separately</a> from the code they test.
|
|
|
|
|
|
|
|
|
|
<li><a href="http://www.python.org/doc/current/lib/"><i class=citetitle>Python Library Reference</i></a> summarizes the <a href="http://www.python.org/doc/current/lib/module-unittest.html"><code>unittest</code></a> module.
|
|
|
|
|
|
|
|
|
|
<li><a href="http://www.extremeprogramming.org/">ExtremeProgramming.org</a> discusses <a href="http://www.extremeprogramming.org/rules/unittests.html">why you should write unit tests</a>.
|
|
|
|
|
|
|
|
|
|
<li><a href="http://www.c2.com/cgi/wiki">The Portland Pattern Repository</a> has an ongoing discussion of <a href="http://www.c2.com/cgi/wiki?UnitTests">unit tests</a>, including a <a href="http://www.c2.com/cgi/wiki?StandardDefinitionOfUnitTest">standard definition</a>, why you should <a href="http://www.c2.com/cgi/wiki?CodeUnitTestFirst">code unit tests first</a>, and several in-depth <a href="http://www.c2.com/cgi/wiki?UnitTestTrial">case studies</a>.
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
<h2 id="roman.success">13.4. Testing for success</h2>
|
|
|
|
|
<p>A test case answers a single question about the code it is testing. A test case should be able to...
|
|
|
|
|
<ul>
|
|
|
|
|
<li>...run completely by itself, without any human input. Unit testing is about automation.
|
|
|
|
|
<li>...determine by itself whether the function it is testing has passed or failed, without a human interpreting the results.
|
|
|
|
|
<li>...run in isolation, separate from any other test cases (even if they test the same functions). Each test case is an island.
|
|
|
|
|
</ul>
|
|
|
|
|
<p>Given that, let's build the first test case. You have the following <a href="#roman.requirements">requirement</a>:
|
|
|
|
|
<div class=orderedlist>
|
|
|
|
|
<ol>
|
|
|
|
|
<li><code>to_roman()</code> should return the Roman numeral representation for all integers <code>1</code> to <code>3999</code>.
|
|
|
|
|
|
|
|
|
|
</ol>
|
|
|
|
|
<div class=example><h3 id="roman.testtoromanknownvalues.example">Example 13.2. <code>testToRomanKnownValues</code></h3><pre><code>
|
|
|
|
|
class KnownValues(unittest.TestCase): <span>①</span>
|
|
|
|
|
knownValues = ( (1, 'I'),
|
|
|
|
|
(2, 'II'),
|
|
|
|
|
(3, 'III'),
|
|
|
|
|
(4, 'IV'),
|
|
|
|
|
(5, 'V'),
|
|
|
|
|
(6, 'VI'),
|
|
|
|
|
(7, 'VII'),
|
|
|
|
|
(8, 'VIII'),
|
|
|
|
|
(9, 'IX'),
|
|
|
|
|
(10, 'X'),
|
|
|
|
|
(50, 'L'),
|
|
|
|
|
(100, 'C'),
|
|
|
|
|
(500, 'D'),
|
|
|
|
|
(1000, 'M'),
|
|
|
|
|
(31, 'XXXI'),
|
|
|
|
|
(148, 'CXLVIII'),
|
|
|
|
|
(294, 'CCXCIV'),
|
|
|
|
|
(312, 'CCCXII'),
|
|
|
|
|
(421, 'CDXXI'),
|
|
|
|
|
(528, 'DXXVIII'),
|
|
|
|
|
(621, 'DCXXI'),
|
|
|
|
|
(782, 'DCCLXXXII'),
|
|
|
|
|
(870, 'DCCCLXX'),
|
|
|
|
|
(941, 'CMXLI'),
|
|
|
|
|
(1043, 'MXLIII'),
|
|
|
|
|
(1110, 'MCX'),
|
|
|
|
|
(1226, 'MCCXXVI'),
|
|
|
|
|
(1301, 'MCCCI'),
|
|
|
|
|
(1485, 'MCDLXXXV'),
|
|
|
|
|
(1509, 'MDIX'),
|
|
|
|
|
(1607, 'MDCVII'),
|
|
|
|
|
(1754, 'MDCCLIV'),
|
|
|
|
|
(1832, 'MDCCCXXXII'),
|
|
|
|
|
(1993, 'MCMXCIII'),
|
|
|
|
|
(2074, 'MMLXXIV'),
|
|
|
|
|
(2152, 'MMCLII'),
|
|
|
|
|
(2212, 'MMCCXII'),
|
|
|
|
|
(2343, 'MMCCCXLIII'),
|
|
|
|
|
(2499, 'MMCDXCIX'),
|
|
|
|
|
(2574, 'MMDLXXIV'),
|
|
|
|
|
(2646, 'MMDCXLVI'),
|
|
|
|
|
(2723, 'MMDCCXXIII'),
|
|
|
|
|
(2892, 'MMDCCCXCII'),
|
|
|
|
|
(2975, 'MMCMLXXV'),
|
|
|
|
|
(3051, 'MMMLI'),
|
|
|
|
|
(3185, 'MMMCLXXXV'),
|
|
|
|
|
(3250, 'MMMCCL'),
|
|
|
|
|
(3313, 'MMMCCCXIII'),
|
|
|
|
|
(3408, 'MMMCDVIII'),
|
|
|
|
|
(3501, 'MMMDI'),
|
|
|
|
|
(3610, 'MMMDCX'),
|
|
|
|
|
(3743, 'MMMDCCXLIII'),
|
|
|
|
|
(3844, 'MMMDCCCXLIV'),
|
|
|
|
|
(3888, 'MMMDCCCLXXXVIII'),
|
|
|
|
|
(3940, 'MMMCMXL'),
|
|
|
|
|
(3999, 'MMMCMXCIX')) <span>②</span>
|
|
|
|
|
|
|
|
|
|
def testToRomanKnownValues(self): <span>③</span>
|
|
|
|
|
"""to_roman should give known result with known input"""
|
|
|
|
|
for integer, numeral in self.knownValues:
|
|
|
|
|
result = roman.to_roman(integer) <span>④</span> <span>⑤</span>
|
|
|
|
|
self.assertEqual(numeral, result) <span>⑥</span></pre><div class=calloutlist>
|
|
|
|
|
<h2 id="roman.failure">13.5. Testing for failure</h2>
|
|
|
|
|
<p>It is not enough to test that functions succeed when given good input; you must also test that they fail when given bad input. And not just any sort of failure; they must fail in the way you expect.
|
|
|
|
|
<p>Remember the <a href="#roman.requirements">other requirements</a> for <code>to_roman()</code>:
|
|
|
|
|