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 Allchin’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.

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.

Jinja Environment Plugins

class engineer.plugins.JinjaEnvironmentPlugin[source]

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.

get_logger()

Returns a logger for the plugin.

handle_settings(config_dict, settings)

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 for the current Engineer process. Any custom settings should be added to this object.
Returns:

The modified config_dict object.

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]

Base class for Post Processor Plugins.

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

get_logger()

Returns a logger for the plugin.

handle_settings(config_dict, settings)

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 for the current Engineer process. Any custom settings should be added to this object.
Returns:

The modified config_dict object.

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]

Base class for Theme Plugins.

ThemeProvider subclasses must provide a value for paths.

Changed in version 0.3.0.

get_logger()

Returns a logger for the plugin.

handle_settings(config_dict, settings)

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 for the current Engineer process. Any custom settings should be added to this object.
Returns:

The modified config_dict object.

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

class engineer.plugins.CommandPlugin[source]

Base class for Command Plugins.

Command plugins add new commands to the Engineer Commandline. CommandPlugin subclasses must provide an implementation for add_command(), and can optionally override the active() classmethod to determine whether or not the plugin should actually be loaded.

Note

Because Engineer uses argparse for parsing out its commands, you should be somewhat familiar with it in order to implement a Command plugin.

See also

Examples

classmethod active()[source]

If this method returns False, the plugin will not run and any commands added by the plugin will not be available.

This method can be overridden to make commands available only if certain criteria are met (for example, a custom setting).

Returns:A boolean value indicating whether or not the plugin is active and should run. Default implementation always returns True.
classmethod add_command(subparser, main_parser, common_parser)[source]

This method is called by Engineer while it is building its ArgumentParser, allowing one to add addition parsers and subparsers to supplement the core Engineer commands.

Parameters:
  • subparser – Since Engineer’s built-in commands are subparsers, add_subparsers() is called to generate a subparser. argparse only supports calling add_subparsers() once, so the subparser object itself (the result of the initial add_subparsers() call Engineer made when building its parser) is passed in this parameter. This allows you to add either another top-level command by calling add_parser() then adding arguments directly, or to create further nested commands by adding a parser with additional subparsers within it.
  • main_parser – The top level ArgumentParser used by Engineer. This is generally only useful if you’re using an argparse wrapper library such as argh in your plugin. Most wrapper libraries require the root ArgumentParser object to add their subparsers to. If you’re using argparse directly, you can ignore this parameter and work with the subparser parameter exclusively.
  • common_parser – Engineer provides several common arguments for its commands. If you wish to makes these arguments available for your custom commands, you should pass common_parser in to add_parser() via the parents parameter.
get_logger()

Returns a logger for the plugin.

handle_settings(config_dict, settings)

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 for the current Engineer process. Any custom settings should be added to this object.
Returns:

The modified config_dict object.

Examples