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 theself.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.
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:
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:
basic_tree.py
#if __name__ == '__main__':
root.run()
Once run()
is called, several steps occur:
The parser tree, including the parser for each
DecreeTree
, is created, usingconfigure_parser()
andconfigure_subparsers()
.For each parser, the appropriate arguments are added by invoking
add_arguments()
.preprocess_options()
is called, which itself calls arparse.ArgumentParser.parse_args to read and parse the options on the command line.set_options()
propagates the initial set of options data to all classes in the selected branch of the command tree.process_options()
performs any additional input data manipulation, as described above.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:
All the arguments to parser.add_argument(), including argument name, option versus positional argument, destination variable name and type, action to take, allowed choices, number of times it can occur, help text, and more.
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 enhancedModifiedArgumentParser
parser. Supply a different parser class to theargument_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()