Custom Tools¶
A tool is a subcommand of the StepUp CLI.
A StepUp tool can be called from the command line like any other console script with
stepup <tool> <args>.
Such tools are different in scope from console scripts:
they interact directly with the internals of StepUp and
require at least .stepup/graph.db to be present in the current working directory
(or under the ${STEPUP_ROOT} directory if that variable is set).
Unless you specifically need this low-level access,
prefer traditional Python console scripts for your extensions.
A new StepUp tool is created by defining two functions and registering them as entry points.
The examples below assume that you want to add a tool called fancy to the StepUp CLI.
-
Write a Python function that implements the tool, using a fixed signature. For example, a tool registered as the
fancysubcommand should have the signature:The
argsargument is aNamespaceobject that contains the command-line arguments passed to the tool. This signature is also available as the type aliasstepup.core.tool.ToolFunc.A tool does not return a return code. It reports a mistake that the user can fix by raising
ToolError, whichstepupturns into a short message on standard error, ending the command with return code1:from stepup.core.exceptions import ToolError def fancy_tool(args: argparse.Namespace) -> None: if not args.path.is_file(): raise ToolError(f"File does not exist: {args.path}")Any other exception keeps its traceback, because it points at a bug rather than at something the user can act on. Setting
STEPUP_DEBUGalso shows the traceback of aToolError.A tool that must end the command with a return code of its own calls
sys.exit, the waystepup buildreports the outcome of a build. This is not how an error is reported, because raising already covers that case. -
Write a second function that registers the argument parser, again with a fixed signature:
import argparse from stepup.core.config_loader import ConfigLoader from stepup.core.tool import ToolFunc def fancy_subcommand(subparsers, loader: ConfigLoader) -> ToolFunc: parser = subparsers.add_parser( "fancy", help="Description of the tool", ) parser.add_argument(...) ... loader.patch_parser(parser) return fancy_toolThe
subparsersargument is the sub-parsers object from the mainstepupargument parser. Theloaderargument is aConfigLoaderinstance that can be used to patch the parser with configuration file values (see existing tools instepup.corefor examples). The section it reads is the last word of the parser’sprog, here[fancy]. Patching never raises on a bad configuration: the problems are collected and reported bystepupitself, before it calls the tool, see Configuration. -
Create an entry point in
pyproject.tomlpointing to this function:where you replace
your.packagewith the name of the module that containsfancy_subcommand. The name of the entry point is the name of the subcommand, so it must be identical to the name passed tosubparsers.add_parser. StepUp refuses to start when the two differ, because the subcommand would otherwise be unreachable.
StepUp never imports these two functions directly: it only loads the registration function through the entry point, which in turn hands it the function implementing the tool. Both must therefore remain importable from the module named in the entry point, even though nothing seems to use them.