Plugins

Engineer provides a plugin model that allows further customization of Engineer behavior. While Engineer contained a rudimentary plugin system for themes in version 0.2.3, version 0.3.0 introduced a much richer system and exposed ways to modify the post rendering pipeline somewhat in addition to Theme plugins.

Engineer’s plugin model is based on Marty Alchin’s simple plugin framework. As such, creating your own plugin is relatively straightforward:

  1. Subclass one of the available plugin base classes (e.g. PostProcessor)
  2. Load the module containing your plugin during an Engineer operation

Step 1 is quite simple. Step 2 is slightly more involved, but you have a couple of options for your plugin.

Loading Plugins

In order for plugins to be found, the module containing them must be imported by Engineer. Engineer provides two ways to achieve this. First, you can use the PLUGINS setting. Each module passed in via that setting will be imported, and the plugins they contain will be available to Engineer.

Tip

Command plugins are the exception; they cannot be loaded using the PLUGINS setting. They must be installed as a Python package using the method below.

Alternatively, you can deliver your plugin as an installable Python package. This allows users to download and install your theme via pip or any other tool. You can do this by adding an engineer.plugins setuptools entry point to your setup.py file.

In particular, within the setup function call in your setup.py file, add something like the following:

entry_points={
    'engineer.plugins': ['post_processors=dotted.path.to.module',
                         'themes=another.module.path'],
    }

The above code registers two plugin modules with Engineer. Engineer will import these modules, and any subclasses of the plugin base classes will be automatically discovered and run with Engineer.

The identifiers to the left of the equals sign (i.e. post_processors and themes) can be anything at all. Engineer doesn’t look at them or use them. For clarity, in the above example the plugins have been broken into different modules by type, and each module has an identifier based on the type of plugin that module contains. But again, this is not required. It could just as easily read:

['foo=dotted.path.to.module',
 'bar=another.module.path']

The type of the plugin is determined by its parent class, not by its module or a specific identifier in the setup function.

Tip

The only requirement to get your plugin loaded is for the module containing it to be imported. Thus, if you have a number of plugins in different modules, you could create a wrapper module that simply imported the others, then sent your entry point to point to the wrapper module. When the wrapper module is imported, the other modules will also be imported, and then your plugins will be magically loaded.

Plugin Permissions

Some plugin capabilities are restricted and require explicit permission from the Engineer user via the PLUGIN_PERMISSIONS setting. As of Engineer 0.5.0 there is only one permission available, MODIFY_RAW_POST.

Prior to Engineer 0.5.0, it was not possible for plugins to modify actual post content. The Metadata Finalization plugin modified post metadata, but post content itself was never changed. This was a deliberate design decision to try and prevent data loss from runaway plugins. It was especially helpful during plugin testing when bugs weren’t yet found and fixed.

In Engineer 0.5.0, plugins can now modify post content using the set_finalized_content() method on the Post class. However, this is protected by the MODIFY_RAW_POST permission. If the plugin is not explicitly listed as having that permission in the user’s config, then calls to set_finalized_content will do nothing.

Note

The plugin permissions system is a little clunky and overly protective. The intent of the system is to help prevent plugins from doing potentially damaging things (like editing post source content) without explicit permission from the user. However, it’s possible that I’m being paranoid and that this is overkill. Thus, consider this ‘experimental’ in Engineer 0.5.0. It may go away in the future; I welcome feedback on this.

New in version 0.5.0.

Common Plugin Methods

All plugins inherit the some methods from PluginMixin. Note that you should not subclass the mixin yourself; rather, you should subclass one of the relevant plugin base classes below. The PluginMixin class is documented only for completeness.

class engineer.plugins.core.PluginMixin[source]
classmethod get_logger(custom_name=None)[source]

Returns a logger for the plugin.

classmethod handle_settings(config_dict, settings)[source]

If a plugin defines its own settings, it may also need to handle those settings in some unique way when the Engineer configuration files are being read. By overriding this method, plugins can ensure such unique handling of their settings is done.

Note that a plugin does not have to handle its own settings unless there is unique processing that must be done. Any settings that are unknown to Engineer will automatically be added as attributes on the EngineerConfiguration object. This method should only be implemented if the settings must be processed in some more complicated way prior to being added to the global configuration object.

Implementations of this method should check for the plugin-specific settings in config_dict and set appropriate attributes/properties on the settings object. In addition, settings that have been handled should be removed from config_dict. This ensures they are not handled by other plugins or the default Engineer code.

Parameters:
  • config_dict – The dict of as-yet unhandled settings in the current settings file.
  • settings – The global EngineerConfiguration object that contains all the
  • settings – The global EngineerConfiguration object that contains all the settings for the current Engineer process. Any custom settings should be added to this object.
Returns:

The modified config_dict object.

Jinja Environment Plugins

class engineer.plugins.JinjaEnvironmentPlugin[source]

Bases: engineer.plugins.core.PluginMixin

Base class for JinjaEnvironment Plugins.

JinjaEnvironment plugins can supplement the Jinja 2 environment with things like filters and global functions. These additions can then be used in your Jinja templates.

New in version 0.5.0.

classmethod get_filters()[source]

If required, subclasses can override this method to return a dict of filters to add to the Jinja environment. The default implementation simply returns filters.

classmethod get_globals()[source]

If required, subclasses can override this method to return a dict of functions to add to the Jinja environment globally. The default implementation simply returns globals.

classmethod update_environment(jinja_env)[source]

For complete customization of the Jinja environment, subclasses can override this method.

Subclasses should ensure that the base implementation is called first in their overridden implementation. For example:

@classmethod
def update_environment(cls, jinja_env):
    super(BundledFilters, cls).update_environment(jinja_env)
    # some other code here...
Parameters:jinja_env – The Jinja environment.
filters = {}

A dict of filters to add to the Jinja environment. The key of each entry should be the name of the filter (as it will be used inside templates), while the value should be the filter function. If you require more custom logic to build the dict of filters, override the get_filters() method.

globals = {}

A dict of functions to add to the Jinja environment globally. The key of each entry should be the name of the function (as it will be used inside templates), while the value should be the function itself. If you require more custom logic to build this dict, override the get_globals() method.

Post Processor Plugins

class engineer.plugins.PostProcessor[source]

Bases: engineer.plugins.core.PluginMixin

Base class for Post Processor Plugins.

PostProcessor subclasses should provide implementations for preprocess() or postprocess() (or both) as appropriate.

classmethod postprocess(post)[source]

The postprocess method is called after the post has been imported and processed as well as converted to HTML and output.

Parameters:post – The post being currently processed by Engineer.
Returns:The post parameter should be returned.
classmethod preprocess(post, metadata)[source]

The preprocess method is called during the Post import process, before any post metadata defaults have been set.

The preprocess method should use the content_preprocessed attribute to get/modify the content of post. This ensures that preprocessors from other plugins can be chained together.

By default, the content_preprocessed value is used only for generating post HTML. It is not written back to the source post file. However, sometimes you may want to make a permanent change to the post content that is written out. In this case, you should call the set_finalized_content() method, passing it the modified content. This method will ensure the data is written back to the source file by the Metadata Finalization plugin. This means that in order for a plugin to write preprocessed data back to the post file, the FINALIZE_METADATA setting must be enabled.

Your plugin will also need to be explicitly granted the MODIFY_RAW_POST permission. See more detail in Plugin Permissions.

In addition, the preprocess method can add/remove/update properties on the post object itself as needed.

Tip

Since the FINALIZE_METADATA setting must be enabled for plugins to write back to source post files, you should check this setting in addition to any other settings you may be using.

Parameters:
  • post – The post being currently processed by Engineer.
  • metadata – A dict of the post metadata contained in the post source file. It contains no default values - only the values contained within the post source file itself. The preprocess method can add, update, or otherwise manipulate metadata prior to it being processed by Engineer manipulating this parameter.
Returns:

The post and metadata values should be returned (as a 2-tuple) by the method.

Theme Plugins

class engineer.plugins.ThemeProvider[source]

Bases: engineer.plugins.core.PluginMixin

Base class for Theme Plugins.

ThemeProvider subclasses must provide a value for paths.

Changed in version 0.3.0.

paths = ()

An iterable of absolute paths containing one or more theme manifests.

The actual Python code needed to register your theme as a plugin is very minimal, but it is overhead compared to simply downloading a theme directly. The benefit, of course, is that users can manage the installation of the theme alongside Engineer itself, and since the theme is globally available, users don’t need to copy the theme to each site they want to use it in.

Command Plugins

The Engineer command line can be customized to include your own commands. See Command Plugins for more information.

Changed in version 0.6.0: Command plugins changed dramatically in version 0.6.0 and are now documented separately.