plugin.html   [plain text]


<?xml version="1.0"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><title>Twisted Documentation: Writing a New Plug-In for mktap</title><link href="../howto/stylesheet.css" type="text/css" rel="stylesheet" /></head><body bgcolor="white"><h1 class="title">Writing a New Plug-In for mktap</h1><div class="toc"><ol><li><a href="#auto0">Getting Started</a></li><li><a href="#auto1">Twisted and You: Where Does Your Code Fit In?</a></li><li><a href="#auto2">What is a Plug-In?</a></li><li><a href="#auto3">Twisted Quotes: A Case Study</a></li><ul><li><a href="#auto4">Before you Begin</a></li><li><a href="#auto5">A Look at the Heart of the Application</a></li></ul></ol></div><div class="content"><span></span><h2>Getting Started<a name="auto0"></a></h2><p>Twisted is a very general and powerful tool.  It can power anything
connected to a network, from your corporate message-broadcasting network to
your desktop IRC client.  This is great for integrating lots of different
tools, but can make it very difficult to document and understand how the whole
platform is supposed to work.  A side effect of this is that it's hard to get
started with a project using Twisted, because it's hard to find out where to
start.</p><p>This guide is to help you understand one of the main ways to get
started working on a Twisted server application.  It probably won't
answer your specific questions about how to do things like <a href="time.html">schedule functions to call in the future</a> or <a href="servers.html">listen on a socket</a>; there are other
documents that address these concerns and you can read them
later. Client applications are not likely to use the infrastructure
described in this document, and a simpler alternative exists for
servers, covered in the <a href="application.html">Application
howto</a>.</p><h2>Twisted and You: Where Does Your Code Fit In?<a name="auto1"></a></h2><p>If you're like most people that have asked me questions about this, you've
probably come to Twisted thinking of it as a library of code to help you write
an application.  It can be, but it is much more useful to think of <em>your
code as the library</em>.  Twisted is a framework.</p><p>The difference between a framework and a library is that a developer's code
will run a library's functions; a framework runs the developer's functions,
instead.  The difference is subtle, but significant; there are a range of
resources which have to be allocated and managed regarding start-up and
shut-down of an process, such as spawning of threads and handling events.  You
don't have to use Twisted this way.  It is quite possible to write applications
that use Twisted almost exclusively as a library. If you use it as a framework,
though, Twisted will help you by managing these resources itself.</p><p>The central framework classes that you will deal with, both as a Twisted
developer and administrator, are services, which implement <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">twisted.application.service.IService</a></code>. <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">twisted.application.service.Application</a></code> creates the root
of a tree of services that form a twisted application.  There is one <code class="python">Application</code> instance per Twisted process, and it is the
top-level manager of resources and handler of events in the Twisted framework.
(Unlike some other frameworks, developers do not subclass <code class="python">Application</code>; rather by adding services it, you
register event handlers to be called by it.)  To store configuration data, as
well as other information, Twisted serializes <code class="python">Application</code> instances, storing all services that
have been registered with them.  Since the whole <code class="python">Application</code> instance is serialized, Twisted
<q>configuration</q> files are significantly more comprehensive than
those for other
systems. These files store everything related to a running <code class="python">Application</code> instance; in essence the full state of a
running process.</p><p>The central concept that a Twisted system administrator will work with are
files that contain
<code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code>
instances serialized
in various formats optimized for different uses. <code>.TAP</code> files are
optimized for speed of loading and saving, <code>.TAX</code> files are
editable by administrators familiar
with XML syntax, and <code>.TAS</code> files are generated Python source code,
most useful
for developers.  The two command-line programs which work with these files are
<code class="shell">mktap</code> and <code class="shell">twistd</code>.  The
<code class="shell">mktap</code> utility create <code>.TA*</code> files from
simple
command-line arguments, and the <code class="shell">twistd</code> daemon will
load and run those files. Alternatively, a Python script can be used to
create the <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code>
instance and this script can be run directly using 
<code class="shell">twistd -y script.py</code>, as long as the file <code>script.py</code>
has a <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code>
object called <code>application</code> on the module level. Applications are
covered in more depth in the <a href="application.html">Application howto</a>.</p><p>There are many ways in which your code will be called by various parts of
the Twisted framework by the time you're done.  The initial one we're going to
focus on here is a plug-in for the <code class="shell">mktap</code> utility.
<code class="shell">mktap</code> produces complete, runnable
<code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code>
instances, so no additional work is necessary to make your code work with
<code class="shell">twistd</code>.  First we will go through the process of
creating a
plug-in that Twisted can find, then we make it adhere to the <code class="shell">mktap</code> interface.  Finally we will load that plug-in with a
server.</p><h2>What is a Plug-In?<a name="auto2"></a></h2><p>Python makes it very easy to dynamically load and evaluate programs.  The
plug-in system for Twisted, <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.python.plugin.html" title="twisted.python.plugin">twisted.python.plugin</a></code>, is
a way to find (without loading) and then load plug-ins for particular
systems.</p><p>Unlike other <q>plug-in</q> systems, like the well known ones associated
with The
Gimp, Photoshop, and Apache <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.python.plugin.html" title="twisted.python.plugin">twisted.python.plugin</a></code> is
generic.  Any one of the Twisted <a href="http://twistedmatrix.com/products/dot-products"><q>dot-products</q></a>
can
define mechanisms for extensibility using plug-ins.  Two Twisted dot-products
already load such plug-ins.  The 
<code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.html" title="twisted.application">twisted.application</a></code>package loads Twisted Application
builder modules (TAP plug-ins) and the
<code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.lore.html" title="twisted.lore">twisted.lore</a></code> package loads document formatting
modules.</p><p>Twisted finds its plug-ins by using pre-existing Python concepts; the load
path, and packages.  Every top-level <a href="http://www.python.org/doc/current/tut/node8.html#SECTION008400000000000000000">Python package</a> (that is, a directory whose parent is on sys.path and which
contains an <code class="shell">__init__.py</code>) can potentially contain
some number of plug-ins.  Packages which contain plug-ins are called
<q>drop-ins</q>, because you <q>drop</q> them into your
<code>sys.path</code>. The only
difference
between a package and a drop-in is the existence of a file named <code class="shell">plugins.tml</code> (TML for Twisted Module List) that contains
some special Python expressions to identify the location of sub-packages or
modules which can be loaded.</p><p>If you look at <code class="shell">twisted/plugins.tml</code>, you will
notice that Twisted is a drop-in for itself!  You can browse through it for
lots of examples of plug-ins being registered.</p><p>The most prevalent kind of plug-in is the <code>TAP</code> (Twisted
Application builder) type.  These are relatively simple to get started with.
Let's look at an excerpt from Twisted's own plugins.tml for an example of
registering one:</p><pre class="python">
<span class="py-src-comment"># ...
</span><span class="py-src-nl">
</span><span class="py-src-variable">register</span><span class="py-src-op">(</span><span class="py-src-string">&quot;Twisted Web Automated TAP builder&quot;</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-string">&quot;twisted.tap.web&quot;</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-variable">description</span><span class="py-src-op">=</span><span class="py-src-string">&quot;&quot;&quot;
         Builds a Twisted Application instance that contains a general-purpose
         web server, which can serve from a filesystem or application resource.
         &quot;&quot;&quot;</span>            <span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-variable">type</span><span class="py-src-op">=</span><span class="py-src-string">&quot;tap&quot;</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-variable">tapname</span><span class="py-src-op">=</span><span class="py-src-string">&quot;web&quot;</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-comment"># ...
</span><span class="py-src-endmarker"></span></pre><p><code class="shell">plugins.tml</code> will be a list of calls to one function:
<pre class="python">
<span class="py-src-variable">register</span><span class="py-src-op">(</span><span class="py-src-variable">name</span><span class="py-src-op">,</span> <span class="py-src-variable">module</span><span class="py-src-op">,</span> <span class="py-src-variable">type</span><span class="py-src-op">=</span><span class="py-src-variable">plugin_type</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-variable">description</span><span class="py-src-op">=</span><span class="py-src-variable">user_description</span><span class="py-src-nl">
</span>         <span class="py-src-op">[</span><span class="py-src-op">,</span> <span class="py-src-op">**</span><span class="py-src-variable">plugin_specific_data</span><span class="py-src-op">]</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-endmarker"></span></pre><ul><li><code class="py-src-parameter">name</code> is a free-form string, to be
    displayed to the user in presentation contexts (like a web page, or a
    list-box in a GUI).</li><li><code class="py-src-parameter">module</code> is a string which must
    be the fully-qualified name of a Python module.</li><li><code class="py-src-parameter">type</code> is the name of the system you are
    plugging in to.  Be sure to spell this right, or Twisted won't find your
    plug-in at all!</li><li><code class="py-src-parameter">**plugin_specific_data</code> is a dictionary of
    information associated with the plug-in, specific to the <code class="py-src-parameter">type</code> of plug-in it is. Note that some plug-in
    types may require a specific bit of data in order to work.</li></ul></p><p>Note the <code class="py-src-parameter">tapname</code> parameter given in
the example above.  This parameter is an example of <code class="py-src-parameter">**plugin_specific_data</code>. The parameter <code class="py-src-parameter">tapname</code> is only used by <code class="py-src-string">&quot;tap&quot;</code>-type modules.  It indicates what name to use
on the <code class="shell">mktap</code> command line.  In English, this
particular call to <code class="python">register</code> means <q>When the user
types <code class="shell">mktap web</code>, it selects the module <code class="python">twisted.tap.web</code> to handle the rest of the arguments</q>.
</p><p>Now that you understand how to register a plug-in, let's move along to
writing your first one.</p><h2>Twisted Quotes: A Case Study<a name="auto3"></a></h2><p>As an example, we are going to work on a Quote of the Day application,
<code>TwistedQuotes</code>.
Aspects of this application will be explored in more depth
throughout in the Twisted documentation. </p><p><code>TwistedQuotes</code> is a very simple plugin which is a great
demonstration of
Twisted's power.  It will export a small kernel of functionality -- Quote of
the Day -- which can be accessed through every interface that Twisted supports:
web pages, e-mail, instant messaging, a specific Quote of the Day protocol, and
more.</p><h3>Before you Begin<a name="auto4"></a></h3><p>First, make a directory, <code>TwistedQuotes</code>, where you're going to
keep
your code.  If you installed Twisted from source, the path of least resistance
is probably just to make a
directory inside your <code class="shell">Twisted-X.X.X</code>
directory, which will already be in your <code class="python">sys.path</code>.  If you want to put it elsewhere, make
sure that your <code>TwistedQuotes</code> directory is a package on your python
path.</p><div class="note"><strong>Note: </strong><p>The directory you add to your PYTHONPATH
needs to be the directory <em>containing</em> your package's
directory! For example, if your TwistedQuotes directory is
/my/stuff/TwistedQuotes, you can <code class="shell">export
PYTHONPATH=/my/stuff:$PYTHONPATH</code> in UNIX, or edit the <code class="shell">PYTHONPATH</code> environment variable to add <code class="shell">/my/stuff;</code> at the beginning through the System
Properties dialog on Windows.</p></div><p>You will then need to add an <code class="shell">__init__.py</code> to this
directory, to mark it as a package.  (For more information on exactly how
Python packages work, read <a href="http://www.python.org/doc/current/tut/node8.html#SECTION008400000000000000000">this section</a> of the Python tutorial.) In order to test that everything is
working, start up the Python interactive interpreter, or your favorite IDE, and
verify that the package imports properly.</p><pre class="python-interpreter">
Python 2.1.3 (#1, Apr 20 2002, 22:45:31) 
[GCC 2.95.4 20011002 (Debian prerelease)] on linux2
Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; import TwistedQuotes
&gt;&gt;&gt; # No traceback means you're fine.
</pre><h3>A Look at the Heart of the Application<a name="auto5"></a></h3><p>(You'll need to put this code into a file called <code class="shell">quoters.py</code> in your <code class="shell">TwistedQuotes</code> directory.)</p><div class="py-listing"><pre>
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">random</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">choice</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-nl">
</span><span class="py-src-keyword">class</span> <span class="py-src-identifier">IQuoter</span><span class="py-src-op">(</span><span class="py-src-parameter">components</span><span class="py-src-op">.</span><span class="py-src-parameter">Interface</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-string">&quot;&quot;&quot;An object that returns quotes.&quot;&quot;&quot;</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-string">&quot;&quot;&quot;Return a quote.&quot;&quot;&quot;</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-nl">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-keyword">class</span> <span class="py-src-identifier">StaticQuoter</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-string">&quot;&quot;&quot;Return a static quote.&quot;&quot;&quot;</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-variable">__implements__</span> <span class="py-src-op">=</span> <span class="py-src-variable">IQuoter</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">quote</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">quote</span> <span class="py-src-op">=</span> <span class="py-src-variable">quote</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-keyword">return</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">quote</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-nl">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-keyword">class</span> <span class="py-src-identifier">FortuneQuoter</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-string">&quot;&quot;&quot;Load quotes from a fortune-format file.&quot;&quot;&quot;</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-variable">__implements__</span> <span class="py-src-op">=</span> <span class="py-src-variable">IQuoter</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">filenames</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">filenames</span> <span class="py-src-op">=</span> <span class="py-src-variable">filenames</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-keyword">return</span> <span class="py-src-variable">choice</span><span class="py-src-op">(</span><span class="py-src-variable">open</span><span class="py-src-op">(</span><span class="py-src-variable">choice</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">filenames</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">read</span><span class="py-src-op">(</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">split</span><span class="py-src-op">(</span><span class="py-src-string">'\n%\n'</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><div class="caption">Twisted Quotes
Central Abstraction - <a href="listings/TwistedQuotes/quoters.py"><span class="filename">listings/TwistedQuotes/quoters.py</span></a></div></div><p>This code listing shows us what the Twisted Quotes system is all about.  The
code doesn't have any way of talking to the outside world, but it provides a
library which is a clear and uncluttered abstraction: <q>give me the quote of
the day</q>. </p><p>Note that this module does not import any Twisted functionality at all!  The
reason for doing things this way is integration.  If your <q>business
objects</q> are not stuck to your user interface, you can make a module that
can integrate those objects with different protocols, GUIs, and file formats.
Having such classes provides a way to decouple your components from each other,
by allowing each to be used independently.</p><p>In this manner, Twisted itself has minimal impact on the logic of your
program.  Although the Twisted <q>dot products</q> are highly interoperable,
they
also follow this approach.  You can use them independently because they are not
stuck to each other.  They communicate in well-defined ways, and only when that
communication provides some additional feature.  Thus, you can use <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.html" title="twisted.web">twisted.web</a></code> with <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.enterprise.html" title="twisted.enterprise">twisted.enterprise</a></code>, but neither requires the other, because
they are integrated around the concept of <a href="defer.html">Deferreds</a>.
(Don't worry we'll get to each of those features in later documentation.)</p><p>Your Twisted applications should follow this style as much as possible.
Have (at least) one module which implements your specific functionality,
independant of any user-interface code.  </p><p>Next, we're going to need to associate this abstract logic with some way of
displaying it to the user.  We'll do this by writing a Twisted server protocol,
which will respond to the clients that connect to it by sending a quote to the
client and then closing the connection.  Note: don't get too focused on the
details of this -- different ways to interface with the user are 90% of what
Twisted does, and there are lots of documents describing the different ways to
do it.</p><p>(You'll need to put this code into a file called <code class="shell">quoteproto.py</code> in your <code class="shell">TwistedQuotes</code> directory.)</p><div class="py-listing"><pre>
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">internet</span><span class="py-src-op">.</span><span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span><span class="py-src-op">,</span> <span class="py-src-variable">Protocol</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span><span class="py-src-op">(</span><span class="py-src-parameter">Protocol</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-indent">    </span><span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">transport</span><span class="py-src-op">.</span><span class="py-src-variable">write</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">factory</span><span class="py-src-op">.</span><span class="py-src-variable">quoter</span><span class="py-src-op">.</span><span class="py-src-variable">getQuote</span><span class="py-src-op">(</span><span class="py-src-op">)</span><span class="py-src-op">+</span><span class="py-src-string">'\r\n'</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>        <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">transport</span><span class="py-src-op">.</span><span class="py-src-variable">loseConnection</span><span class="py-src-op">(</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span><span class="py-src-op">(</span><span class="py-src-parameter">Factory</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-indent">    </span><span class="py-src-variable">protocol</span> <span class="py-src-op">=</span> <span class="py-src-variable">QOTD</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span>    <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">quoter</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">        </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">quoter</span> <span class="py-src-op">=</span> <span class="py-src-variable">quoter</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><div class="caption">Twisted
Quotes Protocol Implementation - <a href="listings/TwistedQuotes/quoteproto.py"><span class="filename">listings/TwistedQuotes/quoteproto.py</span></a></div></div><p>This is a very straightforward <code>Protocol</code> implementation, and the
pattern described above is repeated here.  The Protocol contains essentially no
logic of its own, just enough to tie together an object which can generate
quotes (a <code class="python">Quoter</code>) and an object which can relay
bytes to a TCP connection (a <code class="python">Transport</code>).  When a
client connects to this server, a <code class="python">QOTD</code> instance is
created, and its <code class="python">connectionMade</code> method is called.
</p><p> The <code class="python">QOTDFactory</code>'s role is to specify to the
Twisted framework how to create a <code class="python">Protocol</code> instance
that will handle the connection.  Twisted will not instantiate a <code class="python">QOTDFactory</code>; you will do that yourself later, in the
<code class="shell">mktap</code> plug-in below.
</p><p>Note: you can read more specifics of <code class="python">Protocol</code> and
<code class="python">Factory</code> in the <a href="servers.html">Writing
Servers</a> HOWTO.</p><p>Once we have an abstraction -- a <code>Quoter</code> -- and we have a
mechanism to connect it to the network -- the <code>QOTD</code> protocol -- the
next thing to do is to put the last link in the chain of functionality between
abstraction and user.  This last link will allow a user to choose a
<code>Quoter</code> and configure the protocol.</p><p>Practically speaking, this link is an interface for a savvy user who will
run the server.  (In this case, you; when you have more users, a system
administrator.)  For the purposes of this example we will first implement a
<code class="shell">mktap</code> interface.  Like most system administrator
tools, this is command-line oriented.  (It is possible to implement a graphical
front-end to mktap, using the same plug-in structure, but this has not been
done yet.)</p><p>Creating the extension to <code class="shell">mktap</code> is done through
implementing a
module that follows the <code class="shell">mktap</code> plug-in interface,
and then
registering it to be found and loaded by <code>twisted.python.plugin</code>.
As described above, registration is done by adding a call to
<code class="python">register</code> in the file
<code class="shell">TwistedQuotes/plugins.tml</code></p><p>(You'll need to put this code into a file called <code class="shell">quotetap.py</code> in your <code class="shell">TwistedQuotes</code> directory.)</p><div class="py-listing"><pre>
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span> <span class="py-src-comment"># services that run TCP/SSL/etc.</span><span class="py-src-newline">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">TwistedQuotes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">quoteproto</span>    <span class="py-src-comment"># Protocol and Factory</span><span class="py-src-newline">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">TwistedQuotes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">quoters</span>       <span class="py-src-comment"># &quot;give me a quote&quot; code</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>        <span class="py-src-comment"># twisted command-line processing</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-nl">
</span><span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span><span class="py-src-op">(</span><span class="py-src-parameter">usage</span><span class="py-src-op">.</span><span class="py-src-parameter">Options</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-variable">optParameters</span> <span class="py-src-op">=</span> <span class="py-src-op">[</span><span class="py-src-op">[</span><span class="py-src-string">&quot;port&quot;</span><span class="py-src-op">,</span> <span class="py-src-string">&quot;p&quot;</span><span class="py-src-op">,</span> <span class="py-src-number">8007</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>                      <span class="py-src-string">&quot;Port number to listen on for QOTD protocol.&quot;</span><span class="py-src-op">]</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>                     <span class="py-src-op">[</span><span class="py-src-string">&quot;static&quot;</span><span class="py-src-op">,</span> <span class="py-src-string">&quot;s&quot;</span><span class="py-src-op">,</span> <span class="py-src-string">&quot;An apple a day keeps the doctor away.&quot;</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>                      <span class="py-src-string">&quot;A static quote to display.&quot;</span><span class="py-src-op">]</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>                     <span class="py-src-op">[</span><span class="py-src-string">&quot;file&quot;</span><span class="py-src-op">,</span> <span class="py-src-string">&quot;f&quot;</span><span class="py-src-op">,</span> <span class="py-src-variable">None</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>                      <span class="py-src-string">&quot;A fortune-format text file to read quotes from.&quot;</span><span class="py-src-op">]</span><span class="py-src-op">]</span><span class="py-src-newline">
</span><span class="py-src-nl">
</span><span class="py-src-nl">
</span><span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span><span class="py-src-op">(</span><span class="py-src-parameter">config</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
</span><span class="py-src-indent">    </span><span class="py-src-string">&quot;&quot;&quot;Return a service that will be attached to the application.&quot;&quot;&quot;</span><span class="py-src-newline">
</span>    <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span><span class="py-src-op">[</span><span class="py-src-string">&quot;file&quot;</span><span class="py-src-op">]</span><span class="py-src-op">:</span>                  <span class="py-src-comment"># If I was given a &quot;file&quot; option...</span><span class="py-src-newline">
</span>        <span class="py-src-comment"># Read quotes from a file, selecting a random one each time,
</span><span class="py-src-indent">        </span><span class="py-src-variable">quoter</span> <span class="py-src-op">=</span> <span class="py-src-variable">quoters</span><span class="py-src-op">.</span><span class="py-src-variable">FortuneQuoter</span><span class="py-src-op">(</span><span class="py-src-op">[</span><span class="py-src-variable">config</span><span class="py-src-op">[</span><span class="py-src-string">'file'</span><span class="py-src-op">]</span><span class="py-src-op">]</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>    <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span>                               <span class="py-src-comment"># otherwise,</span><span class="py-src-newline">
</span>        <span class="py-src-comment"># read a single quote from the command line (or use the default).
</span><span class="py-src-indent">        </span><span class="py-src-variable">quoter</span> <span class="py-src-op">=</span> <span class="py-src-variable">quoters</span><span class="py-src-op">.</span><span class="py-src-variable">StaticQuoter</span><span class="py-src-op">(</span><span class="py-src-variable">config</span><span class="py-src-op">[</span><span class="py-src-string">'static'</span><span class="py-src-op">]</span><span class="py-src-op">)</span><span class="py-src-newline">
</span>    <span class="py-src-dedent"></span><span class="py-src-variable">port</span> <span class="py-src-op">=</span> <span class="py-src-variable">int</span><span class="py-src-op">(</span><span class="py-src-variable">config</span><span class="py-src-op">[</span><span class="py-src-string">&quot;port&quot;</span><span class="py-src-op">]</span><span class="py-src-op">)</span>          <span class="py-src-comment"># TCP port to listen on</span><span class="py-src-newline">
</span>    <span class="py-src-variable">factory</span> <span class="py-src-op">=</span> <span class="py-src-variable">quoteproto</span><span class="py-src-op">.</span><span class="py-src-variable">QOTDFactory</span><span class="py-src-op">(</span><span class="py-src-variable">quoter</span><span class="py-src-op">)</span> <span class="py-src-comment"># here we create a QOTDFactory</span><span class="py-src-newline">
</span>    <span class="py-src-comment"># Finally, set up our factory, with its custom quoter, to create QOTD
</span>    <span class="py-src-comment"># protocol instances when events arrive on the specified port.
</span>    <span class="py-src-keyword">return</span> <span class="py-src-variable">internet</span><span class="py-src-op">.</span><span class="py-src-variable">TCPServer</span><span class="py-src-op">(</span><span class="py-src-variable">port</span><span class="py-src-op">,</span> <span class="py-src-variable">factory</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><div class="caption">Twisted Quotes
TAP construction module - <a href="listings/TwistedQuotes/quotetap.py"><span class="filename">listings/TwistedQuotes/quotetap.py</span></a></div></div><p>This module has to conform to a fairly simple interface.  It must have a
class called <code class="python">Options</code> which is a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">twisted.python.usage.Options</a></code>.  It must also have a function
 <code class="python">makeService(config)</code>, which will be
passed an instance of the
<code class="python">Options</code> class defined in the module itself, <code class="python">TwistedQuotes.quotetap.Options</code>.  Command-line options
given on the <code class="shell">mktap</code> command line fill in the values
in <code class="python">Options</code> and are used in <code class="python">makeService</code> to make the actual connections between
objects. <code class="python">makeService</code> is expected to return an object implementing
<code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>. This can
be a <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Service.html" title="twisted.application.service.Service">Service</a></code> subclass,
a <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.MultiService.html" title="twisted.application.service.MultiService">MultiService</a></code> collection
of sub-services, a <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.internet.TCPServer.html" title="twisted.application.internet.TCPServer">TCPServer</a></code>
serving a protocol factory, and so on.</p><p>A more detailed discussion of <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">twisted.python.usage.Options</a></code> can be found in the document <a href="options.html">Using <code>usage.Options</code></a>.</p><p>Now that we've implemented all the necessary pieces, we can finish putting
them together by writing a TML file which allows the <code class="shell">mktap</code> utility to find our protocol module.</p><div class="py-listing"><pre>
<span class="py-src-variable">register</span><span class="py-src-op">(</span><span class="py-src-string">&quot;Quote of the Day TAP Builder&quot;</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-string">&quot;TwistedQuotes.quotetap&quot;</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-variable">description</span><span class="py-src-op">=</span><span class="py-src-string">&quot;&quot;&quot;
         Example of a TAP builder module.
         &quot;&quot;&quot;</span>            <span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-variable">type</span><span class="py-src-op">=</span><span class="py-src-string">&quot;tap&quot;</span><span class="py-src-op">,</span><span class="py-src-nl">
</span>         <span class="py-src-variable">tapname</span><span class="py-src-op">=</span><span class="py-src-string">&quot;qotd&quot;</span><span class="py-src-op">)</span><span class="py-src-newline">
</span><span class="py-src-endmarker"></span></pre><div class="caption">Twisted Quotes
Plug-in registration - <a href="listings/TwistedQuotes/plugins.tml"><span class="filename">listings/TwistedQuotes/plugins.tml</span></a></div></div><p>Now the QOTD server is ready to be instantiated!  Let's start up a server
and get a quote from it.</p><pre class="shell">
% mktap qotd       
Saving qotd application to qotd.tap...
Saved.
% twistd -f qotd.tap 
% nc localhost 8007
An apple a day keeps the doctor away.
% kill `cat twistd.pid`
</pre><p>Let's walk through the above example.  First, we run <code class="shell">mktap</code> specifying the Application type
(<code>qotd</code>) to create.
<code class="shell">mktap</code> reads in our <code class="shell">plugins.tml</code> file, instantiates an <code class="python">Application</code> object, fills in the appropriate data, and
serializes it out to a <code class="shell">qotd.tap</code> file.  Next, we
launch the server using the twistd daemon, passing <code class="shell">qotd.tap</code> as a command line option.  The server launches,
listens on the default port from <code class="shell">quotetap.py</code>.  Next,
we run <code class="shell">nc</code> to connect to the running server.  In this
step, the <code class="python">QOTDFactory</code> creates a <code class="python">Quoter</code> instance, which responds to our network connection
by sending a quote string (in this case, the default quote) over our
connection, and then closes the connection.  Finally, we shutdown the server by
killing it via a saved out process id file.</p><p>(<code class="shell">nc</code> is the <a href="http://www.atstake.com/research/tools/index.html#network_utilities">netcat</a>
utility, which no UNIX system should be without.)  </p><p>So we just saw Twisted in action as a framework.  With relatively little
code, we've got a server that can respond to a request over a network, with two
potential alternative back-ends (fortune files and static text).  </p><p>After reading this (and following along with your own example, of course),
you should be familiar with the process of getting your own Twisted code with
unique functionality in it running inside of a server.  You should be familiar
with the concept of a drop-in and a plug-in, and understand both how to create
them and how to install them from other people on your system.</p><p>By following the rules set out at the
beginning of this HOWTO, we have accidentally implemented another piece of
useful functionality. </p><pre class="shell">
% mktap
Usage:    mktap [options] &lt;command&gt; [command options]

Options:
  -x, --xml      DEPRECATED: same as --type=xml
  -s, --source   DEPRECATED: same as --type=source
  -e, --encrypted  Encrypt file before writing
  -p, --progress   Show progress of plugin loading
  -d, --debug      Show debug information for plugin loading
  -u, --uid=     [default: 1000]
  -g, --gid=     [default: 1000]
  -a, --append=  An existing .tap file to append the plugin to, rather than
                 creating a new one.
  -t, --type=    The output format to use; this can be 'pickle', 'xml', or
                 'source'. [default: pickle]
      --help     display this message
Commands:
    ftp              An FTP server.
    im               A multi-protocol chat client.
    inetd
    mail             An email service.
    manhole          An interactive remote debugger service.
    news             News Server
    portforward      A simple port-forwarder.
    <em>qotd             Example of a TAP builder module.</em>
    socks            A SOCKSv4 proxy service.
    ssh
    telnet           A simple, telnet-based remote debugging service.
    toc              An AIM TOC service.
    web              A general-purpose web server which can serve from a
                     filesystem or application resource.
    words            A chat service.
</pre><p>Not only does our <code class="python">Options</code> class get
instantiated by <code class="shell">mktap</code> directly, the user can query mktap for interactive
help!  This is just one small benefit to using Twisted as it was designed.  As
more tools that use the <code class="py-src-string"><q>tap</q></code> style of
plug-in, more useful functionality will become available from Twisted Quotes.
For example, a graphical tool could provide not just help messages at the
command line, but a listing of all available TAP types and forms for each, for
the user to enter information.</p><p>It is this kind of power that results from using a dynamic, powerful
framework like Twisted.  I hope that you take your newfound knowledge and
discover all kinds of cool things like this that you get for free just by using
it!</p><div class="doit"><p>The plug-in system is a relatively new part of Twisted, and not
as many things use it as they should yet.  Watch this space for new
developments regarding plug-ins, other systems that you can plug your code into,
and more documentation for people wanting to write systems that can be plugged
in to!</p></div></div><p><a href="../howto/index.html">Index</a></p><span class="version">Version: 1.3.0</span></body></html>