Tangelo Plugins¶
Tangelo’s capabilities can be extended by creating plugins to serve custom content and services from a canonical URL, extend Tangelo’s Python runtime environment, and perform specialized setup and teardown actions to support new behaviors. Tangelo ships with several bundled plugins that implement useful features and provide examples of how the plugin system can add value to your Tangelo setup.
Structure and Content¶
A plugin is simply a directory containing a mix of content, documentation, and control directives. Together, these elements determine what services and features the plugin provides to a Tangelo server instance, and how those services and features are prepped and cleaned up. A configuration file supplied to Tangelo at startup time controls which plugins are loaded.
An example plugin’s file contents might be as follows:
foobar/
control.py
config.yaml
requirements.txt
info.txt
python/
__init__.py
helper.py
web/
foobar.js
foobar.py
example/
index.html
index.js
We can examine the contents piece by piece.
Web Content¶
The directory foobar/web
behaves much like any other static and dynamic
content served by Tangelo. Content in this directory is served from a base URL
of /plugin/foobar/
(where, foobar
is the name of this plugin; however,
see Configuration). For example, /plugin/foobar/foobar.js
refers to the
file of the same name in the web
directory; this URL could be used by a web
application to include this file in a <script>
tag, etc. (see
Serving Web Content for more information).
Dynamic web services also behave as elsewhere: the URL
/plugin/foobar/foobar
will cause Tangelo to run the code found in
foobar.py
and return it to the client, etc. (see Tangelo Web Services for
more information).
Python Content¶
A plugin may also wish to export some Python code for use in web services. In
the foobar plugin example, such content appears in
foobar/python/__init__.py
. This file, for example, might contain the
following code:
import helper
def even(n):
return n % 2 == 0
When the foobar plugin is loaded by Tangelo, the contents of
python/__init__.py
are placed in a virtual package named
tangelo.plugin.foobar
. This enables web services to use the even()
functions as in the following example:
import tangelo
import tangelo.plugin.foobar
def run(n):
tangelo.content_type("text/plain")
return "even" if tangelo.plugin.foobar.even(n) else "odd"
To export “submodules” that will appear in the tangelo.plugin.foobar
namespace, note that __init__.py
uses the import
statement to cause the
helper
module to appear within its scope; this module can now be addressed
with tangelo.plugin.foobar.helper
, and any functions and data exported by
helper
will become available for use in web services as well.
The bundled bokeh plugin contains an example of exporting a decorator function using this technique.
Setup and Teardown¶
The file foobar/control.py
defines setup and teardown actions for each
plugin. For example, the contents of that file might be as follows:
import tangelo
def setup(config, store):
tangelo.log("FOOBAR", "Setting up foobar plugin!")
def teardown(config, store):
tangelo.log("FOOBAR", "Tearing down foobar plugin!")
Whenever Tangelo loads (unloads) the foobar plugin, it will import
control.py
as a module and execute any setup()
(teardown()
) function
it finds, passing the configuration and persistent storage (see
Configuration) to it as arguments. If during setup the function raises
any exception, the exception will be printed to the log, and Tangelo will
abandon loading the plugin and move to the next one.
The setup()
function can also cause arbitrary CherryPy applications to be
mounted in the plugin’s URL namespace. setup()
can optionally return a list
of 3-tuples describing the applications to mount. Each 3-tuple should contain a
CherryPy application object, an optional configuration object associated with
the application, and a string describing where to mount the application. This
string will automatically be prepended with the base URL of the plugin being set
up. For instance:
import tangelo.plugin.foobar
def setup(config, store):
app = tangelo.plugin.foobar.make_cherrypy_app()
appconf = tangelo.plugin.foobar.make_config()
return [(app, appconf, "/superapp")]
When the foobar
plugin is loaded, the URL /plugin/foobar/superapp
will
serve the CherryPy application implemented in app
. Any such applications
are also unmounted when the plugin is unloaded.
Configuration¶
Plugin configuration comes in two parts: specifying which plugins to load, and specifying particular behavior for each plugin.
Enabling Plugins¶
The Tangelo executable has an option --plugin-config
that specifies a
plugin configuration file. This defaults to /etc/tangelo/plugin.conf
.
The file is a YAML configuration file consisting of a list of objects, one for each
plugin under consideration. The objects themselves are relatively simple:
- name: foobar
enabled: true
path: /path/to/foobar/plugin
- name: quux
enabled: false
path: path/to/quux
Each contains a required name
property, an optional enabled
boolean flag
(which, if omitted, defaults to true
), and a string path
property
describing where to find the plugin materials (i.e., the example directory shown
above). Whenever this file changes and a client visits any plugin URL, Tangelo
will compare the set of plugins enabled by the configuration file to the set of
plugins currently enabled, and will load and unload plugins to bring the running
plugins up to date. For example, if you edit the example file above to change
quux‘s enabled
flag to true
, then visit /plugin
, Tangelo will
first load the quux plugin, then return a list of running plugins, which will
now include quux. Conversely, if you also changed foobar‘s enabled
flag
to false
(or comment out, or delete foobar‘s entire section), foobar
will additionally be unloaded.
Plugin Setup¶
Some plugins may need to be set up before they can be properly used. Plugin setup consists of two steps: installing Python dependencies, if any, and consulting any informational messages supplied by the plugin.
In the example foobar plugin, note that the root directory includes a
requirements.txt
file. This is simply a pip requirements file declaring
what Python packages the plugin needs to run. You can install these with a
command similar to pip install -r foobar/requirements.txt
.
Secondly, some plugins may require some other action to be taken before they
work. The plugin authors can describe any such instructions in the info.txt
file. After installing the requirements, you should read this file to see if
anything else is required. For instance, a package may need to bootstrap after
it’s installed by fetching further resources or updates from the web; in this
case, info.txt
would explain just how to accomplish this bootstrapping.
These steps constitute a standard procedure when retrieving a new plugin for use
with your local Tangelo installation. For instance, if the foobar plugin
resides in a GitHub repository, you would first find a suitable location on your
local computer to clone that repository. Then you would invoke pip
to
install the required dependencies, then take any action specified by
info.txt
, and finally create an entry in the Tangelo plugin configuration
file. When Tangelo is started (or when the plugin registry is refreshed), the
new plugin will be running.
Configuring Plugins¶
The file foobar/config.yaml
describes a YAML associative array representing
the plugin’s configuration data. This is the same format as web service
configurations (see Configuring Web Services), and can be read with the function
tangelo.plugin_config()
.
Similarly, plugins also have a editable persistent store, accessed with the
tangelo.plugin_store()
function.
Both the configuration and the persistent store and passed as arguments to
setup()
and teardown()
in the control module.
Loading and Unloading¶
When plugins are loaded or unloaded, Tangelo takes a sequence of particular steps to accomplish the effect.
Loading a Plugin¶
Loading a plugin consists of the following actions:
- The configuration is loaded from
config.yaml
. - An empty persistent store is created.
- Any python content is set up by creating a virtual package called
tangelo.plugin.<pluginname>
, and exporting the contents ofpython/__init__.py
to it. - The
control.py
module is loaded, andcontrol.setup()
is invoked, passing the configuration and fresh persistent store to it. - If
setup()
returns a result, the list of CherryPy apps expressed in the"apps"
property of it are mounted. - The plugin name is added to the list of active plugins.
Steps 3, 4, and 5 are not taken if the corresponding content is not present. If any of those steps raises an exception, the exception is logged to disk and step 6 will not be taken (i.e., the plugin will not be loaded).
Unloading a Plugin¶
Unloading a plugins consists of the follow actions (which serve to undo the corresponding setup actions):
- Any python content present in
tangelo.plugin.<pluginname>
is torn down by deleting the virtual package from the runtime. - Any CherryPy applications are unmounted.
- If the control module contains a
teardown()
function, it is invoked, passing the configuration and persistent store to it. - The plugin name is removed from the list of active plugins.
If an exception occurs during step 3, the teardown()
function will not
finish executing, but step 4 will still be taken.