Files
2025-11-30 09:04:14 +08:00

6.2 KiB

Subcommands with argparse

Multi-command CLI like git, docker, or kubectl using subparsers.

Template Reference

templates/subparser-pattern.py

Overview

Create CLIs with multiple commands:

  • mycli init - Initialize project
  • mycli deploy production - Deploy to environment
  • mycli status - Show status

Each subcommand has its own arguments and options.

Quick Start

# View main help
python subparser-pattern.py --help

# View subcommand help
python subparser-pattern.py init --help
python subparser-pattern.py deploy --help

# Execute subcommands
python subparser-pattern.py init --template react
python subparser-pattern.py deploy production --force
python subparser-pattern.py status --format json

Key Patterns

1. Create Subparsers

parser = argparse.ArgumentParser(description='Multi-command CLI')

subparsers = parser.add_subparsers(
    dest='command',        # Store command name in args.command
    help='Available commands',
    required=True          # At least one command required (Python 3.7+)
)

Important: Set dest='command' to access which command was used.

2. Add Subcommand

init_parser = subparsers.add_parser(
    'init',
    help='Initialize a new project',
    description='Initialize a new project with specified template'
)

init_parser.add_argument('--template', default='basic')
init_parser.add_argument('--path', default='.')

Each subcommand is a separate parser with its own arguments.

3. Set Command Handler

def cmd_init(args):
    """Initialize project."""
    print(f"Initializing with {args.template} template...")

init_parser.set_defaults(func=cmd_init)

Dispatch pattern:

args = parser.parse_args()
return args.func(args)  # Call the appropriate handler

4. Subcommand with Choices

deploy_parser = subparsers.add_parser('deploy')

deploy_parser.add_argument(
    'environment',
    choices=['development', 'staging', 'production'],
    help='Target environment'
)

deploy_parser.add_argument(
    '--mode',
    choices=['fast', 'safe', 'rollback'],
    default='safe'
)

Complete Example

#!/usr/bin/env python3
import argparse
import sys


def cmd_init(args):
    print(f"Initializing with {args.template} template")
    return 0


def cmd_deploy(args):
    print(f"Deploying to {args.environment}")
    return 0


def main():
    parser = argparse.ArgumentParser(description='My CLI Tool')
    parser.add_argument('--version', action='version', version='1.0.0')

    subparsers = parser.add_subparsers(dest='command', required=True)

    # Init command
    init_parser = subparsers.add_parser('init', help='Initialize project')
    init_parser.add_argument('--template', default='basic')
    init_parser.set_defaults(func=cmd_init)

    # Deploy command
    deploy_parser = subparsers.add_parser('deploy', help='Deploy app')
    deploy_parser.add_argument(
        'environment',
        choices=['dev', 'staging', 'prod']
    )
    deploy_parser.set_defaults(func=cmd_deploy)

    # Parse and dispatch
    args = parser.parse_args()
    return args.func(args)


if __name__ == '__main__':
    sys.exit(main())

Help Output

Main Help

usage: mycli [-h] [--version] {init,deploy,status} ...

Multi-command CLI tool

positional arguments:
  {init,deploy,status}  Available commands
    init                Initialize a new project
    deploy              Deploy application to environment
    status              Show deployment status

optional arguments:
  -h, --help            show this help message and exit
  --version             show program's version number and exit

Subcommand Help

$ python mycli.py deploy --help

usage: mycli deploy [-h] [-f] [-m {fast,safe,rollback}]
                    {development,staging,production}

positional arguments:
  {development,staging,production}
                        Target environment

optional arguments:
  -h, --help            show this help message and exit
  -f, --force           Force deployment without confirmation
  -m {fast,safe,rollback}, --mode {fast,safe,rollback}
                        Deployment mode (default: safe)

Accessing Parsed Values

args = parser.parse_args()

# Which command was used?
print(args.command)  # 'init', 'deploy', or 'status'

# Command-specific arguments
if args.command == 'deploy':
    print(args.environment)  # 'production'
    print(args.force)        # True/False
    print(args.mode)         # 'safe'

Common Patterns

Pattern 1: Switch on Command

args = parser.parse_args()

if args.command == 'init':
    init_project(args)
elif args.command == 'deploy':
    deploy_app(args)
elif args.command == 'status':
    show_status(args)

Pattern 2: Function Dispatch (Better)

# Set handlers
init_parser.set_defaults(func=cmd_init)
deploy_parser.set_defaults(func=cmd_deploy)

# Dispatch
args = parser.parse_args()
return args.func(args)

Pattern 3: Check if Command Provided

args = parser.parse_args()

if not args.command:
    parser.print_help()
    sys.exit(1)

Note: Use required=True in add_subparsers() to make this automatic.

Common Mistakes

Wrong: Forgetting dest

subparsers = parser.add_subparsers(dest='command')  # ✓ Can check args.command
subparsers = parser.add_subparsers()  # ✗ Can't access which command

Wrong: Accessing wrong argument

# deploy_parser defines 'environment'
# init_parser defines 'template'

args = parser.parse_args(['deploy', 'prod'])
print(args.environment)  # ✓ Correct
print(args.template)     # ✗ Error - not defined for deploy

Wrong: No required=True (Python 3.7+)

subparsers = parser.add_subparsers(dest='command', required=True)  # ✓
subparsers = parser.add_subparsers(dest='command')  # ✗ Command optional
# User can run: python mycli.py (no command)

Nested Subcommands

For multi-level commands like git config get, see:

  • nested-commands.md
  • templates/nested-subparser.py

Next Steps

  • Nested Commands: See nested-commands.md
  • Validation: See validation-patterns.md
  • Complex CLIs: See advanced-parsing.md