Warning

This is the documentation for a development version of flagman.

Documentation for the Most Recent Stable Version

The Anatomy of an Action

Actions are the primary workhorse of flagman. Writing your own actions allows for infinite possible uses of the tool!

The Action Class

Actions are instances of the abstract base class flagman.Action. Let’s look at the included PrintAction as an illustrative example.

class PrintAction(Action):
    """A simple Action that prints messages at the various stages of execution.

    (message: str)
    """

    def set_up(self, msg: str) -> None:  # type: ignore
        """Store the message to be printed and print the "init" message.

        :param msg: the message
        """
        self_msg = msg
        print('init')

    def run(self) -> None:
        """Print the message."""
        print(self._msg)

    def tear_down(self) -> None:
        """Print "cleanup" message."""
        print('cleanup')

We start with a standard class definition and docstring:

class PrintAction(Action):
    """A simple Action that prints messages at the various stages of execution.

    (message: str)
    """

We inherit from Action. The docstring is parsed and becomes the documentation for the action in the CLI output:

$ flagman --list
name  - description [(argument: type, ...)]
--------------------------------------------------------------------------------
print - A simple Action that prints messages at the various stages of execution.
        (message: str)

If the Action takes arguments, it is wise to document them here. The name of the action is defined in an entry point–see Registering an Action below.

Next is the set_up() method.

def set_up(self, msg: str) -> None:  # type: ignore
    """Store the message to be printed and print the "init" message.

    :param msg: the message
    """
    self_msg = msg
    print('init')

All arguments will be passed to this method as strings. If other types are expected, do the conversion in set_up() and raise errors as necessary. If mypy is being used, the # type: ignore comment is required since the parent implementation takes *args.

Do any required set up in this method: parsing arguments, reading external data, etc. If you want values from the environment (e.g. if API tokens or other values that should not be passed on the command line are needed), you can get them here. flagman itself does not provide facilities for parsing the environment, configuration files, etc.

Next we have the most important method, run(). This is the only abstract method on Action and as such it must be implemented.

def run(self) -> None:
    """Print the message."""
    print(self._msg)

Perform whatever action you wish here. This method is called once for each time flagman is signaled with the proper signal, assuming low enough rates of incoming signals. See below in the Overlapping Signals section for more information.

Because of flagman’s architecture, it is safe to do anything inside the run() method. It is not actually called from the signal handler, but in the main execution loop of the program. Therefore, normally “risky” things to do in signal handlers involving locks, etc. (including using the logging module, for example) are completely safe.

Finally, there is the tear_down() method.

def tear_down(self) -> None:
    """Print "cleanup" message."""
    print('cleanup')

Here you can perform any needed cleanup for your action like closing connections, writing out statistics, etc.

This method will be called when the action is “closed” (see below), during garbage collection of the action, and before flagman shuts down.

“Closing” an Action

If an Action has fulfilled its purpose or otherwise no longer needs to be called, it can be “closed” by calling its _close() method. This method takes no arguments and always returns None.

Calling this method does two things: it calls the action’s tear_down() method and it sets a flag that prevents further calls to the internal _run() method that flagman uses to actually run Actions.

Further calls to _run() will raise a flagman.ActionClosed exception and will cause the removal of the action from the internal list of actions to be run. If there are no longer any non-closed actions, flagman will exit with code 1, unless it was originally called with the --successful-empty option, in which case it will exit with 0.

If you want to close your own action in its run() method, a construction like so is advised:

def run(self) -> None:
    if some_condition:
        self._close()
        raise ActionClosed('Closing because of some_condition')
    else:
        ...

This will print your argument to ActionClosed to the log and will result in the immediate removal of the action from the list of actions to be run. If ActionClosed is not raised, flagman will not realize the action has been closed and will not remove it from the list of actions to be run until the next time run() would be called, i.e. the next time the signal is delivered for the action.

Registering an Action

flagman detects available actions in the flagman.action entry point group. Actions must be distributed in packages with this entry point defined. For instance, here is how the built-in actions are referenced in flagman’s setup.cfg:

[options.entry_points]
flagman.action =
    print = flagman.actions:PrintAction
    delay_print = flagman.actions:DelayedPrintAction
    print_once = flagman.actions:PrintOnceAction

The name to the left of the = is how the action will be referenced in the CLI. The entry point specifier to the right of the = points to the class implementing the action. See the Setuptools documentation for more information about using entry points.