Launcher Plugins

A basic plugin system that allow you to add new launchers. The workflow can be simply described as:

  • create a new python module at an arbitrary location

  • subclass BaseLauncher and BaseLauncherSerialized inside

  • make sure its parent location is registred in the PYTHONPATH

  • append the module name to the launcher_plugins configuration key

Which could be illustrated by the following commands:

cd /d/dev/my-kloch-plugins/
touch kloch_mylauncher.py
# edit its content ^
export PYTHONPATH=$PYTHONPATH:/d/dev/my-kloch-plugins/
export KLOCH_CONFIG_LAUNCHER_PLUGINS=kloch_mylauncher
# check if we succesfully registred our plugin
kloch plugins

As example you can check the content of:

  • {root}/tests/data/plugins-behr

  • {root}/tests/data/plugins-tyfa

Creating the subclasses

When creating the a new launcher you would need to create a dataclass object from it which subclass BaseLauncher, and it’s corresponding BaseLauncherSerialized subclass which describe how that dataclass is serialized.

Here is an example which subclass both of those class, in which we create a launcher for “git cloning”:

kloch_gitclone.py
import dataclasses
import http.client
import subprocess
from pathlib import Path
from typing import List
from typing import Optional

from kloch.launchers import BaseLauncher
from kloch.launchers import BaseLauncherSerialized
from kloch.launchers import BaseLauncherFields


@dataclasses.dataclass
class GitCloneLauncher(BaseLauncher):

    name = "git-clone"

    # all field must have a default value
    # but we make it required using the `required_fields` attribute
    remote_url: str = ""

    required_fields = ["remote_url"]

    def execute(self, tmpdir: Path, command: Optional[List[str]] = None):
        # we consider `command` are just extra args to git clone command
        _command = ["git", "clone", self.remote_url] + command
        subprocess.run(_command, env=self.environ, cwd=self.cwd)


@dataclasses.dataclass(frozen=True)
class GitCloneLauncherFields(BaseLauncherFields):
    remote_url: str = dataclasses.field(
        # this is the expected key name in the serialized representation
        default="remote-url",
        # this if for automated documentation generation
        metadata={
            "description": "An URL to a valid remote git repository.",
            "required": True,
        },
    )


def does_url_exists(url: str) -> bool:
    connection = http.client.HTTPConnection(url)
    connection.request("HEAD", "")
    return connection.getresponse().status < 400


class GitCloneLauncherSerialized(BaseLauncherSerialized):
    # the class it serialize
    source = GitCloneLauncher

    # we can pick a different name but we keep it similar for simplicity
    identifier = GitCloneLauncher.name

    fields = GitCloneLauncherFields

    # short one line description of the launcher
    summary = "Just clone a repository from a git remote."

    # full documentation of an arbitrary length for the launcher
    description = "From a git remote repository url, git clone it in the current working directory."

    def validate(self):
        super().validate()
        remote_url = self.fields.remote_url
        assert remote_url in self, f"'{remote_url}': missing or empty attribute."
        assert does_url_exists(
            self[remote_url]
        ), f"'{remote_url}': url provided doesn't exists: {self[remote_url]}."

    # we override for type-hint
    def unserialize(self) -> GitCloneLauncher:
        # noinspection PyTypeChecker
        return super().unserialize()

Registering

Then add the kloch_gitclone.py parent location in your PYTHONPATH so the plugin system can do an import kloch_gitclone.

The last step is to add the module name in the list of launcher plugins to use. You do this by modifying the kloch configuration, which is explained in Configuration

A quick way to do it is just to set the corresponding environment variable before starting kloch.

export KLOCH_CONFIG_LAUNCHER_PLUGINS=kloch_gitclone