Initiate

The first step to using coma is always to initiate() a new coma.

initiate() is always called exactly once, before any calls to register(). Calling initiate() explicitly is required only to initiate a coma with non-default parameters (coma implicitly calls initiate() with default parameters otherwise).

argparse Overrides

By default, coma creates an ArgumentParser with default parameters. However, initiate() can optionally accept a custom ArgumentParser:

main.py
import argparse

import coma

if __name__ == "__main__":
    coma.initiate(parser=argparse.ArgumentParser(description="My Program description."))
    coma.register("greet", lambda: print("Hello World!"))
    coma.wake()

Now, 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 initiate():

coma.initiate(subparsers_kwargs=dict(help="sub-command help"))

Global Configs

Configs can be initiate()d globally to all commands or register()ed locally to a specific command.

Let’s revisit the second of the Multiple Configurations examples from the introductory tutorial to see the difference:

main.py
from dataclasses import dataclass

import coma

@dataclass
class Greeting:
    message: str = "Hello"

@dataclass
class Receiver:
    entity: str = "World!"

if __name__ == "__main__":
    coma.register("greet", lambda g, r: print(g.message, r.entity), Greeting, Receiver)
    coma.register("leave", lambda r: print("Goodbye", r.entity), Receiver)
    coma.wake()

Notice how, in the original example, the Receiver config is register()ed (locally) to both commands. Instead, we can initiate() a coma with this config so that it is (globally) supplied to all commands:

main.py
from dataclasses import dataclass

import coma

@dataclass
class Greeting:
    message: str = "Hello"

@dataclass
class Receiver:
    entity: str = "World!"

if __name__ == "__main__":
    coma.initiate(Receiver)
    coma.register("greet", lambda r, g: print(g.message, r.entity), Greeting)
    coma.register("leave", lambda r: print("Goodbye", r.entity))
    coma.wake()

This produces the same overall effect, while being more DRY.

Note

Configs need to be uniquely identified per-command, but not across commands.

Note

Each command parameter will be bound (in the given order) to the supplied config objects if the command is invoked. In this example, because Receiver is now supplied first instead of second to greet, the order of parameters to greet had to be swapped: g, r becomes r, g. See below for a nexample of how to prevent this.

Global Hooks

coma’s behavior can be easily tweaked, replaced, or extended using hooks. These are covered in great detail in their own tutorial. Here, the emphasis is on the difference between global and local hooks: As with configs, hooks can be initiate()d globally to affect coma’s behavior towards all commands or register()ed locally to only affect coma’s behavior towards a specific command.

Let’s revisit the previous example. Recall that the order of parameters to greet had to be swapped: g, r became r, g. Suppose we want to prevent this change. To do so, we can force coma to bind configs to parameters differently by writing a custom init_hook:

main.py
from dataclasses import dataclass

import coma

@dataclass
class Greeting:
    message: str = "Hello"

@dataclass
class Receiver:
    entity: str = "World!"

@coma.hooks.hook
def custom_init_hook(command, configs):
    return command(*reversed(list(configs.values())))

if __name__ == "__main__":
    coma.initiate(Receiver, init_hook=custom_init_hook)
    coma.register("greet", lambda g, r: print(g.message, r.entity), Greeting)
    coma.register("leave", lambda r: print("Goodbye", r.entity))
    coma.wake()

The details of how the hook is defined aren’t important for the moment. The point is that coma’s default behavior regarding config binding has been replaced from positional matching to anti-positional matching, which is sufficient in this simple example.