Commands

New commands can be easily added to the pipeline through the command framework.

Creating a new command

Commands are managed by the pipeline, and those the module containing the framework for the pipeline is defined in poppy.pop.command. To create a new command, simply derive a class from Command.

Simple command

For example, we will create a command to display informations on the pipeline, such as who wrote the pipeline.

from poppy.pop import Command

class WhoCommand(Command):
    """
    Define a command displaying informations on the pipeline author.
    """
    # a name to reference the command
    __command__ = "who_command"

    # the name of the command used
    __command_name__ = "who"

    # the parent of the command
    __parent__ = "master"

    # the help message displayed to the user
    __help__ = "Show the author of the pipeline"

    def __call__(self, args):
        print("Super Mario, pipeline expert")

That’s all!

Calling the pipeline with the who sub-command will display the name of the author. Now the details of the parameters.

The WhoCommand inherits the Command. By doing that, the WhoCommand is automatically registered by the framework, and the command is made available to the pipeline. Some attributes allows to perform some selection and modify the behaviour of the command.

  • WhoCommand.__command__ is used to give a reference name to the command. If the command associated to the WhoCommand needs to be referenced somewhere, this is the name defined by WhoCommand.__command__ that will be used.
  • WhoCommand.__command_name__ is the name of the command. When invoking the sub-command, this is the WhoCommand.__command_name__ that will be used.
  • WhoCommand.__parent__ is the reference to the parent sub-command. If you want that your command to be used as a sub-command of another command, simply add the reference name of another command inside this variable.
  • WhoCommand.__help__ contains the message to display to the user when invoking the help.

Warning

In order to the framework be able to discover the command just defined, you should be sure of two things:

  • the class of your command inherits the Command class
  • the class you defined has been imported somewhere before invoking the command. Typically an import has been done somewhere, happening before the execution of the function in the scripts directory.

Add arguments

Let’s say you are an exigent user and you want to have the possibility to select the format for displaying the name of the author. A way to do that is to create arguments that will be used in the command handler. For this, you just have to define a method to add argument to the created parser for the command, parser created by argparse.

So we add an option to the who command to display only the information on the author name, and a second one to display only the short description. Rewriting WhoCommand:

class WhoCommand(Command):
    """
    Define a command displaying informations on the pipeline author.
    """
    # a name to reference the command
    __command__ = "who_command"

    # the name of the command used
    __command_name__ = "who"

    # the parent of the command
    __parent__ = "master"

    # the help message displayed to the user
    __help__ = "Show the author of the pipeline"

    def add_arguments(self, parser):
        """
        Add arguments to the parser associated to the command.
        """
        # add option for displaying only the author name
        parser.add_argument(
            "--author",
            help="Show only author name",
            action="store_true",
        )

        # same but only for the description
        parser.add_argument(
            "--description",
            help="Show only the description of the author",
            action="store_true",
        )

    def __call__(self, args):
        if args.author:
            print("Super Mario")
        elif args.description:
            print("pipeline expert")
        else:
            print("Super Mario, pipeline expert")

Now doing:

$ pipeline who
Super Mario, pipeline expert

$ pipeline who --author
Super Mario

$ pipeline who --description
pipeline expert

If you want to specify to the user that the two options are mutually exclusives, you can the arguments inside a group. Simply modify add_aguments() to:

def add_arguments(self, parser):
    """
    Add arguments to the parser associated to the command.
    """
    # create a group
    group = parser.add_mutually_exclusive_group()

    # add option for displaying only the author name in the group
    group.add_argument(
        "--author",
        help="Show only author name",
        action="store_true",
    )

    # same but only for the description
    group.add_argument(
        "--description",
        help="Show only the description of the author",
        action="store_true",
    )

Now, an error is displayed to the user if both options are given at the same time.

See also

Details on all the large possibilities on how to add arguments and actions that can be taken with them are given on argparse.

Hierarchy in commands

Now, you have a working pipeline, with several author contributions, and you want to show a list of them through the command line interface. This is clearly related to the who command, but you do not want to simply add arguments, since you may want to add arguments to make some filtration on the list you want to show. For this, you will need to add a subcommand to the who command. This can be easily achieved by creating a new Command.

class WhoListCommand(Command):
    """
    Command displaying the list of authors and contributors.
    """
    # reference for the command
    __command__ = "who_list_command"

    # command used in cli
    __command_name__ = "list"

    # here specify that the parent command is the who one
    __parent__ = "who_command"

    # help message to display for this command
    __help__ = "Show a list of contributors to the pipeline"

    def __call__(self, args):
        """
        With the list of authors taken from somewhere, display it when
        invoked as a command.
        """
        # simple message
        print("Contributors:")

        # print the list of authors
        print(", ".join(get_authors_list()))

The command is used like this:

$ pipeline who list
Contributors:
Super Mario, Luigi, Peach

This is simply the __parent__ that will order the hierarchy of commands between them. You can add any arguments as needed to this command as described above. The arguments and options will be associated to this command only.

Note

The framework is very flexible and allows quick development of new commands and change in the hierarchy. Let’s say that you do not want to use the list command with the who command anymore, and want it to be a “root” command, independent of the who one. The only thing you have to do is changing __parent__ to __master__ and rename the command if you wish with the __command_name__ attribute. Resulting in a calling interface like this:

$ pipeline list
Contributors:
Super Mario, Luigi, Peach

Inheritance of parameters

Simple use case

A command that you define can inherit the commands of a parent command if you specify it. By default, arguments of a command must be placed between the command and its subcommand, else the arguments will not be recognized.

$ pipeline command1 --arg_command1 command2

This command is valid. But the following not:

$ pipeline command1 command2 --arg_command1

while it seems natural to do it this way if for example, –arg_command1 is equivalent to a –verbose option.

To get something similar to the last behaviour, you can use the __parent_arguments__ attribute, containing a list of parent command names, whose arguments will be inherited.

Warning

If some arguments are conflicting given their names, the latter definition of the argument will be used in priority, which should with the framework structure, always be the one of the child command.

Advanced use case

argparse will always add an help option by default to any parser that it creates. But for inheritance, this behaviour is not always wanted, since the child command will override the help command of the parent parser. To avoid such situations, you can also create a parser that will not be used, only as a parent for other parsers. For this, you only need to override the parser() of the Command.

def parser(self, subparser, parents):
    """
    Return the parser for the command and options that this command must
    use. Take as argument the subparser from the parent parser.
    """
    # create a parser with no help
    parser = argparse.ArgumentParser(add_help=False)

    # add the parser to the list of parents
    new_parents = parents + [parser]

    # call the Command class method
    old_parser = super(WhoCommand, self).parser(subparser, new_parents)

    # return this parser
    return parser

This way, a new parser is created with its arguments and no help, given to the parser of the command as a parent to get its options, and the parser is returned to be associated to the command, if a child wants to reference its arguments.

Warning

By using this technique, you are modifying the intrinsic behaviour of the framework. While it can be helpful some times, if you don’t know what you are doing or what you want to do can be achieved in an other way, try to avoid this “advanced technique”, since side effects will be unknown.

API

class poppy.core.command.Command[source]

Bases: object

Base class for all accepted commands for the pop command line program.

add_arguments(parser)[source]

Used by the user to add arguments associated to the given parser.

classmethod add_parent_parser(name, parser)[source]

Used to add a parser with its options and be able to refer from a command, since the conflict handler of argparse is not well done, as many other things.

has_children()[source]

To know if the command has subcommands.

parser(subparser, parents)[source]

Return the parser for the command and options that this command must use. Take as argument the subparser from the parent parser.

print_help()[source]

Print the command help message

setup_tasks(pipeline)[source]

Set up the task workflow

subparser(parser)[source]

Should return a subparser for this command. Not always called, just when the command has possibly subcommands to generate.

class poppy.core.command.CommandManager[source]

A class to manage the available defined commands by the user through the plugin command classes.

add(name, cls)[source]

Adds the command and its class to the manager.

create(instance)[source]

Register the instances of the commands by their name.

generate(options)[source]

Given a first base subparser and the parents parser, generate the parsers for children commands recursively.

launch(argv=None)[source]

Launch the commands by parsing the input of the program and then calling the good command with the good arguments.

parse(argv=None)[source]

Responsible to parse the command line, and also check the consistency of the tree of commands for the base command.

preprocess_args(argv)[source]

Preprocess options like –settings and –config.

These options could affect the pipeline behavior, so they must be processed early.

Parameters:argv
Returns:options, args