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)

Universal Command Line Interface for Amazon Web Services

This package provides a unified command line interface to Amazon Web Services.

Amazon Web Services 13.3k Jan 07, 2023
GDBIGtools: A command line tools for GDBIG varaints browser

GDBIGtools: A command line tools for GDBIG varaints browser Introduction Born in Guangzhou Cohort Study Genome Research Database is based on thousands

ๅนฟๅทžๅธ‚ๅ‡บ็”Ÿ้˜Ÿๅˆ—ๅŸบๅ› ็ป„ๅญฆ็ ”็ฉถ(The genomics study of BIGCS) 7 Sep 14, 2022
Low-Cost Open Source Ventilator or PAPR

Last updated 2020/04/19 Low-Cost Open-Source Ventilator-ish Device or PAPR NOTE: This is currently an independent project not affiliated with any comm

Johnny Lee 1.7k Dec 21, 2022
Convert ACSM files to DRM-free EPUB files with one command on Linux

Knock Convert ACSM files to DRM-free EPUB files using one command. This software does not utilize Adobe Digital Editions nor Wine. It is completely fr

Benton Edmondson 622 Dec 09, 2022
Pymongo based CLI client, to run operation on existing databases and collections

Mongodb-Operations-Console Pymongo based CLI client, to run operation on existing databases and collections Program developed by Gustavo Wydler Azuaga

Gus 1 Dec 01, 2021
gget is a free and open-source command-line tool and Python package that enables efficient querying of genomic databases.

gget is a free and open-source command-line tool and Python package that enables efficient querying of genomic databases. gget consists of a collection of separate but interoperable modules, each des

Pachter Lab 570 Dec 29, 2022
Bad Apple printed out on the console with Python!

Bad Apple printed out on the console with Python!

CalvinLoke 186 Dec 01, 2022
f90nml - A Fortran namelist parser, generator, and editor

f90nml - A Fortran namelist parser, generator, and editor A Python module and command line tool for parsing Fortran namelist files Documentation The c

Marshall Ward 110 Dec 14, 2022
A terminal client for connecting to hack.chat servers

A terminal client for connecting to hack.chat servers.

V9 2 Sep 21, 2022
A command line tool that creates a super timeline from SentinelOne's Deep Visibility data

S1SuperTimeline A command line tool that creates a super timeline from SentinelOne's Deep Visibility data What does it do? The script accepts a S1QL q

Juan Ortega 2 Feb 08, 2022
A terminal spreadsheet multitool for discovering and arranging data

VisiData v2.6.1 A terminal interface for exploring and arranging tabular data. VisiData supports tsv, csv, sqlite, json, xlsx (Excel), hdf5, and many

Saul Pwanson 6.2k Jan 04, 2023
๐ŸŒŒ A Python script to generate blog banners from command line.

Auto Blog Banner Generator A Python script to generate blog banners. This script is used at RavSam. The following image is an example of the blog bann

RavSam 10 Sep 20, 2022
A 3D engine powered by ASCII art

3D engine powered by ASCII art

Lingdong Huang 48 Nov 16, 2022
AutoSub is a CLI application to generate subtitle files (.srt, .vtt, and .txt transcript) for any video file using Mozilla DeepSpeech.

AutoSub About Motivation Installation Docker How-to example How it works TO-DO Contributing References About AutoSub is a CLI application to generate

Abhiroop Talasila 414 Jan 06, 2023
Command-line search tool for GitHub

cligh is a command-line search tool for GitHub.

1 Oct 02, 2022
A simple CLI to convert snapshots into EAVT log, and EAVT log into SCD.

EAVT helper CLI Simple CLI to convert snapshots into eavt log, and eavt log into slowly changing dimensions Usage Installation Snapshot to EAVT log EA

2 Apr 07, 2022
Konsave lets use save your KDE Plasma customizatios and restore them very easily!

Konsave (Save Plasma Customization) A CLI program that will let you save and apply your KDE Plasma customizations with just one command! Als

439 Jan 02, 2023
Themes for Windows Terminal

Windows Terminal Themes Preview and copy themes for the new Windows Terminal. Use the project at windowsterminalthemes.dev How to use the themes This

Tom 1.1k Jan 03, 2023
Gitfetch is a simple tool to get github user details

Gitfetch Just a (cli?) tool to get github user details ๐Ÿ™‚ Installation ๐Ÿ“‚ Install Gitfetch via pypi pip install gitfetch or pip install git+https://g

I'm Not A Bot #Left_TG 7 Jan 23, 2022
A command line application to analyse reports from TBC Warcraft Logs.

README A command line application to analyse reports from TBC Warcraft Logs. The application was written and tested with Python 3.9. Features Dumps an

2 Dec 17, 2021