DecreeTree Class API #################### The :class:`decree_tree.DecreeTree` class is the basic building block for nested commands. Basics ****** The :class:`DecreeTree ` has several instance and class variables. To instantiate a new :class:`DecreeTree `, use ``DecreeTree(...)`` as normal. All arguments are required to be passed by keyword. .. autoclass:: decree_tree.DecreeTree :show-inheritance: .. autoattribute:: decree_tree.DecreeTree.help .. autoattribute:: decree_tree.DecreeTree.name This attribute has a default value at the class level but can be overridden on a per-instance basis. .. autoattribute:: decree_tree.DecreeTree.inherit .. autoattribute:: decree_tree.DecreeTree.options This attribute itself is not normally set by end user code, but items in this namespace may be created or read. .. autoattribute:: decree_tree.DecreeTree.prog_is_name .. autoattribute:: decree_tree.DecreeTree.version 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. .. automethod:: decree_tree.DecreeTree.add_arguments 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: .. testcode:: 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 :ref:`nested DecreeTrees ` fill this role. .. automethod:: decree_tree.DecreeTree.process_options This command is typically not called by end-user code. .. FIXME expand this .. automethod:: decree_tree.DecreeTree.execute 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: .. testcode:: 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``. .. automethod:: decree_tree.DecreeTree.run 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: .. code:: 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()``: .. code:: # 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``. .. testsetup:: testclasses # Originate classes in a distinct file for __class__.__module__ import os import sys current_dir = os.getcwd() new_dir = os.path.join(current_dir, 'source') new_dir = os.path.join(new_dir, 'code') sys.path.append(new_dir) from nesting import Root, Foo, Bar, Baz, Qux Refer to :doc:`nesting` for an overview of how to nest ``DecreeTree`` commands. .. automethod:: decree_tree.DecreeTree.add(child: DecreeTree | Type(DecreeTree)) -> DecreeTree 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... .. testcode:: testclasses 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: .. testoutput:: testclasses 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``. .. automethod:: decree_tree.DecreeTree.child_index Determine the list index of a particular child command in order to manipulate its containing list. Executing this... .. testcode:: testclasses root = Root() foo = root.add(Foo) bar = root.add(Bar) i = root.child_index('bar') print(f'{i=}') \... leads to this output: .. testoutput:: testclasses i=1 .. automethod:: decree_tree.DecreeTree.clear_children Empty out the list of children for the ``DecreeTree``. Executing this... .. testcode:: testclasses root = Root() foo = root.add(Foo) bar = root.add(Bar) print(root.structure) print('...') root.clear_children() print(root.structure) \... leads to this output: .. testoutput:: testclasses root: nesting.Root root -> foo: nesting.Foo root -> bar: nesting.Bar ... root: nesting.Root .. automethod:: decree_tree.DecreeTree.find_child This method is similar to ``get`` below, but can only retrieve a direct child of a single ``DecreeTree``. For example: .. testcode:: testclasses 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. .. automethod:: decree_tree.DecreeTree.get Retrieve a child from an arbitrary depth in a command tree. .. testcode:: testclasses 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``. .. automethod:: decree_tree.DecreeTree.insert_child Insert a child command to parents. Executing this... .. testcode:: testclasses root = Root() root.add(Foo) root.add(Bar) print(root.structure) print('...') root.insert_child(1, Baz) print(root.structure) \... leads to this output: .. testoutput:: testclasses 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 .. automethod:: decree_tree.DecreeTree.remove_child Insert a child command to parents. Executing this... .. testcode:: testclasses root = Root() root.add(Foo) root.add(Bar) print(root.structure) print('...') root.remove_child('foo') print(root.structure) \... leads to this output: .. testoutput:: testclasses 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. .. automethod:: decree_tree.DecreeTree.replace_child Replace a child command with another. Executing this... .. testcode:: testclasses 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: .. testoutput:: testclasses 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 .. automethod:: decree_tree.DecreeTree.sort_children Sort the direct children of a ``DecreeTree``. Grandchildren are retained unsorted. Executing this... .. testcode:: testclasses 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: .. testoutput:: testclasses 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 ********** .. autoproperty:: decree_tree.DecreeTree.children .. doctest:: testclasses >>> root = Root() >>> _ = root.add(Foo) >>> _ = root.add(Bar) >>> root.children [nesting.Foo(), nesting.Bar()] .. autoproperty:: decree_tree.DecreeTree.child_names .. doctest:: testclasses >>> root = Root() >>> _ = root.add(Foo) >>> _ = root.add(Bar) >>> root.child_names ['foo', 'bar'] .. autoproperty:: decree_tree.DecreeTree.count_children .. doctest:: testclasses >>> root = Root() >>> _ = root.add(Foo) >>> _ = root.add(Bar) >>> root.count_children 2 .. autoproperty:: decree_tree.DecreeTree.get_name .. doctest:: new-root-no-testclasses >>> from decree_tree import DecreeTree >>> class Root(DecreeTree): pass >>> root = Root() >>> root.get_name 'root' .. autoproperty:: decree_tree.DecreeTree.parent .. doctest:: testclasses >>> root = Root() >>> foo = root.add(Foo) >>> foo.parent nesting.Root() .. autoproperty:: decree_tree.DecreeTree.parents .. doctest:: testclasses >>> root = Root() >>> foo = root.add(Foo) >>> bar = foo.add(Foo) >>> bar.parents [nesting.Root(), nesting.Foo()] .. autoproperty:: decree_tree.DecreeTree.parent_names .. doctest:: testclasses >>> root = Root() >>> foo = root.add(Foo) >>> bar = foo.add(Foo) >>> bar.parent_names ['root', 'foo'] .. autoproperty:: decree_tree.DecreeTree.structure .. doctest:: testclasses >>> 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. .. automethod:: decree_tree.DecreeTree.configure_parser .. automethod:: decree_tree.DecreeTree.configure_parser_tree .. automethod:: decree_tree.DecreeTree.handle_run_exception Override this method to change the default error handling behavior. .. FIXME add example .. automethod:: decree_tree.DecreeTree.parser_options Override this method to provide additional arguments to the `argparse.ArgumentParser `__ constructor for this ``DecreeTree``, when used as the root command. .. FIXME add example .. automethod:: decree_tree.DecreeTree.preprocess_options .. automethod:: decree_tree.DecreeTree.subparsers_options Override this method to provide additional arguments to the `add_parser `__ call for this ``DecreeTree``, when used as a child command. .. FIXME add example