A dynamic argparse command loader for building modular CLIs.
argparts automatically discovers and registers subcommands from your Python modules, eliminating the need for manual argparse subparser registration. Organize your commands using directory hierarchies, double-underscore (__) filename separation, or a mix of both.
If you're building a CLI with multiple subcommands, you've probably written code like this dozens of times:
subparsers = parser.add_subparsers()
user_parser = subparsers.add_parser('user')
user_subparsers = user_parser.add_subparsers()
add_parser = user_subparsers.add_parser('add')
# ... and so onargparts eliminates this boilerplate. Drop your command files into a directory structure, and they're automatically registered. Each command needs just two functions: one to configure its arguments, and one to execute.
Install argparts using pip:
pip install argpartsYou can organize commands using directories, __ separators in filenames, or both:
Directory-based (commands/user/add.py):
commands/
├── __init__.py
└── user/
├── __init__.py
└── add.py
Filename-based (commands/user__add.py):
commands/
├── __init__.py
└── user__add.py
Mixed (commands/admin__db/connect.py):
commands/
├── __init__.py
└── admin__db/
├── __init__.py
└── connect.py
Each command file needs two functions:
# commands/user__add.py
import argparse
def argparts_setup_parser(subparsers, command_name):
"""Sets up the argument parser for this command."""
parser = subparsers.add_parser(
command_name,
help="Add a new user"
)
parser.add_argument("username", help="Username for the new user")
parser.add_argument("--email", help="User's email address")
# Link the execution function
parser.set_defaults(func=run_command)
def run_command(args):
"""Executed when the command is run."""
print(f"Creating user: {args.username}")
if args.email:
print(f"Email: {args.email}")# run.py
from argparts import create_command_parser
import commands
def main():
parser = create_command_parser(commands, description="My CLI tool")
args = parser.parse_args()
if hasattr(args, "func"):
args.func(args)
else:
parser.print_help()
if __name__ == "__main__":
main()$ python run.py user add john --email john@example.com
Creating user: john
Email: john@example.com
$ python run.py user --help
usage: run.py user [-h] {add} ...
positional arguments:
{add}
add Add a new userYou have three options for organizing commands:
Directory hierarchy - Good for larger projects:
commands/
├── user/
│ ├── add.py → user add
│ └── delete.py → user delete
└── admin/
└── settings/
└── config.py → admin settings config
Filename separation with __ - Good for flatter structures:
commands/
├── user__add.py → user add
├── user__delete.py → user delete
└── admin__config.py → admin config
Mixed approach - Use both:
commands/
├── user__db/
│ ├── connect.py → user db connect
│ └── backup.py → user db backup
└── admin/
└── user__list.py → admin user list
The examples/ directory contains working examples:
single_app/- Simple single-level commandsdunder_app/- Using__filename separationdirs_app/- Directory-based hierarchymixed_app/- Combining both approaches
Try them out:
cd examples/dunder_app
python run.py --helpPython 3.8+ with no external dependencies.
Run tests:
pip install -e ".[testing]"
pytestSee tests/README.md for details.
Pull requests welcome. For major changes, open an issue first.
MIT License - see LICENSE for details.
Copyright (c) 2025 davfive (davfive@gmail.com)