Fitting coma to Existing Code

In general, there are at least three ways to modify coma to fit the interface of an existing codebase. We highlight these options using the following example:

class StartCommand:
    def __init__(self):
        self.foo = "bar"

    def start(self):
        print(f"foo = {self.foo}")

In this example, we suppose that we have an existing command-like class called StartCommand that cannot be modified. Supposed further that StartCommand has a start() method instead of the run() method that coma expects by default.

1. Redefining Hooks

The first option is redefining the run_hook to call start() instead of run() for this command using the run hook factory:

from coma import command, wake, run_hook

@command(name="start", run_hook=run_hook.default_factory("start"))
class StartCommand:
    def __init__(self):
        self.foo = "bar"

    def start(self):
        print(f"foo = {self.foo}")

if __name__ == "__main__":
    wake()

The program now runs as expected:

$ python main.py start
foo = bar

Warning

Internally, function-based commands will still be wrapped in a programmatically-generated class that always defines a run() method, regardless of any run_hook redefinition. As such, redefining run_hook globally as a shared hook instead of locally as a command hook will break function-based command declarations.

2. Wrapping with Functions

The second option is wrapping StartCommand in lightweight function-based command:

from coma import command, wake

class StartCommand:
    def __init__(self):
        self.foo = "bar"

    def start(self):
        print(f"foo = {self.foo}")

if __name__ == "__main__":
    command(name="start", cmd=lambda: StartCommand().start())
    wake()

The benefit of this approach is in its simplicity. The drawback is the loss of separation between command initialization and execution. It works well here only because StartCommand has a no-argument __init__() method.

3. Wrapping with Classes

The third option is wrapping the incompatible StartCommand in a compatible class-based command:

from coma import command, wake

class StartCommand:
    def __init__(self):
        self.foo = "bar"

    def start(self):
        print(f"foo = {self.foo}")

@command(name="start")
class WrapperCommand(StartCommand):
    def run(self):
        self.start()

if __name__ == "__main__":
    wake()

The benefit of this approach is that it maintains the separation between command initialization and execution. The drawback is that it is slightly more verbose than the function-based wrapper.