Simple subcommand CLIs with argparse

Overview

multicommand

Simple subcommand CLIs with argparse.

PyPI Version Downloads

multicommand uses only the standard library and is ~150 lines of code (modulo comments and whitespace)

Installation

pip install multicommand

Overview

Multicommand enables you to easily write CLIs with deeply nested commands using vanilla argparse. You provide it with a package, it searches that package for parsers (ArgumentParser objects), and connects, names, and converts those parsers into subcommands based on the package structure.

        Package                       ->                    CLI


commands/unary/negate.py                            mycli unary negate ...
commands/binary/add.py                              mycli binary add ...
commands/binary/divide.py             ->            mycli binary divide ...
commands/binary/multiply.py                         mycli binary multiply ...
commands/binary/subtract.py                         mycli binary subtract ...

All it needs is for each module to define a module-level parser variable which points to an instance of argparse.ArgumentParser.

Motivation

I like argparse. It's flexible, full-featured and it's part of the standard library, so if you have python you probably have argparse. I also like the "subcommand" pattern, i.e. one root command that acts as an entrypoint and subcommands to group related functionality. Of course, argparse can handle adding subcommands to parsers, but it's always felt a bit cumbersome, especially when there are many subcommands with lots of nesting.

If you've ever worked with technologies like Next.js or oclif (or even if you haven't) there's a duality between files and "objects". For Next.js each file under pages/ maps to a webpage, in oclif each module under commands/ maps to a CLI command. And that's the basic premise for multicommand: A light-weight package that lets you write one parser per file, pretty much in isolation, and it handles the wiring, exploiting the duality between command structure and file system structure.

Getting Started

See the simple example, or for the impatient:

Create a directory to work in, for example:

mkdir ~/multicommand-sample && cd ~/multicommand-sample

Install multicommand:

python3 -m venv ./venv
source ./venv/bin/activate

python3 -m pip install multicommand

Create the subpackage to house our parsers:

mkdir -p mypkg/parsers/topic/cmd/subcmd

Create the *.py files required for the directories to be packages

touch mypkg/__init__.py
touch mypkg/parsers/__init__.py
touch mypkg/parsers/topic/__init__.py
touch mypkg/parsers/topic/cmd/__init__.py
touch mypkg/parsers/topic/cmd/subcmd/{__init__.py,greet.py}

Add a parser to greet.py:

# file: mypkg/parsers/topic/cmd/subcmd/greet.py
import argparse


def handler(args):
    greeting = f'Hello, {args.name}!'
    print(greeting.upper() if args.shout else greeting)


parser = argparse.ArgumentParser(
    description='My first CLI with multicommand',
    formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('name', help='Name to use in greeting')
parser.add_argument('--shout', action='store_true', help='Yell the greeting')
parser.set_defaults(handler=handler)

lastly, add an entrypoint:

touch mypkg/cli.py

with the following content:

# file: mypkg/cli.py
import multicommand
from . import parsers


def main():
    parser = multicommand.create_parser(parsers)
    args = parser.parse_args()
    if hasattr(args, 'handler'):
        args.handler(args)
        return
    parser.print_help()


if __name__ == "__main__":
    exit(main())

Try it out!

$ python3 -m mypkg.cli
usage: cli.py [-h] {topic} ...

optional arguments:
  -h, --help  show this help message and exit

subcommands:

  {topic}

Take a look at our greet command:

$ python3 -m mypkg.cli topic cmd subcmd greet --help
usage: cli.py topic cmd subcmd greet [-h] [--shout] name

My first CLI with multicommand

positional arguments:
  name        Name to use in greeting

optional arguments:
  -h, --help  show this help message and exit
  --shout     Yell the greeting (default: False)

From this we get:

$ python3 -m mypkg.cli topic cmd subcmd greet "World"
Hello, World!

$ python3 -m mypkg.cli topic cmd subcmd greet --shout "World"
HELLO, WORLD!

Bonus

Want to add the command topic cmd ungreet ... to say goodbye?

Add the module:

touch mypkg/parsers/topic/cmd/ungreet.py

with contents:

# file: mypkg/parsers/topic/cmd/ungreet.py
import argparse


def handler(args):
    print(f'Goodbye, {args.name}!')


parser = argparse.ArgumentParser(description='Another subcommand with multicommand')
parser.add_argument('name', help='Name to use in un-greeting')
parser.set_defaults(handler=handler)

The new command is automatically added!:

$ python3 -m mypkg.cli topic cmd --help
usage: cli.py cmd [-h] {subcmd,ungreet} ...

optional arguments:
  -h, --help        show this help message and exit

subcommands:

  {subcmd,ungreet}

Try it out:

$ python3 -m mypkg.cli topic cmd ungreet "World"
Goodbye, World!
You might also like...
This a simple tool to query the awesome ippsec.rocks website from your terminal
This a simple tool to query the awesome ippsec.rocks website from your terminal

ippsec-cli This a simple tool to query the awesome ippsec.rocks website from your terminal Installation and usage cd /opt git clone https://github.com

Simple Tool To Grab Like-Card Coupon
Simple Tool To Grab Like-Card Coupon

Simple Tool To Grab Like-Card Coupon

(BionicLambda Universal SHell) A simple shell made in Python. Docs and possible C port incoming.

blush 😳 (BionicLambda Universal SHell) A simple shell made in Python. Docs and possible C port incoming. Note: The Linux executables were made on Ubu

A simple reverse shell in python

RevShell A simple reverse shell in python Getting started First, start the server python server.py Finally, start the client (victim) python client.py

Un module simple pour demander l'accord de l'utilisateur dans une CLI.
Un module simple pour demander l'accord de l'utilisateur dans une CLI.

Demande de confirmation utilisateur pour CLI Présentation ask_lib est un module pour le langage Python proposant une seule fonction; ask(). Le but pri

💻 Physics2Calculator - A simple and powerful calculator for Physics 2
💻 Physics2Calculator - A simple and powerful calculator for Physics 2

💻 Physics2Calculator A simple and powerful calculator for Physics 2 🔌 Predefined constants pi = 3.14159... k = 8988000000 (coulomb constant) e0 = 8.

Simple script to download OTA packages from Realme's endpoint.

Realme OTA Downloader CLI tool (based on this C# program) to create requests to the Realme's endpoint. Requirements Python 3.9. pycryptodome. Installa

A simple command line dumper written in Python 3.

A simple command line dumper written in Python 3.

A simple cli tool to commit Conventional Commits

convmoji A simple cli tool to commit Conventional Commits. Requirements Install pip install convmoji convmoji --help Examples A conventianal commit co

Releases(1.0.0)
  • 0.1.1(Jun 8, 2021)

  • 0.1.0(May 24, 2021)

    • Make sure to always initialize a root index parser (if one doesn't already exist) so that multicommand.create_parser(...) always returns a useable ArgumentParser (instead of raising an exception).

      This way multicommand.create_parser(...) can be called on a package that has no parsers, and will still behave sensibly.

    • Check that found parsers are actually (sub-classes of) ArgumentParser, skip them if they aren't.

    • Fix bug in _requires_subparsers

    • Improve help for subcommands

    Source code(tar.gz)
    Source code(zip)
  • 0.0.8(Apr 9, 2021)

    • Fix: Fix prog=... for intermediate index parsers. Prior to this these parsers would only show the command name (sys.argv[0]) and the last parser's name, but none of the intermediate parser's names, which meant the usage string was wrong.
    • Update: Add a license (MIT)
    • Update: Change registry data structure from List[Tuple[PurePath, ArgumentParser]] -> OrderedDict[PurePath, Dict[str, ArgumentParser]]
    Source code(tar.gz)
    Source code(zip)
  • 0.0.7(Apr 6, 2021)

  • 0.0.6(Apr 5, 2021)

    • Update: Refactored multicommand.py to simplify the structure of the create_parser(...) function. Moreover, the keys in the parser registry (OrderedDict) are now pathlib.PurePath objects instead of tuples. The motivation for this change was because the registry keys (Tuple[str, ...] objects) were already basically being treated like pathlib.Path objects and it made the registry key manipulation easier to read and understand.
    • Update: pyproject.toml (added keywords)
    Source code(tar.gz)
    Source code(zip)
  • 0.0.5(Apr 4, 2021)

    • Update: load_parsers now uses pkg.__name__ when loading parsers into the parser registry (OrderedDict). Prior to this, the value commands was hardcoded meaning that for multicommand to work the subpackage housing all of the parsers had to be called mypkg.<blah>.commands.
    • Update: Documentation
      • General housekeeping: Fixed typos, fixed broken links, added PyPI badge, etc.
    Source code(tar.gz)
    Source code(zip)
  • 0.0.4(Apr 3, 2021)

    • Fix: Fixed the scenario where "intermediate" commands were not defined. When that happened multicommand wouldn't have intermediate parsers to link terminal parsers to the root parser.

      Specifically, if there was say a single command defined as: commands/mytopic/mycommand/mysubcommand.py (with appropriate __init__.py files) multicommand would crash, because the index parsers required to exist "between" the parser defined in mysubcommand.py and the root parser (created by multicommand) weren't getting created, and the application would crash.

    This release also adds a very basic README and a simple example demonstrating the basic usage of multicommand.

    Source code(tar.gz)
    Source code(zip)
  • 0.0.3(Apr 2, 2021)

    • Update: Forward parser config on subparser creation
    • Update: Add custom prog=... when linking parsers to accurately reflect the expected command invocation
    • Maintenance: Add comments, clean up type hints, update pyproject.toml
    Source code(tar.gz)
    Source code(zip)
  • 0.0.2(Apr 1, 2021)

nbcommands bring the goodness of Unix commands to Jupyter notebooks.

nbcommands nbcommands bring the goodness of Unix commands to Jupyter notebooks. Installation You can simply use pip to install nbcommands: $ pip insta

Vinayak Mehta 181 Dec 23, 2022
triggercmd is a CLI client for the TRIGGERcmd cloud service.

TriggerCMD CLI client triggercmd is a CLI client for the TRIGGERcmd cloud service. installation the triggercmd package is available in PyPI. to instal

Gustavo Soares 7 Oct 18, 2022
🎄 Advent of Code command-line tool.

🎄 advent-cli advent-cli is a command-line tool for interacting with Advent of Code, specifically geared toward writing solutions in Python. It can be

Christian Ferguson 6 Dec 01, 2022
Rover is a command line interface application that allows through browse through mission data, images, metadata from the NASA Official Website

🤖 rover Rover is a command line interface application that allows through browse through mission data, images, metadata from the NASA Official Websit

Saketha Ramanjam 4 Jan 19, 2022
The project help you to quickly build layouts in terminal,cross-platform

The project help you to quickly build layouts in terminal,cross-platform

gojuukaze 133 Nov 30, 2022
Jupyter notebook client in neovim

🪐 Jupyter-Nvim Read jupyter notebooks in neovim Note: The plugin is still in alpha stage 👾 Usage Just open any *.ipynb file and voila! ✨ Contributin

Ahmed Khalf 85 Dec 29, 2022
🕰 The command line tool for scheduling Python scripts

hickory is a simple command line tool for scheduling Python scripts.

Max Humber 146 Dec 07, 2022
Hack-All is a simple CLI tool that helps ethical-hackers to make a reverse connection without knowing the target device in use is it computer or phone

Hack-All is a simple CLI tool that helps ethical-hackers to make a reverse connection without knowing the target device in use is it computer

LightYagami17 5 Nov 22, 2022
Terminal Colored Text for Python

Terminal Colored Text for Python

R3CKhi-**75 3 Sep 10, 2022
An interactive cheatsheet tool for the command-line

navi An interactive cheatsheet tool for the command-line. navi allows you to browse through cheatsheets (that you may write yourself or download from

Denis Isidoro 12.2k Dec 31, 2022
Double Pendulum visualised with fetching system information in Python.

Show off your terminal, in style. A nice relaxing double pendulum simulation using ASCII, able to simulate multiple pendulums at once, and provide tra

Nekurone 62 Dec 14, 2022
jenkins-tui is a terminal based user interface for Jenkins.

jenkins-tui 📦 jenkins-tui is a terminal based user interface for Jenkins. 🚧 ⚠️ This app is a prototype and in very early stages of development. Ther

Craig Gumbley 22 Oct 24, 2022
Synchronization tool for external devices which does not support time stamps, e.g. over MTP.

MTP-Sync Tool to synchronize data to a slow device, e.g. a smartphone which is connected over MTP. A state file (.mtp_sync_state.json) is created in t

2 Jul 22, 2022
term2048 is a terminal-based version of 2048.

term2048 is a terminal-based version of 2048.

Baptiste Fontaine 798 Nov 21, 2022
A CLI minesweeper application written in 60 LoC python

This is a CLI minesweeper application written in 60 LoC python. You can use d row,column to dig and f row,column to flag/unflag

1 Dec 21, 2021
ctree - command line christmas tree

ctree ctree - command line christmas tree It is small python script that prints a christmas tree in terminal. It is colourful and always gives you a d

15 Aug 15, 2022
Generate your name in Ascii modular type art through the terminal

ASCII Name Generator Designed and developed by Eduardo Aire The ASCII Art Name Generator is a simple program that helps you to have a practical Shell/

Eduardo Aire 1 Nov 17, 2021
Wordle-solver - A tool that helps people who struggle with vocabulary to enjoy the famous game of WORDLE

Wordle-Solver Wordle-Solver helps people who struggle with vocabulary to enjoy t

Jason Chao 104 Dec 31, 2022
A CLI Application to detect plagiarism in Source Code Files.

Plag Description A CLI Application to detect plagiarism in Source Code Files. Features Compare source code files for plagiarism. Extract code features

default=dev 2 Nov 10, 2022
This project contains the ClonedPerson dataset and code described in our paper "Cloning Outfits from Real-World Images to 3D Characters for Generalizable Person Re-Identification".

ClonedPerson This is the official repository for the ClonedPerson project, which contains the ClonedPerson dataset and code described in our paper "Cl

Yanan Wang 55 Dec 27, 2022