Command Plugins

There are many cases where it may be useful to enhance Engineer by adding new commands to its command line interface. Engineer provides a way for you to do this fairly easily using the same model as other plugins. In fact, the core Engineer commands are implemented as plugins that come bundled with Engineer.

Tip

Unlike other plugin types, Command plugins cannot be loaded using the PLUGINS setting. They must be installed as a Python package and are therefore available to all sites using a given Engineer installation.

Command Plugin Types

Engineer provides two forms of command extensibility: argparse and argh. The argparse style will be familiar to anyone who has used argparse, the command-line processing module in the Python standard library.

The alternative is to use argh, which is a wrapper around argparse that provides a great deal of simplifying functionality while retaining the power inherent in argparse.

So, what style should you use? In general, I recommend using argh if you’re new to Python or command-line handling in general. I think it’s much simpler to use, and it requires a lot less boilerplate code. While none of the core Engineer commands use argh, there are examples below that will guide you through the process.

One very minor drawback to argh is that as of Engineer version 0.6.0, it is not included as a dependency and thus is not installed by default. This means that you’ll need to include it as a dependency in your own plugin’s package. An upcoming version of Engineer may include it so this would no longer be necessary.

The core Engineer commands were originally written before the command plugin model existed, and were written to use argparse directly. Depending on what you’re doing you might find it more powerful. Certainly if you already have experience with argparse, there’s no reason to go out and learn how to use argh unless you want to.

Basic Plugin Model

As with other plugin types, command plugins are implemented by subclassing a plugin base class. Unlike other plugins, however, there are multiple base classes to use. In addition, there is a more complex class hierarchy including some private mixin classes that are documented here for completeness, but that you shouldn’t need to subclass directly in your plugins.

The mixins and base classes abstract away most of the complexity of dealing with the guts of the parsers, and provide simple ways to plug in your own functions. In addition, you can also add the verbose and settings options that are available in most Engineer commands easily without implementing them yourself.

Engineer’s command processing is built using argparse. One of argparse’s ‘quirks,’ or design decisions/constraints, is that it is not easy to access subparsers arbitrarily. Essentially, you can only parsers very early on in the process of building the argparse objects. Thus, all command plugins are passed a main_parser, which is the main ArgumentParser object, when they are instantiated. In addition, they are passed the top-most subparser object, created by initially calling add_subparsers on the main ArgumentParser object. With these two components, it is possible to manipulate commands in diverse ways.

Fortunately, for the most part, plugin implementers needn’t be concerned with this detail, since it is abstracted away by various subclasses.

Tip

It is easiest to model each of your commands as a single independent class wherever possible. In many cases, this will be straightforward. If, however, you want to add a more complicated command, or a command with subcommands and argh, you can do this. See the examples below.

Argparse-based Plugins

In order to implement an argparse-based plugin, you should subclass ArgparseCommand.

class engineer.commands.core.ArgparseCommand(main_parser, top_level_parser=None)[source]

Bases: engineer.commands.core._ArgparseMixin

Serves as a base class for simple argparse-based commands. All built-in Engineer commands, such as engineer clean, are examples of this type of command. See the source for the classes in the engineer.commands.bundled module for a specific example.

get_logger(custom_name=None)

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 – 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.

handler_function(args=None)

This function contains your actual command logic. Note that if you prefer, you can implement your command function with a different name and simply set handler_function to be the function you defined. In other words:

def my_function(*args, **kwargs):
    # my implementation
    pass

handler_function = my_function

The built-in Engineer commands all use this approach. You can see the source for those classes in the engineer.commands.bundled module.

help

The help string for the command.

name

The name of the command.

need_settings

Defaults to True. Set to False if the command does not require an Engineer config file.

need_verbose

Defaults to True. Set to False if the command does not support the standard Engineer verbose option.

parser

Returns the appropriate parser to use for adding arguments to your command.

Argh-based Plugins

More Advanced Plugin Styles

class engineer.commands.core.Command(main_parser, top_level_parser=None)[source]

The most barebones command plugin base class. You should use ArgparseCommand or ArghCommand wherever possible.