DecreeTree Class API#

The decree_tree.DecreeTree class is the basic building block for nested commands.

Basics#

The DecreeTree has several instance and class variables. To instantiate a new DecreeTree, use DecreeTree(...) as normal. All arguments are required to be passed by keyword.

class decree_tree.DecreeTree(*, name=None, inherit=True, prog_is_name=False, version=None)#

Bases: Tree[DecreeTree], Decree

A class for creating a command-line program using argparse subcommands.

Initialize the variables specific to the combined DecreeTree. This method exists to make its interface explicit, rather than relying on unspecified **kwargs.

Parameters:
  • name (str | None) – the command name, if overriding it for the instance

  • inherit (bool) – whether parents are considered when invoking a DecreeTree

  • prog_is_name (bool) – whether to use self.name as the program name in help

  • version (str | None) – version information; use --version to show

help: ClassVar[str] = ''#

short help text to use (allows __doc__ to be lengthy)

name: str = ''#

the name of the command, if not snake-cased class name

This attribute has a default value at the class level but can be overridden on a per-instance basis.

inherit#

whether parents are considered when invoking a DecreeTree

options: argparse.Namespace#

the set of argparse-processed options to be used

This attribute itself is not normally set by end user code, but items in this namespace may be created or read.

prog_is_name: bool#

whether to use the name as the prog, for help

version: str | None#

version information; use --version to show

This attribute could be set at the top level of an inherited tree.

Critical Methods#

These are the subset of DecreeTree methods that are most typically called, or overridden in subclasses.

DecreeTree.add_arguments(parser)#

Add arguments to parser (not self._decree_parser), if any, optionally adding arguments “inherited” from the parent DecreeTree. Subclass overrides typically include a call to super().add_arguments(parser). Handle manual argument processing in process_options, not here.

Parameters:

parser (ArgumentParser) – the parser to which arguments will be added

Return type:

None

This command is typically not called by end-user code. To use it to add arguments to a command parser, override it in a subclass:

from decree_tree import DecreeTree

class MyCommand(DecreeTree):

    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument('-o', '--option', action='store_true',
                            help='a Boolean option')
        parser.add_argument('input', help='a positional argument')

See the arparse docs for details on the sorts of options, arguments, and groups that can be added to the parser. Note that typically there is no need to add subparsers here, as nested DecreeTrees fill this role.

DecreeTree.process_options()#

Perform any needed processing on the options, prior to execution, optionally processing arguments “inherited” from the parent DecreeTree. Subclass overrides typically include a call to super().process_options().

Return type:

None

This command is typically not called by end-user code.

DecreeTree.execute()#

Execute [sub]command processing, optionally executing processing “inherited” from the parent DecreeTree. Subclass overrides typically include a call to super().execute().

Returns:

Any – any required data

This command is typically not called by end-user code. To use it to define the code to be ultimately executed by a command, override it in a subclass:

from decree_tree import DecreeTree

class MyCommand(DecreeTree):

    def execute(self):
        data = super().execute(parser)
        if self.options.print:
            print(data)

Use the processed command-line arguments stored in self.options.

DecreeTree.run(argv=None, options=None)#

Run the command via command line or with defined arguments. The command object executing run() will be considered the to be the root of the command, aside from any inherited processing. To override the actual command line arguments, pass to argv either a space-delimited string of tokens or a sequence of individual token strings.

Parameters:
  • argv (Sequence[str] | None) – command-line arguments to parse

  • options (Namespace | None) – processed arguments, circumventing parse_args if specified

Raises:

Exception – if any are raised within the running DecreeTree

Returns:

Any – the results from execution, if any

This command is the starting point for building the tree of command parsers, processing the options and arguments observed on the command line, and executing the selected commands. It is typically not overridden in end-user code. For simple standalone scripts, run() may be called from within a typical check for the module name:

if __name__ == '__main__':
    MyRootCommand().run()

Sometimes run() needs to be invoked in a way that doesn’t use the actual command-line tokens. This is often be the case during testing. To provide a different set of command line tokens, pass the argv argument to run():

# split() will be called on the string
MyRootCommand().run('--option value')

# or split it manually
MyRootCommand().run(['--option', 'value'])

Similarly, to inject processed option and argument values as an argparse.Namespace (when run calls parser.parse_args() from argparse), use the options argument.

Child Command Manipulation#

The class has several ways to manipulate its list of child commands. For this section, assume that Root, Foo, Bar, Baz, and Qux are DecreeTree subclasses, all imported from a module named nesting.

Refer to Nesting Commands for an overview of how to nest DecreeTree commands.

DecreeTree.add(child)#

A shortcut to append a new child, equivalent to append_child.

Parameters:

child (DecreeTree | Type[DecreeTree]) – the DecreeTree object, class, or subclass to append

Returns:

DecreeTree – the child itself, in case it was created within this method

Augment a tree by adding a child command to a parent. The new child will be appended to the list of children at that level. If this method (and other similar methods) receives a DecreeTree class, it will instantiate it. Executing this…

root = Root()
foo = root.add(Foo)
bar = root.add(Bar(version='1.0.0'))
baz = Baz()
foo.add(baz)
qux = foo.add(Qux())
print(root.structure)

... leads to this output:

root: nesting.Root
root -> foo: nesting.Foo
root -> foo -> baz: nesting.Baz
root -> foo -> qux: nesting.Qux
root -> bar: nesting.Bar

Note that this method can be equivalently invoked as append_child.

DecreeTree.child_index(name, raise_exception=False)#

Determine the index of a child.

Parameters:
  • name (str) – the name of the child to look up

  • raise_exception (bool) – whether to raise an exception if the child cannot be found

Raises:

InvalidName – if the child cannot be found and raise_exception is True

Returns:

int | None – the index of the child or None if not found

Determine the list index of a particular child command in order to manipulate its containing list. Executing this…

root = Root()
foo = root.add(Foo)
bar = root.add(Bar)
i = root.child_index('bar')
print(f'{i=}')

... leads to this output:

i=1
DecreeTree.clear_children()#

Clear the list of children.

Return type:

None

Empty out the list of children for the DecreeTree. Executing this…

root = Root()
foo = root.add(Foo)
bar = root.add(Bar)
print(root.structure)
print('...')
root.clear_children()
print(root.structure)

... leads to this output:

root: nesting.Root
root -> foo: nesting.Foo
root -> bar: nesting.Bar
...
root: nesting.Root
DecreeTree.find_child(name, raise_exception=False)#

Find a direct child by name and return it or None.

Parameters:
  • name (str) – the name of the child to find

  • raise_exception (bool) – whether to raise an exception if the child cannot be found

Raises:

InvalidName – if the child cannot be found and raise_exception is True

Returns:

DecreeTree | None – the child or None if not found

This method is similar to get below, but can only retrieve a direct child of a single DecreeTree. For example:

root = Root()
foo_1 = root.add(Foo)
foo_2 = root.find_child('foo')
assert foo_2 is foo_1

Pass raise_exception if the method should raise an exception if a child with the particular name cannot be found, otherwise None will be returned in that circumstance.

DecreeTree.get(*args)#

Look up and provide the nested child object, equivalent to get_child.

Parameters:

*args (str) – the nested path of names to the requested object

Raises:

InvalidName – if a matching object cannot be found

Returns:

DecreeTree – the object found

Retrieve a child from an arbitrary depth in a command tree.

root = Root()
foo = root.add(Foo)
bar_1 = foo.add(Bar)
bar_2 = root.get('foo', 'bar')
assert bar_2 is bar_1

Note that this method can be equivalently invoked as get_child.

DecreeTree.insert_child(index, child)#

Insert a new child.

Parameters:
  • index (int) – the position at which to insert the child

  • child (DecreeTree | Type[DecreeTree]) – the object, class, or subclass to insert

Returns:

DecreeTree – the child object

Insert a child command to parents. Executing this…

root = Root()
root.add(Foo)
root.add(Bar)
print(root.structure)
print('...')
root.insert_child(1, Baz)
print(root.structure)

... leads to this output:

root: nesting.Root
root -> foo: nesting.Foo
root -> bar: nesting.Bar
...
root: nesting.Root
root -> foo: nesting.Foo
root -> baz: nesting.Baz
root -> bar: nesting.Bar
DecreeTree.remove_child(name)#

Remove a child.

Parameters:

name (str) – The name of the child to remove.

Return type:

None

Insert a child command to parents. Executing this…

root = Root()
root.add(Foo)
root.add(Bar)
print(root.structure)
print('...')
root.remove_child('foo')
print(root.structure)

... leads to this output:

root: nesting.Root
root -> foo: nesting.Foo
root -> bar: nesting.Bar
...
root: nesting.Root
root -> bar: nesting.Bar

Note that the removed child, and its tree of children if any, may still be accessible through another Python reference. This method does not delete them.

DecreeTree.replace_child(name, child, raise_exception=False)#

Replace a child.

Parameters:
  • name (str) – the name of the child to replace

  • child (DecreeTree | Type[DecreeTree]) – the new object, class, or subclass

  • raise_exception (bool) – the raise_exception argument to the index() call

Returns:

DecreeTree | None – the instantiated child object

Replace a child command with another. Executing this…

root = Root()
root.add(Foo)
root.add(Bar)
print(root.structure)
print('...')
baz = Baz()
baz.add(Qux)
root.replace_child('bar', baz)
print(root.structure)

... leads to this output:

root: nesting.Root
root -> foo: nesting.Foo
root -> bar: nesting.Bar
...
root: nesting.Root
root -> foo: nesting.Foo
root -> baz: nesting.Baz
root -> baz -> qux: nesting.Qux
DecreeTree.sort_children(*, key=None, reverse=False)#

Alphabetically sort the list of subcommands in place.

Parameters:
  • key (Callable[[DecreeTree], SupportsLT | SupportsGT] | None) – a single-argument function used to extract the comparison key for each child

  • reverse (bool) – whether to sort in reverse

Return type:

None

Sort the direct children of a DecreeTree. Grandchildren are retained unsorted. Executing this…

root = Root()
root.add(Foo)
root.add(Bar)
root.add(Qux)
print(root.structure)
print('...')
root.sort_children()
print(root.structure)
print('...')
root.sort_children(
    key=lambda child: child.get_name[2:],
    reverse=True,
)
print(root.structure)

... leads to this output:

root: nesting.Root
root -> foo: nesting.Foo
root -> bar: nesting.Bar
root -> qux: nesting.Qux
...
root: nesting.Root
root -> bar: nesting.Bar
root -> foo: nesting.Foo
root -> qux: nesting.Qux
...
root: nesting.Root
root -> qux: nesting.Qux
root -> bar: nesting.Bar
root -> foo: nesting.Foo

Properties#

property DecreeTree.children: list[DecreeTree]#

Provide a copy of the child objects. This could be a generator, but since the list of children is expected to be short, there shouldn’t be a need.

Returns:

the list of child instances

>>> root = Root()
>>> _ = root.add(Foo)
>>> _ = root.add(Bar)
>>> root.children
[nesting.Foo(), nesting.Bar()]
property DecreeTree.child_names: list[str]#

Provide a list of the names of the child objects. This could be a generator, but since the list of children is expected to be short, there shouldn’t be a need.

Returns:

the list of the names of the child instances

>>> root = Root()
>>> _ = root.add(Foo)
>>> _ = root.add(Bar)
>>> root.child_names
['foo', 'bar']
property DecreeTree.count_children: int#

Provide the number of children.

Returns:

the number of children

>>> root = Root()
>>> _ = root.add(Foo)
>>> _ = root.add(Bar)
>>> root.count_children
2
property DecreeTree.get_name: str#

Provides the name of the instance, to facilitate separation of concerns via the use of Tree.

Returns:

the name of the instance

>>> from decree_tree import DecreeTree
>>> class Root(DecreeTree): pass
>>> root = Root()
>>> root.get_name
'root'
property DecreeTree.parent: DecreeTree | None#

Return the parent of the object, if any.

Returns:

the parent object or None

>>> root = Root()
>>> foo = root.add(Foo)
>>> foo.parent
nesting.Root()
property DecreeTree.parents: list[DecreeTree]#

Generate a list of the parents of this tree, in order. This could be a generator, but since the list of parents is expected to be short, there shouldn’t be a need.

Returns:

the parents

>>> root = Root()
>>> foo = root.add(Foo)
>>> bar = foo.add(Foo)
>>> bar.parents
[nesting.Root(), nesting.Foo()]
property DecreeTree.parent_names: list[str]#

Generate a list of the names of the parents of this tree, in order. This could be a generator, but since the list of parents is expected to be short, there shouldn’t be a need.

Returns:

the names of the parents

>>> root = Root()
>>> foo = root.add(Foo)
>>> bar = foo.add(Foo)
>>> bar.parent_names
['root', 'foo']
property DecreeTree.structure: str#

Show the structure of the nested tree. Relies on str() including a list of parents.

Returns:

the structure string

>>> root = Root()
>>> foo = root.add(Foo)
>>> bar = foo.add(Bar)
>>> baz = foo.add(Baz)
>>> for line in root.structure.splitlines():
...     print(line)
root: nesting.Root
root -> foo: nesting.Foo
root -> foo -> bar: nesting.Bar
root -> foo -> baz: nesting.Baz

Additional Methods#

These methods are typically not called by nor overridden in DecreeTree subclasses, but can be if custom behavior is desired.

DecreeTree.configure_parser(subparsers=None)#

Configure the parser for this object. Typically not called by nor overridden in end-user code.

Parameters:

subparsers (_SubParsersAction | None) – the subparsers object from a parent, if any

Raises:

ValueError – when the configuration of the name or prog is incorrect

Return type:

None

DecreeTree.configure_parser_tree(subparsers=None, parser=None)#

Configure the parser for this tree. Typically not called by nor overridden in end-user code.

Parameters:
  • subparsers (_SubParsersAction | None) – the subparsers object from a parent, if any

  • parser (ArgumentParser | None) – the parser to which arguments will be added, if not self._decree_parser

Return type:

None

DecreeTree.handle_run_exception(exc)#

Handle general exceptions raised within the run method.

Parameters:

exc (Exception) – the exception that was raised

Return type:

None

Override this method to change the default error handling behavior.

DecreeTree.parser_options()#

Add to and override options passed to the argparse parser.

Returns:

dict[str, Any] – the options to provide to the parser

Override this method to provide additional arguments to the argparse.ArgumentParser constructor for this DecreeTree, when used as the root command.

DecreeTree.preprocess_options(argv=None, options=None)#

Populate self.options if it isn’t already. Typically not called by nor overridden in end-user code.

Parameters:
  • argv (Sequence[str] | None) – command-line arguments to parse

  • options (Namespace | None) – processed arguments, circumventing parse_args if specified

Return type:

None

DecreeTree.subparsers_options()#

Add to and override options passed to add_subparsers.

Returns:

dict[str, Any] – the options to provide to the subparser creator

Override this method to provide additional arguments to the add_parser call for this DecreeTree, when used as a child command.