Cement defines a Plugin Interface, as well as the default CementPluginHandler that implements the interface.
Cement Extensions that Provide Plugin Handlers:
The following settings under the application's primary configuration section modify plugin handling:
A directory path where plugin code can be found. Will be prepended to
The following options under
App.Meta modify plugin handling:
A hardcoded list of plugins to load. In general, application plugins should be dynamically enabled/disabled via the application's configuration files. However, some application designs may prefer to always load specific builtin plugins. Default:
Plugin configuration files are loaded from any discovered application configuration directories.
A python module (dotted import path) where plugin code can be loaded from instead of external directories (builtin plugins shipped with the application code). Default:
A list of directory paths where plugin code (modules) can be loaded from (external to the application). Will be merged with
The plugin handler can be used to access information about loaded plugins, as well as manually loading plugins if necessary.
from cement import Appwith App('myapp') as app:# list loaded pluginsapp.plugin.get_loaded_plugins()# list enabled pluginsapp.plugin.get_enabled_plugins()# list disabled pluginsapp.plugin.get_disabled_plugins()# load a pluginapp.plugin.load_plugin('myplugin')# load a list of pluginsapp.plugin.load_plugins(['myplugin1','myplugin2'])
The plugin system is a mechanism for dynamically loading code to extend the functionality of a specific application. In general, this includes the registration of interfaces, handlers, and/or hooks but can include controllers, command-line options, or anything else.
The preferred method of creating a plugin would be via the included developer tools:
$ cement generate plugin /path/to/myapp/plugins
This will produce an example plugin directory like the following:
├── __init__.py├── controllers│ ├── __init__.py│ └── myplugin.py└── templates├── __init__.py└── plugins└── myplugin└── command1.jinja2
The example plugin includes a controller, sub-command, and output generated via Jinja2 template. That said, the only thing Cement needs is a
load() function.... everything else is arbitrary. In the generated plugin, we find this in
import osfrom .controllers.myplugin import MyPlugindef add_template_dir(app):path = os.path.join(os.path.dirname(__file__), 'templates')app.add_template_dir(path)def load(app):app.handler.register(MyPlugin)app.hook.register('post_setup', add_template_dir)
You will notice that plugins are essentially the same as framework extensions. The difference is found both in when/how the code is loaded, as well as the purpose of that code.
Plugin modules are discovered and loaded in the following order:
From directories listed in
From the python path defined in
In order for the framework to know about a plugin, it must be defined in the application's configuration settings under its designated section of
plugin.myplugin. This configuration block can live in any application configuration file, including files loaded from configuration dirs (ex:
myapp.pyfrom cement import Appclass MyApp(App):class Meta:label = 'myapp'plugin_dirs = ['./plugins']with MyApp() as app:app.run()
plugins/myplugin.pydef load(app):print('Inside MyPlugin!')
$ python myapp.pyInside MyPlugin!...
If a plugin configuration is found, its settings will be loaded into the app. However, the plugin will only be loaded if it is enabled:
[myapp]# ...[plugin.myplugin]enabled: true
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 paths. For example:
myplugin/__init__.pyfrom .controllers import MyPluginControllerdef load(app):app.handler.register(MyPluginController)
This will ensure that Python will properly load the sub-modules regardless of where they live on the filesystem (or within a project's own modules, etc).
In order for a plugin to use its own template files, its templates directory first needs to be registered with the app. We accomplish this with a
myplugin/__init__.pyimport osdef 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)
All interfaces in Cement can be overridden with your own implementation. This can be done either by sub-classing
PluginHandler itself, or by sub-classing an existing extension's handlers in order to alter their functionality.
myapp.pyfrom cement import Appfrom cement.core.plugin import PluginHandlerclass MyPluginHandler(PluginHandler):class Meta:label = 'my_plugin_handler'# do something to implement the interfaceclass MyApp(App):class Meta:label = 'myapp'plugin_handler = 'my_plugin_handler'handlers = [MyPluginHandler,]