Tutorial: write a runner plugin

In this tutorial we describe step by step how to implement a tool Runner for bioimageit_core. By default the bioimageit_core library has two runners:

  • CONDA: a runner that execute process localy from conda environements. This means that the binaries of the process have to be installed locally with Conda.

  • DOCKER: a runner that execute a process from a Docker image. It needs Docker client to be installed.

Runners are implemented using the service design pattern. To make it easier to identify the services plugins in the python code repository we prefix the python plugin file with runner_. Thus, to create a new Runner you need to create a python file at bioimageit_core/plugins/runner_yourservicename.py

Then, the main class of the runner is in this single file. A runner service plugin file contains two classes: the ServiceBuilder and the RunnerService

Runner service builder

The service builder is a class that allows to instantiate and initialize a single runner service. The code bellow shows an example of runner service for the LocalServiceBuilder

class LocalRunnerServiceBuilder:
    """Service builder for the runner service"""
    def __init__(self):
        self._instance = None

    def __call__(self, **_ignored):
        if not self._instance:
            self._instance = LocalRunnerService()
        return self._instance

The constructor initialize a null instance of the LocalRunnerService, and the __call__ method instante a new LocalRunnerService. Thus, when the LocalRunnerServiceBuilder is called it is always the same instance of the RunnerService that is used.

Runner service

The runner service is the class that implements the runner functionalities. The code bellow shows the implementation of the LocalRunnerService. As we can see, a runner service has a 3 methodd called set_up, exec and tear_down. These methods have one common input: the Tool which is a class that contains all the metadata of the tool. The exec method has one extra argument called args that is the list of the command line arguments. For the example of local runner, we just call the command line arguments with subprocess. We do not need the set_up and the tear_down methods to initialize and clean the run environement. Another more complex example of runner service implementation can be found at bioimageit_core/plugins/runner_docker.py. For the Docker case, the set_up method pull and run the Docker image, the exec method execute the command line in the Docker container, and the tear_down method stops and removes the container.

class LocalRunnerService:
    """Service for local runner exec

    To initialize the database, you need to set the xml_dirs from
    the configuration and then call initialize

    """
    def __init__(self):
        super().__init__()
        self.service_name = 'LocalRunnerService'

    def set_up(self, process: Tool):
        """setup the runner

        Add here the code to initialize the runner

        Parameters
        ----------
        process
            Metadata of the process

        """
        pass

    def exec(self, process:Tool, args):
        """Execute a process

        Parameters
        ----------
        process
            Metadata of the process
        args
            list of arguments

        """
        subprocess.run(args)

    def tear_down(self, process: Tool):
        """tear down the runner

        Add here the code to down/clean the runner

        Parameters
        ----------
        process
            Metadata of the process

        """
        pass

Register the runner

The last step is to register the runner to the bioimageit_core runner factory. Open the file bioimageit_core/plugins/runners_factory.py, and add a line at the end to register the runner:

runnerServices.register_builder('LOCAL', LocalRunnerServiceBuilder())

In the example above, the string 'LOCAL' is the name of the runner. Then, if we want to use this runner, we need to specify it in the config file:

...
"process": {
    "service": "LOCAL",
...

Summary

To summarize, in order to create a new Runner we need to follow these steps:

  • create a python file in bioimageit_core/plugins/runner_name.py

  • implement a RunnerServiceBuilder class.

  • implement a RunnerService class.

  • register the runner at bioimageit_core/plugins/runners_factory.py

  • setup the config.json file with the new builder to be able to use it