Plugin Development

This section has guidelines on creating extension plugins for pyetta.

Click Subcommands

The click commands used by pyetta subclass a special implementation of both click groups and commands. This allows for special properties to be injected in via the provided decorators.

class pyetta.cli.utils.PyettaCommand(category: str = 'Commands', plugin_name: Optional[str] = None, *args, **kwargs)

Bases: click.core.Command

Command base for pyetta commands, use this as your class injection for click commands.

This uses the category property to allow proper help text to group the command types.

__init__(category: str = 'Commands', plugin_name: Optional[str] = None, *args, **kwargs)
Parameters
  • category – The category this plugin belongs to. This shows up in the help page.

  • plugin_name – Name given to the plugin loading this command. Used in the help function to help with filtering.

To use this specialisation within the plugin, the standard click decorators may be used. See the foo_plugin.py Example for usage.

Magic Method (load_plugin)

The load_plugin is a magic method that the cli tool will attempt to find after loading your python module from the relevant places. If this method is not found or an exception is thrown during the loading of a plugin, a ImportError will be thrown instead and the cli tool will terminate.

Warning

Because of this behaviour, misbehaved plugins can break the cli tool. Consider uninstalling the plugin if this occurs, or use the option flag --ignore-plugins with the name of the plugins. This flag has to be used before the extras flag as they are evaluated in the order of occurrence.

Loading Plugins

Plugins can be loaded in 2 distinct ways.

  • Using the --extras flag.

  • Using the entrypoint group pyetta.plugins.

Note

Any plugin developed to be used as a plugin can be directly invoked using the --extras flag. No additional development is needed.

Using Entry Point pyetta.plugins

A plugin can be loaded using the entrypoint. This allows for plugin developers to distribute their plugins using PyPI or any other python package repository. pyetta uses this mechanism to load its own builtin handlers, which can be seen from the snippet in the project pyproject.toml manifest.

[project.entry-points."pyetta.plugins"]
_builtins = "pyetta._builtins"

A plugin package may support multiple entry points. All of which can have their own load plugin entry points. To enforce consistency, pyetta plugin entries should be modules which have the Magic Method (load_plugin) as part of their members. The system will extract this method and call it when loading the CLI tool.

Using --extras

The CLI flag allows developers to include project specific plugins that do not need to be distributed. This allows for once of for hyper specific stages to be implemented as part of the firmware development (such as a customer firmware update process).

foo_plugin.py Example

The foo_plugin.py example shows a module that is designed as a sample for what a plugin might look like.

Tip

See the examples folder for examples of plugins. This guide only covers the basics via the foo_plugin.py file which is also located within that folder.

Adding Sub Commands

Subcommands can be added to the CLI by creating them using click, then injecting the commands into the system via the Magic Method (load_plugin).

The snippet from examples/foo_plugin.py shows a new command being added into the system. Executing the --help option on pyetta will result in this new loader being present.

 1@click.command("lfoo",
 2               help="Loader for the foo.",
 3               cls=PyettaCommand, category='Loaders',
 4               plugin_name="foo_plugin")
 5def lfoo() -> ExecutionCallable:
 6    """Custom click based cli command. This will be added dynamically at
 7    runtime but can take any options typical to the cli.
 8
 9    Given the pipeline architecture, ensure this returns a callable that
10    registers the component to the pipeline stage."""
11
12    @execution_config
13    def configure_pipeline(_: Context,
14                           pipeline: ExecutionPipeline) -> None:
15        click.echo("Running the foo loader!")
16        pipeline.loader = Loader()
17
18        # if the resource to register is a context manager, use click's
19        # context.with_resource() function to register and load it.
20
21    return configure_pipeline

All defined objects and classes will not have any effect until they are loaded. This can be done by calling the helper method add_command_to_cli.

1def load_plugin():
2    """Magic method to load the plugin to the system. This is called by the CLI
3    prior to invoking any command."""
4    add_command_to_cli(lfoo)

Debugging and Validating Plugins

Plugins can be debugged by either running the cli tool in debug mode, or by creating a script that acts as a wrapper around the entry point.

An example of such a script is the entry point python script used to call pyetta itself. This can be called by importing and calling the pyetta.__main__.main() function.

 1import logging
 2
 3from pyetta.cli.cli import cli
 4
 5logging.basicConfig(level=logging.ERROR)
 6
 7
 8def main():
 9    cli()
10
11
12# no need to check if name is main, as our file name is explicit.
13main()

Validation at a high level can be performed by testing the correct loading of the plugin. Plugins are all loaded before the --help dialog, so any loaded plugins will be visible in the top level help. For the plugin example, we will see the lfoo loader present in the system.

The [plugin: foo_plugin] hint is provided by the plugin loader to inform the user which commands are provided by which plugins.

Usage: cli_entry.py [OPTIONS] STAGE1 [ARGS]... [STAGE2 [ARGS]...]...

  Python Embedded Test Toolbox and Automation

...

Loaders:
  ...
  lfoo    Loader for the foo. [plugin: foo_plugin]
  ...

...