Protocols

coma uses a hook pipeline, both to implement its default behavior and to enable customization. To make this work, the various types of hook must each follow a pre-defined protocol (i.e., function signature) for both their parameters and their return value.

The hook protocols are fairly similar across all hook types, but there are a number of variations depending on the type. We begin by enumerating the shared aspects of the various protocols.

Function Signature

All protocols require the corresponding hook function signatures to define positional-or-keyword parameters. For example, we can define and invoke some (hypothetical) hook function as:

def some_hook(a, b, c):
    ...

some_hook(a=..., b=..., c=...)

This is fine because the hook function parameters are defined as positional-or-keyword. These requirement is needed because hook parameters are bound positionally by keyword.

Note

Technically, the requirement is that the hook function parameters are not positional-only, not variadic (positional or keyword), and not keyword-only. In practice, that means they have to be positional-or-keyword.

In addition to the above, all protocol parameters must be:

  • Defined in the hook function’s signature.

  • Ordered correctly in the hook function’s signature.

  • Named (i.e., spelled) correctly in the hook function’s signature.

Protocol Parameters

generic_protocol(name, parser, known_args, unknown_args, command, configs, result)

Here, we list all possible protocol parameters, in the order in which they should be defined in the hook function’s signature.

Note

Not every type of hook uses every protocol parameter. The protocols specific to each hook type are listed below. None of these specific differences affect the parameter ordering or naming shown here.

Parameters
  • name (str) – The name given to a command when it is register()ed.

  • parser (argparse.ArgumentParser) – The ArgumentParser created to add command line arguments for a specific command when it is register()ed and subsequently parse actual command line arguments if the command is invoked on the command line.

  • known_args (argparse.Namespace) – The Namespace object (i.e., first object) returned by parse_known_args() if a specific command is invoked on the command line.

  • unknown_args (List[str]) –

    The list object (i.e., second object) returned by parse_known_args() if a specific command is invoked on the command line.

  • command (Union[Callable, Any]) –

    The command object itself that was register()ed if it is invoked on the command line.

    Note

    If the register()ed object was a class, it is left unchanged. If the register()ed object was a function, it is implicitly wrapped in another object. The main init hook is meant to instantiate command.

    Warning

    Never make decisions based on the type of command, since it may be implicitly wrapped. Instead, use name, which is guaranteed to be unique across all register()ed commands.

  • configs (Dict[str, Any]) –

    A dictionary of identifier-configuration pairs representing all configs (both global and local) bound to a specific command if it is invoked on the command line.

    Note

    Before the main config hook, the values in the configs dictionary are assumed to be uninitialized config objects. Afterwards, they are assumed to be initialized config objects.

  • result (Any) – The value returned from executing the command if it is invoked on the command line.

Returns

Some protocols return values; others do not. See below for details on each protocol.

Return type

Any

@hook Decorator

For many hooks, only a subset of the corresponding protocol parameters are needed to implement their logic. It can therefore be cumbersome to define a function with multiple unused parameters just to satisfy the hook protocol. The @hook decorator (link: hook()) solves this problem, as it allows hook functions to be defined with a subset of the protocol parameters. For example:

@coma.hooks.hook
def name_hook(name):
    ...

defines a hook that only requires the command’s name and ignores all other protocol parameters.

Note

The @hook decorator only alleviates the requirement that all protocol parameters are defined in the hook function’s signature. Other requirements, such as having the correct ordering and spelling of parameters, remain active.

sequence() Function

Each type of hook must be implemented as a single function. However, it is often beneficial to decompose a large hook function into a series of smaller ones. These component functions must then be wrapped with a higher-order function that executes them in order, while binding all parameters using keywords.

While this wrapping can always be done manually, a convenience wrapper, sequence(), can be used when all hooks share the exact same function signature (or are wrapped in the @hook decorator) to abstract away some of the minutiae. Compare:

wrapper = coma.hooks.sequence(
    coma.hooks.parser_hook.factory("-a", type=int, default=123),
    coma.hooks.parser_hook.factory("-b", type=int, default=456),
)

coma.register(..., parser_hook=wrapper)

with:

@coma.hooks.hook
def wrapper(parser):
    coma.hooks.parser_hook.factory("-a", type=int, default=123)(parser=parser)
    coma.hooks.parser_hook.factory("-b", type=int, default=456)(parser=parser)

coma.register(..., parser_hook=wrapper)

The former isn’t shorter, but it removes the minutiae of adding (parser=parser) to each wrapped hook function and removes the need to decorate the wrapper function with the @hook decorator.

Specific Protocols

Here, we list the specific protocol and intended semantics for each type of hook. See Protocol Parameters for details on each parameter.

Parser

parser_hook_protocol(name, parser, command, configs)
Semantics

This protocol adds command line arguments using parser.

Returns

The return value of a parser hook (if any) is always ignored.

Return type

None

Pre Config

pre_config_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

This protocol is the first invocation hook to be executed.

Returns

The return value of a pre config hook (if any) is always ignored.

Return type

None

Config

config_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

The values in the configs dictionary represent uninitialized config objects. This protocol initializes them and returns them in the same order.

Returns

The return value of a config hook is an initialized configs dictionary.

Return type

Dict[str, Any]

Post Config

post_config_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

This protocol takes the initialized configs objects and returns these same objects (possibly modified in some way) in the same order.

Returns

The return value of post config hooks is the configs dictionary.

Return type

Dict[str, Any]

Pre Init

pre_init_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

This protocol’s hook is executed after all the config hooks and before the main init hook.

Returns

The return value of a pre init hook (if any) is always ignored.

Return type

None

Init

init_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

This protocol instantiates command using the configs, returning the resulting instance object.

Note

If the register()ed command object was a class, it was left unchanged. If the register()ed command object was a function, it was implicitly wrapped in another object. Either way, command acts as though it is a class object that can be instantiated.

Returns

The return value of an init hook is an instantiated command object.

Return type

Any

Post Init

post_init_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

This protocol takes the instantiated command object and returns the same object (possibly modified in some way).

Returns

The return value of a post init hook is the instantiated command object.

Return type

Any

Pre Run

pre_run_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

This protocol’s hook is executed after all the config and init hooks and before the main run hook.

Returns

The return value of a pre run hook (if any) is always ignored.

Return type

None

Run

run_hook_protocol(name, known_args, unknown_args, command, configs)
Semantics

This protocol executes the instantiated command object, then returns the resulting value.

Returns

The return value of a run hook is the value resulting from executing the instantiated command object.

Return type

Any

Post Run

post_run_hook_protocol(name, known_args, unknown_args, command, configs, result)
Semantics

This protocol is the last invocation hook to be executed.

Returns

The return value of a post run hook (if any) is always ignored.

Return type

None