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.
Unlike other plugin types, Command plugins cannot be loaded using
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
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
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.
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.
In order to implement an argparse-based plugin, you should subclass
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.bundledmodule for a specific example.
Returns a logger for the plugin.
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
EngineerConfigurationobject. 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_dictand set appropriate attributes/properties on the
settingsobject. 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.
- config_dict – The dict of as-yet unhandled settings in the current settings file.
- settings – The global
EngineerConfigurationobject that contains all the
- settings – The global
EngineerConfigurationobject that contains all the settings for the current Engineer process. Any custom settings should be added to this object.
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_functionto 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
The help string for the command.
The name of the command.
Defaults to True. Set to False if the command does not require an Engineer config file.
Defaults to True. Set to False if the command does not support the standard Engineer
Returns the appropriate parser to use for adding arguments to your command.