1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
|
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Twisted Documentation: The Twisted Plugin System</title>
<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
</head>
<body bgcolor="white">
<h1 class="title">The Twisted Plugin System</h1>
<div class="toc"><ol><li><a href="#auto0">Writing Extensible Programs</a></li><li><a href="#auto1">Extending an Existing Program</a></li><li><a href="#auto2">Alternate Plugin Packages</a></li><li><a href="#auto3">Plugin Caching</a></li><li><a href="#auto4">Further Reading</a></li></ol></div>
<div class="content">
<span/>
<p>The purpose of this guide is to describe the preferred way to
write extensible Twisted applications (and consequently, also to
describe how to extend applications written in such a way). This
extensibility is achieved through the definition of one or more
APIs and a mechanism for collecting code plugins which
implement this API to provide some additional functionality.
At the base of this system is the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> module.</p>
<p>Making an application extensible using the plugin system has
several strong advantages over other techniques:</p>
<ul>
<li>It allows third-party developers to easily enhance your
software in a way that is loosely coupled: only the plugin API
is required to remain stable.</li>
<li>It allows new plugins to be discovered flexibly. For
example, plugins can be loaded and saved when a program is first
run, or re-discovered each time the program starts up, or they
can be polled for repeatedly at runtime (allowing the discovery
of new plugins installed after the program has started).</li>
</ul>
<h2>Writing Extensible Programs<a name="auto0"/></h2>
<p>Taking advantage of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> is
a two step process:</p>
<ol>
<li>
<p>
Define an interface which plugins will be required to implement.
This is done using the zope.interface package in the same way one
would define an interface for any other purpose.
</p>
<p>
A convention for defining interfaces is do so in a file named like
<em>ProjectName/projectname/iprojectname.py</em>. The rest of this
document will follow that convention: consider the following
interface definition be in <code>Matsim/matsim/imatsim.py</code>, an
interface definition module for a hypothetical material simulation
package.
</p>
</li>
<li>
At one or more places in your program, invoke <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.getPlugins.html" title="twisted.plugin.getPlugins">twisted.plugin.getPlugins</a></code> and iterate over its
result.
</li>
</ol>
<p>
As an example of the first step, consider the following interface
definition for a physical modelling system.
</p>
<pre class="python"><p class="py-linenumber"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">Attribute</span>
<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMaterial</span>(<span class="py-src-parameter">Interface</span>):
<span class="py-src-string">"""
An object with specific physical properties
"""</span>
<span class="py-src-keyword">def</span> <span class="py-src-identifier">yieldStress</span>(<span class="py-src-parameter">temperature</span>):
<span class="py-src-string">"""
Returns the pressure this material can support without
fracturing at the given temperature.
@type temperature: C{float}
@param temperature: Kelvins
@rtype: C{float}
@return: Pascals
"""</span>
<span class="py-src-variable">dielectricConstant</span> = <span class="py-src-variable">Attribute</span>(<span class="py-src-string">"""
@type dielectricConstant: C{complex}
@ivar dielectricConstant: The relative permittivity, with the
real part giving reflective surface properties and the
imaginary part giving the radio absorption coefficient.
"""</span>)
</pre>
<p>In another module, we might have a function that operates on
objects providing the <code>IMaterial</code> interface:</p>
<pre class="python"><p class="py-linenumber">1
2
3
</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">displayMaterial</span>(<span class="py-src-parameter">m</span>):
<span class="py-src-keyword">print</span> <span class="py-src-string">'A material with yield stress %s at 500 K'</span> % (<span class="py-src-variable">m</span>.<span class="py-src-variable">yieldStress</span>(<span class="py-src-number">500</span>),)
<span class="py-src-keyword">print</span> <span class="py-src-string">'Also a dielectric constant of %s.'</span> % (<span class="py-src-variable">m</span>.<span class="py-src-variable">dielectricConstant</span>,)
</pre>
<p>The last piece of required code is that which collects
<code>IMaterial</code> providers and passes them to the
<code>displayMaterial</code> function.</p>
<pre class="python"><p class="py-linenumber">1
2
3
4
5
6
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">getPlugins</span>
<span class="py-src-keyword">from</span> <span class="py-src-variable">matsim</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">imatsim</span>
<span class="py-src-keyword">def</span> <span class="py-src-identifier">displayAllKnownMaterials</span>():
<span class="py-src-keyword">for</span> <span class="py-src-variable">material</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">getPlugins</span>(<span class="py-src-variable">imatsim</span>.<span class="py-src-variable">IMaterial</span>):
<span class="py-src-variable">displayMaterial</span>(<span class="py-src-variable">material</span>)
</pre>
<p>Third party developers may now contribute different materials
to be used by this modelling system by implementing one or more
plugins for the <code>IMaterial</code> interface.</p>
<h2>Extending an Existing Program<a name="auto1"/></h2>
<p>The above code demonstrates how an extensible program might be
written using Twisted's plugin system. How do we write plugins
for it, though? Essentially, we create objects which provide the
required interface and then make them available at a particular
location. Consider the following example.</p>
<pre class="python"><p class="py-linenumber"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
<span class="py-src-keyword">from</span> <span class="py-src-variable">matsim</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">imatsim</span>
<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleMaterial</span>(<span class="py-src-parameter">object</span>):
<span class="py-src-variable">implements</span>(<span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">imatsim</span>.<span class="py-src-variable">IMaterial</span>)
<span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">yieldStressFactor</span>, <span class="py-src-parameter">dielectricConstant</span>):
<span class="py-src-variable">self</span>.<span class="py-src-variable">_yieldStressFactor</span> = <span class="py-src-variable">yieldStressFactor</span>
<span class="py-src-variable">self</span>.<span class="py-src-variable">dielectricConstant</span> = <span class="py-src-variable">dielectricConstant</span>
<span class="py-src-keyword">def</span> <span class="py-src-identifier">yieldStress</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">temperature</span>):
<span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_yieldStressFactor</span> * <span class="py-src-variable">temperature</span>
<span class="py-src-variable">steelPlate</span> = <span class="py-src-variable">SimpleMaterial</span>(<span class="py-src-number">2.06842719e11</span>, <span class="py-src-number">2.7</span> + <span class="py-src-number">0.2j</span>)
<span class="py-src-variable">brassPlate</span> = <span class="py-src-variable">SimpleMaterial</span>(<span class="py-src-number">1.03421359e11</span>, <span class="py-src-number">1.4</span> + <span class="py-src-number">0.5j</span>)
</pre>
<p><code>steelPlate</code> and <code>brassPlate</code> now provide both
<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.IPlugin.html" title="twisted.plugin.IPlugin">IPlugin</a></code> and <code>IMaterial</code>.
All that remains is to make this module available at an appropriate
location. For this, there are two options. The first of these is
primarily useful during development: if a directory which
has been added to <code>sys.path</code> (typically by adding it to the
<code class="shell">PYTHONPATH</code> environment variable) contains a
<em>directory</em> named <code class="shell">twisted/plugins/</code>,
each <code class="shell">.py</code> file in that directory will be loaded
as a source of plugins. This directory <em>must not</em> be a Python
package: including <code class="shell">__init__.py</code> will cause the
directory to be skipped and no plugins loaded from it. Second, each
module in the installed version of Twisted's <code class="shell">
twisted.plugins</code> package will also be loaded as a source of
plugins.</p>
<p>Once this plugin is installed in one of these two ways,
<code>displayAllKnownMaterials</code> can be run and we will see
two pairs of output: one for a steel plate and one for a brass
plate.</p>
<h2>Alternate Plugin Packages<a name="auto2"/></h2>
<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.getPlugins.html" title="twisted.plugin.getPlugins">getPlugins</a></code> takes one
additional argument not mentioned above. If passed in, the 2nd argument
should be a module or package to be used instead of
<code>twisted.plugins</code> as the plugin meta-package. If you
are writing a plugin for a Twisted interface, you should never
need to pass this argument. However, if you have developed an
interface of your own, you may want to mandate that plugins for it
are installed in your own plugins package, rather than in
Twisted's. In this case, you probably also want to support <code class="shell">yourproject/plugins/</code> directories for ease of
development. To do so, you should make the <code class="shell">__init__.py</code> for that package contain at least
the following lines.</p>
<pre class="python"><p class="py-linenumber">1
2
3
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pluginPackagePaths</span>
<span class="py-src-variable">__path__</span>.<span class="py-src-variable">extend</span>(<span class="py-src-variable">pluginPackagePaths</span>(<span class="py-src-variable">__name__</span>))
<span class="py-src-variable">__all__</span> = []
</pre>
<p>The key behavior here is that interfaces are essentially paired
with a particular plugin package. If plugins are installed in a
different package than the one the code which relies on the
interface they provide, they will not be found when the
application goes to load them.</p>
<h2>Plugin Caching<a name="auto3"/></h2>
<p>In the course of using the Twisted plugin system, you may
notice <code class="shell">dropin.cache</code> files appearing at
various locations. These files are used to cache information
about what plugins are present in the directory which contains
them. At times, this cached information may become out of date.
Twisted uses the mtimes of various files involved in the plugin
system to determine when this cache may have become invalid.
Twisted will try to re-write the cache each time it tries to use
it but finds it out of date.</p>
<p>For a site-wide install, it may not (indeed, should not) be
possible for applications running as normal users to rewrite the
cache file. While these applications will still run and find
correct plugin information, they may run more slowly than they
would if the cache was up to date, and they may also report
exceptions if certain plugins have been removed but which the
cache still references. For these reasons, when installing or
removing software which provides Twisted plugins, the site
administrator should be sure the cache is regenerated.
Well-behaved package managers for such software should take this
task upon themselves, since it is trivially automatable. The
canonical way to regenerate the cache is to run the following
Python code:</p>
<pre class="python"><p class="py-linenumber">1
2
</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">getPlugins</span>
<span class="py-src-variable">list</span>(<span class="py-src-variable">getPlugins</span>(<span class="py-src-variable">IPlugin</span>))
</pre>
<p>As mentioned, it is normal for exceptions to be raised
<strong>once</strong> here if plugins have been removed.</p>
<h2>Further Reading<a name="auto4"/></h2>
<ul>
<li><a href="components.html" shape="rect">Components: Interfaces and Adapters</a></li>
</ul>
</div>
<p><a href="index.html">Index</a></p>
<span class="version">Version: 10.0.0</span>
</body>
</html>
|