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.
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`.
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.
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.
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.