Python module to run commands from CLI

×

Overview

module url https://pypi.org/project/py-runner-base.html
git repository https://bitbucket.org/arrizza-public/py-runner-base
git command git clone git@bitbucket.org:arrizza-public/py-runner-base.git
verification report https://arrizza.com/web-ver/py-runner-base-report.html
version info
OS Language #Runs Last Run Cov%
Ubuntu 24.04 noble Python 3.12 398 2026-01-28 100%

Summary

This project is a base class for running various commands in python from the command line.

see sample/toolbox_app.py for an example use.

see doc/api.md for the API available.

how to run

Don't specify a command gives you help. Note that you can use check() in toolbox_runner.py or run() in toolbox_app.py to specify a default command.

./doit 
WARN no command chosen, nothing to do
     py-runner-base: v0.1.0
     Choose one of these:
        task1               : run task1
        task2a              : run task2 step-a
        task2b              : run task2 step-b
        task3               : run task3
        task4               : run task4

Note: that there are several sorting orders available, see property sort_order in base_runner.py or cmd_map.py.

  • Add an argument arg1:
$ ./doit task1 arg1
==== doit: starting...
     running Task1 args:['arg1']
     doit: run_it rc=0
  • Run a command "task1" that has no prerequisites:
./doit task1
==== doit: starting...
     running Task1 args:[]
     doit: run_it rc=0
  • Run a command "task2b" that has a prerequisite:
./doit task2b
==== doit: starting...
     running Task2 step-a
     running Task2 step-b
     doit: run_it rc=0

how to add commands

See the sample/taskxx.py files for example tasks. Note register() functions that allow you to specify command names for any tasks you want to add.

    # --------------------
    def register(self, runner):
        self._runner = runner
        self._runner.add_cmd('task2a', self._run_step_a, 'run task2 step-a')
        self._runner.add_cmd('task2b', self._run_step_b, 'run task2 step-b', prereqs=['task2a'])

API

Typically three classes are needed:

  • xx_app.py: hold all the references, do all the setup and configuration, and then run the commands.
  • xx_runner.py: derives from RunnerBase and handles any special command setup, argument handling, etc.
  • xx_taskx.py : one or more classes to hold the code that implements the command(s)

xx_app.py

Typical:

# import task class(es) here

class ToolboxApp:
    def __init__(self):
        # any initialization here

        # create an instance of parent of RunnerBase 
        self._runner = ToolboxRunner()

        # build toolbox from the base and the mixins wanted
        tasks = (
            Task1,  # _ do Task1
            Task2,  # _ do Task2
        )

        # register() all the tasks.
        # register() can take a tuple, a list, or an individual class
        # Each tasks is class name, the register() instantiates it.
        self._runner.register(tasks)
        self._runner.register([Task3])
        self._runner.register(Task4)

    # the main.py invokes this function
    def run(self):
        # parse the CLI commands the user typed in
        self._runner.get_cli_info()
        # if there's any problems return
        if self._runner.rc != 0:
            return

        # do a double-check. Your version may or may not need this.
        self._runner.check()
        if self._runner.rc != 0:
            return

        # all is well, so run any/all the commands specified by the user
        # note that you can specify that only one command be called, or N, whatever is needed
        self._runner.run_all()

        # note that main.py will use svc.rc to ensure sys.exit() returns with the right rc. 
        svc.rc += self._runner.rc

xx_taskx.py

A typical class to register and run a command:

class Task1:
    def __init__(self):
        # needed to access any CLI args (or can use sys.argv[])
        self._runner = None

    def register(self, runner):
        # save the runner parent instance
        self._runner = runner

        # register a command "task1", the function to run and some help text
        self._runner.add_cmd('task1', self._run, 'run task1')

    def _run(self):
        # the command to run when task1 is declared
        # must return an rc so the sys.exit() can be set correctly
        errs = 0
        svc.log.line(f'running Task1 args:{self._runner.args}')
        return errs

xx_runner.py

The runner parent class:

from src.py_runner_base.runner_base import RunnerBase


# other imports as needed

class ToolboxRunner(RunnerBase):
    def __init__(self):
        super().__init__()
        # initialize the base runner and share the same logger
        self.init_runner_base(svc.log)

    def handle_unknown_arg(self, arg):
        # not a recognized command/task/step so assume it is an arg
        self._args.append(arg)
        return True

    def check(self):
        if self.nothing_chosen():
            svc.rc += 1
            svc.log.warn('no command chosen, nothing to do')
            self.report_err_msg()

API

init_runner_base

  • initialize the base content
  • logger: set the logger
  • prefix: set the prefix to use for any logging

sort_order (property and setter)

  • set the order that the commands are executed
    • created - (default) run as created via calls to register()
    • entered - run as entered
    • alpha - sort alphabetically
    • index - sort by index

add_cmd

  • called by task classes to register a command
  • cmd - the command name
  • cmd_fn - the function to invoke when this command is named
  • desc - help for this command
  • prereqs - any pre-requisite commands for this cmd
  • sort_idx - the sorting value/index for this cmd (see sort_order)

run_all

  • run all of the commands requested by the CLI. The commands are sorted according to the sort_order()
  • see run_cmd() for individual commands.

run_cmd

  • run the given command. Typically run via run_all().
    • cmd_name - the cmd name
    • returns an integer rc

rc

  • returns the current return code. Note the return code is a sum of all values returned by the invoked command functions

get_cli_info

  • get commands and args from the command line

handle_unknown_arg

  • callback when an unknown CLI arg is found by get_cli_info(). Normally this is overridden when using this base class
    • _arg - the CLI argument found
    • should return True if no error occurred, False when an error occurred

report_err_msg

  • reports the list of registered commands

nothing_chosen

  • returns True if the cmd list from the CLI is empty, False otherwise

cmds

  • returns the current set of commands that will be run

args

  • returns any CLI args entered
  • Note: "-n" is predefined. This causes run_cmd() to only write a log line to stdout.

dry_run

  • returns True if this a dry run. see "-n" CLI arg.

delete_cmd

  • delete the given command from the list of commands to run
    • cmd_name - the cmd to remove

- John Arrizza