Configuration ############# The :class:`~decree_tree.DecreeTree` class can be configured in several ways, many of which are documented on this page. Refer to :doc:`nesting` for a discussion of now to nest commands and configure the nesting process. General Workflow **************** There are three stages to the use of :class:`~decree_tree.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: * :meth:`~decree_tree.DecreeTree.add_arguments` is defined to specify the :ref:`options and arguments ` to be applied for that command (and its children, by default). * :meth:`~decree_tree.DecreeTree.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. * :meth:`~decree_tree.DecreeTree.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 :ref:`command name ` and its :ref:`help text `. For the basic tree example, the definition of some of the classes is shown below. ``BasicCommand`` is defined elsewhere. .. literalinclude:: code/basic_tree.py :language: python :caption: Part 1 of ``basic_tree.py`` :lines: 5-29 :ref:`Abstract parent classes ` may be employed in the definition of these command classes. In addition, if knowledge of the tree structure is known, and :ref:`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 :ref:`nesting:Nesting Commands` and :ref:`decreetree:Child Command Manipulation`. The most common method used for tree construction is :meth:`~decree_tree.DecreeTree.add`, likely followed by :meth:`~decree_tree.DecreeTree.get`. For the basic tree example, stage 2 is represented by these lines of code: .. literalinclude:: code/basic_tree.py :language: python :caption: Part 2 of ``basic_tree.py`` :lines: 31-34 Complex structures can be defined. Stage 3: Runtime ================ Finally, to start command-line processing, the :meth:`~decree_tree.DecreeTree.run` method is invoked. The options to ``run()`` are described in :ref:`configuration:Runtime Configuration`, but by default, the actual arguments on the command line are processed. For the basic tress example, this is short: .. literalinclude:: code/basic_tree.py :language: python :caption: Part 3 of ``basic_tree.py`` :lines: 36- Once ``run()`` is called, several steps occur: 1. The parser tree, including the parser for each ``DecreeTree``, is created, using :meth:`~decree_tree.DecreeTree.configure_parser` and :meth:`~decree_tree.DecreeTree.configure_subparsers`. 2. For each parser, the appropriate arguments are added by invoking :meth:`~decree_tree.DecreeTree.add_arguments`. 3. :meth:`~decree_tree.DecreeTree.preprocess_options` is called, which itself calls `arparse.ArgumentParser.parse_args `__ to read and parse the options on the command line. 4. :meth:`~decree_tree.DecreeTree.set_options` propagates the initial set of options data to all classes in the selected branch of the command tree. 5. :meth:`~decree_tree.DecreeTree.process_options` performs any additional input data manipulation, as described above. 6. Ultimately :meth:`~decree_tree.DecreeTree.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 :class:`~decree_tree.DecreeTree` subclass has a name associated with it, stored in the :attr:`~decree_tree.DecreeTree.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 :meth:`~decree_tree.DecreeTree.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. * `Argument groups `__ * `Mutually exclusive arguments `__ 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 :ref:`nesting:Inheritance`, and so calling ``super().add_arguments(parser)`` is critical. .. FIXME note some things like easy pathlib.Path actions/types? e.g. ``type=lambda p: pathlib.Path(p).absolute()`` Parsers and Subparsers ********************** This can be overridden by using the ``argument_parser_class`` argument in the call to :meth:`~decree_tree.DecreeTree.run`, as described in :ref:`configuration: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 :meth:`~decree_tree.DecreeTree.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 :meth:`~decree_tree.DecreeTree.subparsers_options` and passing the appropriate argument as a key-value pair in its return dict. Similarly, :meth:`~decree_tree.DecreeTree.configure_parser` configures the actual parser at for each subcommand. This can be overridden if substantially different behavior is required, however :meth:`~decree_tree.DecreeTree.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 :attr:`~decree_tree.DecreeTree.argument_parser_class`, which can be overridden in the call to :meth:`~decree_tree.DecreeTree.run` per :ref:`configuration:Runtime Configuration`. The Help Option *************** .. FIXME add more config details to this section? 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 :class:`~decree_tree.DecreeTree` class, but this can be overridden by defining a :attr:`~decree_tree.DecreeTree.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 :meth:`~decree_tree.DecreeTree.parser_options` and :meth:`~decree_tree.DecreeTree.subparsers_options`. Specifically, returning ``{'add_help': False}`` from the former will disable the help argument entirely. See the :ref:`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 :class:`~decree_tree.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. .. literalinclude:: code/version.py :language: python :caption: ``version.py`` :lines: 3- When ``version.py`` called from the command line, the version is shown: .. shtest:: :cwd: code $ python version.py --version version.py the first one Beware that if ``super()`` is not properly invoked on subclass :meth:`~decree_tree.DecreeTree.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 :meth:`~decree_tree.DecreeTree.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 :ref:`nesting: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 :ref:`development: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 :ref:`development:Testing` for more details. * The ``debug_tracing`` Boolean argument can be used to enable printing of debug statements. See :ref:`development:Debugging` for more details. * To provide better usage messages when unexpected arguments are detected, ``decree-tree`` uses the enhanced :class:`~decree_tree.parser.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 :meth:`~decree_tree.DecreeTree.handle_run_exception` method in the ``DecreeTree`` subclass that calls ``run()``