CLI application structure
coreintermediateadd_subparsers() splits one program into named subcommands (tool migrate, tool status), each with its own arguments and its own function — instead of one parser with a growing pile of flags.
Think of it as
A CLI with one flat argument list is one long form with every field visible at once, whether relevant or not. Subcommands split that into separate forms — migrate has --target, status has none — and each routes to its own handler function, the same way separate view functions handle separate URL routes.
What we're doing: Build a two-subcommand CLI (migrate, status) where each subcommand owns its own arguments and dispatches to its own function.
- 9
- add_subparsers(dest="command", required=True) turns dbtool into a program that needs a subcommand — dbtool migrate, dbtool status.
- 10
- sub.add_parser("migrate") creates a full ArgumentParser scoped to migrate; --target added here does not exist under status.
- 13
- set_defaults(func=cmd_migrate) attaches the handler directly to the parsed args — no if args.command == "migrate": chain needed.
- 18
- args.func(args) calls whichever handler matched the subcommand that was actually given.
migrating to 0007
status: okWhy this works: Each subcommand gets its own ArgumentParser scoped under add_subparsers(), so migrate can require --target while status needs no arguments at all — and set_defaults(func=...) lets parse_args() itself carry the correct handler, replacing a manual if/elif dispatch on args.command.
Growing one flat argument list instead of splitting into subcommands
Wrong
Better
What you see: --help lists every flag for every operation at once, unrelated flags can be combined by mistake (--migrate --status), and the handler needs an if/elif chain to figure out which operation actually ran.
Why: Boolean flags for each operation do not stop two of them being passed together, and a flag meaningful only to one operation (--target) still shows in --help for all of them. Subparsers make each subcommand its own scoped argument set — the ambiguity does not exist to guard against.
- sys.argv — dbtool migrate --target 0007
- leads to ArgumentParser
- ArgumentParser — add_subparsers(dest="command")
- leads to migrate subparser (command == "migrate")
- leads to status subparser (command == "status")
- migrate subparser — --target flag, func=cmd_migrate
- leads to args.func(args)
- status subparser — func=cmd_status
- leads to args.func(args)
- args.func(args) — runs the matched handler
Structuring a multi-command CLI
Together
Remember: add_subparsers() turns one flat argument list into named subcommands, each with its own arguments and its own set_defaults(func=...) handler — no if/elif dispatch needed.
See also: argparse module · environment variables and exit codes · deployment and developer tooling

