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
start()
method instead of the default run()
method.
Redefining Hooks
The first option is redefining the run_hook
of
initiate()
to call start()
instead of 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 run()
method, regardless of any run_hook
redefinition.
As such, it is generally safer, if more verbose, to locally redefine the
run_hook
using register()
and a
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
here for details on using forget()
.
Wrapping with Functions
The second option is wrapping 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 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 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.