Skip to content

Latest commit

 

History

History
164 lines (113 loc) · 4.17 KB

coma.rst

File metadata and controls

164 lines (113 loc) · 4.17 KB

Fitting coma to Existing Code

In general, there are at least four 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 an existing command-like class has a :obj:`start()` method instead of the default :obj:`run()` method.

Redefining Hooks

The first option is redefining the :obj:`run_hook` of :func:`~coma.core.initiate.initiate` to call :obj:`start()` instead of :obj:`run()`:

import coma

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

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

if __name__ == "__main__":
    coma.initiate(run_hook=coma.hooks.run_hook.factory("start"))
    coma.register("start", StartCommand)
    coma.wake()

The program now runs as expected:

$ python main.py start
foo = bar

Warning

Internally, function-based commands will still be wrapped in a class that defines a :obj:`run()` method, regardless of any :obj:`run_hook` redefinition. As such, it is generally safer, if more verbose, to locally redefine the :obj:`run_hook` using :func:`~coma.core.register.register` and a :func:`~coma.core.forget.forget` context manager:

import coma

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

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

if __name__ == "__main__":
    with coma.forget(run_hook=True):
        coma.register("start", StartCommand,
                      run_hook=coma.hooks.run_hook.factory("start"))
    coma.wake()

This ensures that other commands are not affected. See :doc:`here <../core/forget>` for details on using :func:`~coma.core.forget.forget`.

Wrapping with Functions

The second option is wrapping :obj:`StartCommand` in a function-based command:

import coma

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

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

if __name__ == "__main__":
    coma.register("start", lambda: StartCommand().start())
    coma.wake()

The benefit of this approach is in its simplicity. The drawback is the loss of separation between command initialization and execution.

Wrapping with Classes

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

import coma

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

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

class WrapperCommand(StartCommand):
    def run(self):
        self.start()

if __name__ == "__main__":
    coma.register("start", WrapperCommand)
    coma.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.

Adding Interface Elements

The fourth option is adding missing interface elements (in this case, an attribute) to :obj:`StartCommand`:

import coma

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

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

if __name__ == "__main__":
    StartCommand.run = StartCommand.start
    coma.register("start", StartCommand)
    coma.wake()

For simple cases, this option is often the most succinct.