Configuration#

The DecreeTree class can be configured in several ways, many of which are documented on this page. Refer to Nesting Commands for a discussion of now to nest commands and configure the nesting process.

General Workflow#

There are three stages to the use of DecreeTree. These are summarized here to provide and overview of the general DecreeTree workflow and how to define and configure your commands.

Stage 1: Class Definition#

One or more DecreeTree classes are defined. The most critical aspects of each of these definitions include the following:

  • add_arguments() is defined to specify the options and arguments to be applied for that command (and its children, by default).

  • process_options() can be defined to perform custom steps required to process the initial set of options gathered by the parser, for example if two arguments depend on each other and result in a modified value. This method is frequently not required.

  • execute() defines the main code to be executed. It has access to the self.options set of processed options.

  • Consideration should also be given to the command name and its help text.

For the basic tree example, the definition of some of the classes is shown below. BasicCommand is defined elsewhere.

Part 1 of basic_tree.py#
from decree_tree import DecreeTree

class Root(DecreeTree):
    """An example command tree."""

    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument('-e', '--extra', action='store_true')

    def execute(self):
        super().execute()
        if self.options.extra:
            print("Extra output")

class Double(DecreeTree):
    """Doubly-echo the input."""

    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument('second', help='the input to double')

    def execute(self):
        super().execute()
        print(1, self.options.second)
        print(2, self.options.second)

Abstract parent classes may be employed in the definition of these command classes. In addition, if knowledge of the tree structure is known, and manual inheritance is to be allowed, consideration may be given to options generated via arguments in parent or child commands. Regardless, ensure each method defined includes a super() call to its parent, unless you know what you are doing.

Stage 2: Tree Instantiation#

Once the DecreeTree subclasses have been defined, they can be instantiated and assembled into the desired command tree structure. This is accomplished by using the tree management functionality described in Nesting Commands and Child Command Manipulation. The most common method used for tree construction is add(), likely followed by get().

For the basic tree example, stage 2 is represented by these lines of code:

Part 2 of basic_tree.py#
root = Root()
root.add(BasicCommand)
double = root.add(Double)
# `double.add(...)` could be used for sub-sub-commands

Complex structures can be defined.

Stage 3: Runtime#

Finally, to start command-line processing, the run() method is invoked. The options to run() are described in Runtime Configuration, but by default, the actual arguments on the command line are processed.

For the basic tress example, this is short:

Part 3 of basic_tree.py#
if __name__ == '__main__':
    root.run()

Once run() is called, several steps occur:

  1. The parser tree, including the parser for each DecreeTree, is created, using configure_parser() and configure_subparsers().

  2. For each parser, the appropriate arguments are added by invoking add_arguments().

  3. preprocess_options() is called, which itself calls arparse.ArgumentParser.parse_args to read and parse the options on the command line.

  4. set_options() propagates the initial set of options data to all classes in the selected branch of the command tree.

  5. process_options() performs any additional input data manipulation, as described above.

  6. Ultimately execute() is called to perform the actual functions of the command.

While the call to run() can be followed by subsequent operations, no further action is required.

Command Names#

Each DecreeTree subclass has a name associated with it, stored in the name class variable. If unspecified by the subclass definition, the DecreeTree looks at parent classes for a name, and if otherwise not found, name defaults to the snake-cased version of the subclass name.

This name is used in several ways. When the DecreeTree is used as a subcommand, it becomes the name of the subcommand on the command line and in the help. If the DecreeTree is the root command, the name can be shown as the command name in the help if the prog_is_name argument is passed as True upon class instantiation; otherwise the default value of the program name (related to the prog argument to ArgumentParser) is used. Python 3.14 enhances the latter behavior by more accurately showing the calling program name.

Options and Arguments#

Options and positional arguments for each DecreeTree subclass should be specified in its add_arguments() method. This method receives an argparse.ArgumentParser argument named parser, which can be configured in any way allowed by argparse. This includes using:

However it is recommended that add_subparsers not be employed here, as this functionality is handled by DecreeTree itself.

Note that the order of argument definition is important for positional arguments and help output. This interacts with Inheritance, and so calling super().add_arguments(parser) is critical.

Parsers and Subparsers#

This can be overridden by using the argument_parser_class argument in the call to run(), as described in Runtime Configuration.

DecreeTree uses argparse._SubParsersAction objects to configure subcommand parsing. These are normally created via the add_subparsers method from an argparse parser. Each DecreeTree sets some default arguments for any necessary add_subparsers call in configure_parser_tree().

One of the defaults is to set required=True for each subparsers object. This means that invocation of a subcommand, if any are available at at particular level of a tree, is required. The expectation is that this behavior is typical. However, if this or other arguments to add_subparsers need to be modified, this can be accomplished by overriding subparsers_options() and passing the appropriate argument as a key-value pair in its return dict.

Similarly, configure_parser() configures the actual parser at for each subcommand. This can be overridden if substantially different behavior is required, however parser_options() can be used to override any of the arguments supplied to the parser initialization or subparsers.add_parser call. The actual class used as the parser is controlled by argument_parser_class, which can be overridden in the call to run() per Runtime Configuration.

The Help Option#

It is important for a script to optionally provide a help message describing its usage. This is a key feature of argparse. All arguments and subcommands added are included in the help, and properly shown at each level.

DecreeTree facilitates this as well. By default, specifying -h or --help at any level of a command tree will cause the script to show a help message and exit. The summary message for that level of help is taken from the docstring of the relevant DecreeTree class, but this can be overridden by defining a help attribute for that class. If neither is specified, the DecreeTree looks for a help specification in its parent classes, and an empty string is used as a default. This message is reused to describe the subcommand entry in the parent command as well. The help arguments to parser.add_argument() calls are also added to the help output.

Much of this functionality can be overridden by returning the appropriate keywords in parser_options() and subparsers_options(). Specifically, returning {'add_help': False} from the former will disable the help argument entirely.

See the basic tree example for sample output.

The Version Option#

It is often desirable to allow a --version command-line option that shows the name and version of the tool and exits. DecreeTree optionally adds such an option to the command parsers using the 'version' action provided by argparse.

This functionality is disabled by default. To enable it, supply the desired version string to the version argument of the DecreeTree constructor. Do so for each command at the root of where that --version option should apply. Nested subcommands will inherit the option. An example is shown below.

version.py#
from decree_tree import DecreeTree

DecreeTree(version="the first one").run()

When version.py called from the command line, the version is shown:

$ python version.py --version
version.py the first one

Beware that if super() is not properly invoked on subclass add_arguments() calls, this functionality may fail.

Runtime Configuration#

Once a tree of commands has been created, command line processing can be initiated by calling the run() method. Typically run() is called on the top-most command in the tree, but a subtree can be used in lieu of the entire tree, as described in Alternate Roots.

The run(argv, options, *, debug_tracing, argument_parser_class) method takes several arguments which govern the following functionality:

  • Normally the command line arguments to be parsed are read from the actual command line arguments. However, if this needs to be changed for testing or for a more complex calling situation, the sequence of string arguments to be parsed can be passed in via argv. See Testing for more details.

  • Similarly, the options argument can be used to provide parsed options in the form of an argparse.Namespace object. This circumvents all argument parsing, and is primarily used for testing. See Testing for more details.

  • The debug_tracing Boolean argument can be used to enable printing of debug statements. See Debugging for more details.

  • To provide better usage messages when unexpected arguments are detected, decree-tree uses the enhanced ModifiedArgumentParser parser. Supply a different parser class to the argument_parser_class argument to alter this behavior.

Most of the activity that occurs during run() is wrapped in a try ... except block. The handling of the any exceptions that occurs can be modified by overriding the handle_run_exception() method in the DecreeTree subclass that calls run()