Application Plugins

Cement defines a plugin interface called IPlugin, as well as the default CementPluginHandler that implements the interface.

Please note that there may be other handlers that implement the IPlugin interface. The documentation below only references usage based on the interface and not the full capabilities of the implementation.

The following plugin handlers are included and maintained with Cement:

Please reference the IPlugin interface documentation for writing your own plugin handler.

Plugin Configuration Settings

There are a few settings related to how plugins are loaded under an applications meta options. These are:

plugins = []
A list of plugins to load. This is generally considered bad practice since plugins should be dynamically enabled/disabled via a plugin config file.
plugin_config_dirs = None

A list of directory paths where plugin config files can be found. Files must end in .conf (or the extension defined by CementApp.Meta.config_extension), or they will be ignored.

Note: Though CementApp.Meta.plugin_config_dirs is None, Cement will set this to a default list based on CementApp.Meta.label. This will equate to:

['/etc/<app_label>/plugins.d', '~/.<app_label>/plugin.d']

Files are loaded in order, and have precedence in that order. Therefore, the last configuration loaded has precedence (and overwrites settings loaded from previous configuration files).

plugin_config_dir = None

A directory path where plugin config files can be found. Files must end in .conf (or the extension defined by CementApp.Meta.config_extension), or they will be ignored. By default, this setting is also overridden by the [<app_label>] -> plugin_config_dir config setting parsed in any of the application configuration files.

If set, this item will be appended to CementApp.Meta.plugin_config_dirs so that it’s settings will have presedence over other configuration files.

In general, this setting should not be defined by the developer, as it is primarily used to allow the end-user to define a plugin_config_dir without completely trumping the hard-coded list of default plugin_config_dirs defined by the app/developer.

plugin_bootstrap = None

A python package (dotted import path) where plugin code can be loaded from. This is generally something like myapp.plugins where a plugin file would live at myapp/plugins/myplugin.py or myapp/plugins/myplugin/__init__.py. This provides a facility for applications that have builtin plugins that ship with the applications source code and live in the same Python module.

Note: Though the meta default is None, Cement will set this to <app_label>.plugins if not set.

plugin_dirs = None

A list of directory paths where plugin code (modules) can be loaded from.

Note: Though CementApp.Meta.plugin_dirs is None, Cement will set this to a default list based on CementApp.Meta.label if not set. This will equate to:

['~/.<app_label>/plugins', '/usr/lib/<app_label>/plugins']

Modules are attempted to be loaded in order, and will stop loading once a plugin is successfully loaded from a directory. Therefore this is the oposite of configuration file loading, in that here the first has precedence.

plugin_dir = None

A directory path where plugin code (modules) can be loaded from. By default, this setting is also overridden by the [<app_label>] -> plugin_dir config setting parsed in any of the application configuration files.

If set, this item will be prepended to Meta.plugin_dirs so that a users defined plugin_dir has precedence over others.

In general, this setting should not be defined by the developer, as it is primarily used to allow the end-user to define a plugin_dir without completely trumping the hard-coded list of default plugin_dirs defined by the app/developer.

Creating a Plugin

A plugin is essentially an extension of a Cement application, that is loaded from an internal or external source location. It is a mechanism for dynamically loading code (whether the plugin is enabled or not). It can contain any code that would normally be part of your application, but should be thought of as optional features, where the core application does not rely on that code to operate.

The following is an example plugin (single file) that provides a number of options and commands via an application controller:

myplugin.py

from cement.core.controller import CementBaseController, expose

class MyPluginController(CementBaseController):
    class Meta:
        label = 'myplugin'
        description = 'this is my controller description'
        stacked_on = 'base'

        config_defaults = dict(
            foo='bar',
            )

        arguments = [
            (['--foo'],
             dict(action='store', help='the infamous foo option')),
            ]

    @expose(help="this is my command description")
    def mycommand(self):
        print 'in MyPlugin.mycommand()'

def load(app):
    app.handler.register(MyPluginController)

As you can see, this is very similar to an application that has a base controller, however as you’ll note we do not create an application object via foundation.CementApp() like we do in our application. This code/file would then be saved to a location defined by your applications configuration that determines where plugins are loaded from (see the next section).

Notice that all ‘bootstrapping’ code goes in a load() function. This is where registration of handlers/hooks should happen. For convenience, and certain edge cases, the app object is passed here in its current state at the time that load() is called. You do not need to do anything with the app object, but you can.

A plugin also has a configuration file that will be Cement will attempt to find in one of the directories listed in CementApp.Meta.plugin_config_dirs as defined by your application’s configuration. The following is an example plugin configuration file:

myplugin.conf

[myplugin]
enable_plugin = true
foo = bar

Loading a Plugin

Plugin modules are looked for first in one of the defined plugin_dirs, and if not found then Cement attempts to load them from the plugin_bootstrap. The following application shows how to configure an application to load plugins. Take note that these are the default settings and will work the same if not defined:

from cement.core.foundation import CementApp
from cement.core.controller import CementBaseController, expose

class MyBaseController(CementBaseController):
    class Meta:
        label = 'base'
        description = 'MyApp Does Amazing Things'

class MyApp(CementApp):
    class Meta:
        label = 'myapp'
        base_controller = MyBaseController
        plugin_bootstrap='myapp.bootstrap',
        plugin_config_dirs=[
            '/etc/myapp/plugins.d',
            '~/.myapp/plugins.d',
            ]
        plugin_dirs=[
            '/usr/lib/myapp/plugins',
            '~/.myapp/plugins',
            ]


def main():
    with MyApp() as app:
        app.run()

if __name__ == '__main__':
    main()

We modified the default settings for plugin_config_dirs and plugin_dirs. These are the default settings under Cementapp, however we have put them here for clarity.

Running this application will do nothing particularly special, however the following demonstrates what happens when we add a simple plugin that provides an application controller:

/etc/myapp/plugins.d/myplugin.conf

[myplugin]
enable_plugin = true
some_option = some value

/usr/lib/myapp/plugins/myplugin.py

from cement.core.controller import CementBaseController, expose
from cement.utils.misc import init_defaults

defaults = init_defaults('myplugin')

class MyPluginController(CementBaseController):
    class Meta:
        label = 'myplugin'
        description = 'this is my plugin description'
        stacked_on = 'base'
        config_defaults = defaults
        arguments = [
            (['--some-option'], dict(action='store')),
            ]

    @expose(help="this is my command description")
    def my_plugin_command(self):
        print 'In MyPlugin.my_plugin_command()'

def load(app):
    app.handler.register(MyPluginController)

Running our application with the plugin disabled, we see:

$ python myapp.py --help
usage: myapp.py (sub-commands ...) [options ...] {arguments ...}

MyApp Does Amazing Things

optional arguments:
  -h, --help  show this help message and exit
  --debug     toggle debug output
  --quiet     suppress all output

But if we enable the plugin, we get something a little different:

$ python myapp.py --help
usage: myapp.py (sub-commands ...) [options ...] {arguments ...}

MyApp Does Amazing Things

commands:

  my-plugin-command
    this is my command description

optional arguments:
  -h, --help            show this help message and exit
  --debug               toggle debug output
  --quiet               suppress all output
  --some-option SOME_OPTION

We can see that the my-plugin-command and the --some-option option were provided by our plugin, which has been ‘stacked’ on top of the base controller.

User Defined Plugin Configuration and Module Directories

Most applications will want to provide the ability for the end-user to define where plugin configurations and modules live. This is possible by setting the plugin_config_dir and plugin_dir settings in any of the applications configuration files. Note that these paths will be added to the built-in plugin_config_dirs and plugin_dirs settings respectively, rather than completely overwriting them. Therefore, your application can maintain it’s default list of plugin configuration and module paths while also allowing users to define their own.

/etc/myapp/myapp.conf

[myapp]
plugin_dir = /usr/lib/myapp/plugins
plugin_config_dir = /etc/myapp/plugins.d

The plugin_bootstrap setting is however only configurable within the application itself.

What Can Go Into a Plugin?

The above example shows how to add an optional application controller via a plugin, however a plugin can contain anything you want. This could be as simple as adding a hook that does something magical. For example:

from cement.core import hook

def my_magical_hook(app):
    # do something magical
    print('Something Magical is Happening!')

def load(app):
    hook.register('post_setup', my_magical_hook)

And with the plugin enabled, we get this when we run the same app defined above:

$ python myapp.py
Something Magical is Happening!

The primary detail is that Cement calls the load() function of a plugin... after that, you can do anything you like.

Single File Plugins vs. Plugin Directories

As of Cement 2.9.x, plugins can be either a single file (i.e myplugin.py) or a python module directory (i.e. myplugin/__init__.py). Both will be loaded and executed the exact same way.

One caveat however, is that the submodules referenced from within a plugin directory must be relative path. For example:

myplugin/__init__.py

from .controllers import MyPluginController

def load(app):
    app.handler.register(MyPluginController)

myplugin/controllers.py

from cement.core.controller import CementBaseController, expose

class MyPluginController(CementBaseController):
    class Meta:
        label = 'myplugin'
        stacked_on = 'base'
        stacked_type = 'embedded'

    @expose()
    def my_command(self):
        print('Inside MyPluginController.my_command()')

Loading Templates From Plugin Directories

A common use case for complex applications is to use an output handler the uses templates, such as Mustache, Genshi, Jinja2, etc. In order for a plugin to use it’s own template files it’s templates directory first needs to be added to the list of template directories to be parsed. In the future, this will be more streamlined however currently the following is the recommeded way:

myplugin/__init__.py

def add_template_dir(app):
    path = os.path.join(os.path.basename(self.__file__, 'templates')
    app.add_template_dir(path)

def load(app):
    app.hook.register('post_setup', add_template_dir)

The above will append the directory /path/to/myplugin/templates to the list of template directories that the applications output handler with search for template files.