Writing plugins for mokapot

This is a tutorial on how to write plugins for mokapot. The plugin architecture allows you to write Python packages that extend mokapot without requiring any changes to mokapot itself. Currently, it allows you to modify the data before it gets used by the models or implement a new model.

Plugins are python packages that export a BasePlugin object to an entry point called mokapot.plugins. It requires setting an entry point up in your package configuration, which we will walk you though together. This entails adding a specification in your package configuration that lets your build system know that one of your classes should be “installed” inside mokapot; once installed, mokapot will be able to find your plugin and use it.

Configuration for you plugin

If using setuptools with setup.py you can do that like this::

setup(
    ...
    entry_points={
        'mokapot.plugins': [
            'myplugin = my_package:MyPluginClass',
        ],
    },
)

If using setuptools with pyproject.toml you can do that like this::

[project.entry-points."mokapot.plugins"]
myplugin = "my_package:MyPluginClass"

If using poetry you will need these lines in your pyproject.toml::

[tool.poetry.plugins."mokapot.plugins"]
"myplugin" = "my_package:MyPluginClass"

In these examples myplugin is the name you wish to give to your plugin and my_package:MyPluginClass is the name of the class you wish to export.

Writing your plugin

An example plugin can be found in the tests for mokapot. Each plugin must implement the BasePlugin class. You do not need to implement all of the methods, but you can if you want to.:

class Plugin(BasePlugin):
    def add_arguments(parser: _ArgumentGroup) -> None:
        # Define any arguments your plugin may need
        parser.add_argument(
            "--yell", action="store_true", help="Whether to yell into the command line while executing"
        )

    def get_model(self, config) -> mokapot.model.Model:
        # Define any models, they should be a subclass of
        # mokapot.model.Model
        if config.yell:
            LOGGER.warning("I LOVE WRITTING PLUGINS")
        return PluginModel(
            train_fdr=config.train_fdr,
            max_iter=config.max_iter,
            direction=config.direction,
            override=config.override,
            subset_max_train=config.subset_max_train,
        )

    def process_data(self, data, config):
        # Modify the data before it gets used by the model
        LOGGER.info("Processing data within the plugin")
        return data

These methods control the following: - The add_arguments() method defines new command line arguments that will be used by your plugin. - The get_model() method creates a new mokapot model that will be used. - The process_data() method adds data processing steps that are executed before model training.

Using your plugin

Once mokapot and your plugin are installed in the same Python environment you can use your plugin by requesting it on the command line::

mokapot ... --plugin myplugin ...

In the case of the example plugin, called mokapot_ctree you can use it like this::

mokapot ... --plugin mokapot_ctree ...

Note that when calling mokapot with the –help option, the arguments for the plugin will show up in the bottom as a new section::

mokapot --help
...
mokapot_ctree:
  --yell               Whether to yell into the command line while executing