Skip to content

Instantly share code, notes, and snippets.

@amarao
Last active May 30, 2025 05:46
Show Gist options
  • Save amarao/36327a6f77b86b90c2bca72ba03c9d3a to your computer and use it in GitHub Desktop.
Save amarao/36327a6f77b86b90c2bca72ba03c9d3a to your computer and use it in GitHub Desktop.
Example of argparse with subparsers for python
#!/usr/bin/env python
import argparse
def main(command_line=None):
parser = argparse.ArgumentParser('Blame Praise app')
parser.add_argument(
'--debug',
action='store_true',
help='Print debug info'
)
subparsers = parser.add_subparsers(dest='command')
blame = subparsers.add_parser('blame', help='blame people')
blame.add_argument(
'--dry-run',
help='do not blame, just pretend',
action='store_true'
)
blame.add_argument('name', nargs='+', help='name(s) to blame')
praise = subparsers.add_parser('praise', help='praise someone')
praise.add_argument('name', help='name of person to praise')
praise.add_argument(
'reason',
help='what to praise for (optional)',
default="no reason",
nargs='?'
)
args = parser.parse_args(command_line)
if args.debug:
print("debug: " + str(args))
if args.command == 'blame':
if args.dry_run:
print("Not for real")
print("blaming " + ", ".join(args.name))
elif args.command == 'praise':
print('praising ' + args.name + ' for ' + args.reason)
if __name__ == '__main__':
main()
@mpkocher
Copy link

It can be useful to define a utility function to avoid duplication when defining subparsers.

#!/usr/bin/env python
import logging
import sys
from pathlib import Path
import argparse
from argparse import ArgumentParser, Namespace
from typing import Callable

logger = logging.getLogger(__name__)


def run_alpha(input_path: Path) -> int:
    logger.info(f"Running alpha for {input_path}")
    return 0


def run_beta(src: Path, dest: Path) -> int:
    logger.info(f"Running alpha with {src=} {dest=}")
    return 0


def _to_parser_alpha(p: ArgumentParser) -> ArgumentParser:
    p.add_argument("-i", "--input", type=Path, required=True)
    return p


def _to_parser_beta(p: ArgumentParser) -> ArgumentParser:
    p.add_argument("-s", "--src", type=Path, required=True)
    p.add_argument("-d", "--dest", type=Path, required=True)
    return p


def get_parser() -> ArgumentParser:
    p = ArgumentParser(prog="example.py")
    sp = p.add_subparsers()

    def _add(
        name: str,
        add_opts: Callable[[ArgumentParser], ArgumentParser],
        func: Callable[[Namespace], int],
        help_: str,
    ) -> argparse.ArgumentParser:
        px = sp.add_parser(name, help=help_)
        add_opts(px)
        px.set_defaults(func=func)
        return px

    _add("alpha", _to_parser_alpha, lambda ns: run_alpha(ns.input), "Running alpha")
    _add("beta", _to_parser_beta, lambda ns: run_beta(ns.src, ns.dest), "Run beta")

    p.add_argument("--version", action="version", version="0.1.0")
    return p


def main(argv: list[str]) -> int:
    logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
    p = get_parser()
    pargs = p.parse_args(argv)
    return pargs.func(pargs)


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

You can also get shell autocomplete functionality by adding shtab.

https://github.com/iterative/shtab

Support can added by:

import stab

# Add in get_parser()
shtab.add_argument_to(p)

Yielding --help:

 python shtab_test.py --help                
usage: example.py [-h] [--version] [--print-completion {bash,zsh,tcsh}] {alpha,beta} ...

positional arguments:
  {alpha,beta}
    alpha               Running alpha
    beta                Run beta

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit
  --print-completion {bash,zsh,tcsh}
                        print shell completion script

Then emitting the bash/zsh completion to stdout by:

python example.py --print-completion zsh 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment