Wake Settings¶
After all commands have been declared using
@command, the last step is always to
wake() the program. wake() serves three functions:
Parameterizing the top-level ArgumentParser that determines which declared command is invoked.
Adding shared hooks that propagate to all command declarations by default.
Waking the program by invoking the correct command (as determined in step 1).
Let’s examine each in turn.
Parameters to argparse¶
wake() optionally accepts a custom ArgumentParser
object via its parser parameter. When None is given, wake() defaults to
an ArgumentParser with default parameters.
from coma import command, wake
from argparse import ArgumentParser
if __name__ == "__main__":
command(name="greet", cmd=lambda: print("Hello World!"))
wake(parser=ArgumentParser(description="My Program description."))
Let’s run this program with the -h flag to see the result:
$ python main.py -h
usage: main.py [-h] {greet} ...
My Program description.
positional arguments:
{greet}
optional arguments:
-h, --help show this help message and exit
You can also provide keyword arguments to override the default parameter values
to the internal ArgumentParser.add_subparsers()
call through the subparsers_kwargs parameter to wake():
wake(subparsers_kwargs=dict(help="sub-command help"))
Waking the Program¶
The main use case for wake() is to invoke the command
specified on the command line.
An additional use case is simulating command line arguments using the cli_args
and (rarely) the cli_namespace parameters to wake(). These parameters are
directly passed to ArgumentParser.parse_known_args(),
so the simulation behavior is identical to the one described there:
from coma import command, wake
if __name__ == "__main__":
command(name="greet", cmd=lambda: print("Hello World!"))
wake(cli_args=["greet"])
Running this program without providing a command name as part of the command line
arguments works because wake() is simulating greet as a command line argument:
$ python main.py
Hello World!
Simulated command line arguments are useful for invoking a default command. wake()
raises a WakeException when encountering a waking problem.
In particular, waking without a program command specified on the command line results
in raising this error. Typically, we would simply leave the exception unhandled
as it gives useful warnings (e.g., about the fact that the command name is missing
from amongst the command line arguments). A more advanced use case involves catching
the exception and then waking with a default command:
from coma import WakeException, command, wake
if __name__ == "__main__":
command(name="greet", cmd=lambda: print("Hello World!"))
command(name="default", cmd=lambda: print("Default command."))
try:
wake()
except WakeException:
wake(cli_args=["default"])
Running this program without providing command line arguments simulates running
default as a command line argument:
$ python main.py greet
Hello World!
$ python main.py default
Default command.
$ python main.py
Default command.
Importing Commands from Other Modules¶
Warning
A declared command (via @command) is only
registered with coma if the module in which the command is declared is
imported at runtime. This is standard Python behavior: Non-imported code is
not interpreted by the VM and not available at runtime. This is a bit obscured
by the behind-the-scenes magic done by @command (which talks to a Coma
singleton object in the background). This magic only works if the declaration
code runs (via being imported) at some point before the call to wake().
One way to ensure that all declared commands are properly registered with coma
is to have a from . import module statement (for every module that
declares a command) in the top-level __init__.py of your codebase. That
forces each command module to be imported.
Alternatively, a common pattern is to put lightweight (one-line) @command wrappers
around calls to the main/workhorse functions all in a single module (typically, the
same module that calls wake()). For example, supposed you define some commands in
modules called my_command.py and my_other_command.py:
def my_cmd(...):
...
and
def my_other_cmd(...):
...
Then, inside main.py, wrap these functions in @command declarations:
from coma import command, wake
from my_command import my_cmd
from my_other_command import my_other_cmd
if __name__ == "__main__":
command(cmd=my_cmd)
command(cmd=my_other_cmd)
wake()
Finally, a third alternative is to pass all declared commands scattered throughout
a codebase to the import_commands parameter of wake(). The contents of
import_commands is fully ignored by wake(). However, it forces the Python
VM to import each of the provided modules, thus registering the declared commands.
Note
Providing the imported commands to import_commands is not required (merely
importing them is enough), but doing so prevents linters from complaining of
unused import statements.
From the previous example, let’s directly declare our functions as commands inside their respective modules:
from coma import command
@command
def my_cmd(...):
...
and
from coma import command
@command
def my_other_cmd(...):
...
Then, inside main.py, we import these commands and pass them to wake():
from coma import wake
from my_command import my_cmd
from my_other_command import my_other_cmd
if __name__ == "__main__":
wake(my_cmd, my_other_cmd)