python
42 lines · 6 steps
Building a subcommand CLI with argparse
How argparse subparsers dispatch to per-command handler functions in a Python tool.
Explained by
highlit
1import argparse
2import sys
3from pathlib import Path
4
5
6def cmd_init(args):
7 target = Path(args.directory)
8 target.mkdir(parents=True, exist_ok=True)
9 (target / "config.toml").write_text(f"name = \"{args.name}\"\n")
10 print(f"Initialized project in {target}")
11
12
13def cmd_build(args):
14 src = Path(args.source)
15 if not src.exists():
16 sys.exit(f"error: source {src} does not exist")
17 flags = ["--release"] if args.release else []
18 print(f"Building {src} {' '.join(flags)}".rstrip())
19
20
21def build_parser():
22 parser = argparse.ArgumentParser(prog="proj", description="Project tool")
23 parser.add_argument("-v", "--verbose", action="count", default=0)
24 sub = parser.add_subparsers(dest="command", required=True)
25
26 p_init = sub.add_parser("init", help="create a new project")
27 p_init.add_argument("directory")
28 p_init.add_argument("-n", "--name", default="untitled")
29 p_init.set_defaults(func=cmd_init)
30
31 p_build = sub.add_parser("build", help="build the project")
32 p_build.add_argument("source", nargs="?", default="src")
33 p_build.add_argument("--release", action="store_true")
34 p_build.set_defaults(func=cmd_build)
35
36 return parser
37
38
39def main(argv=None):
40 parser = build_parser()
41 args = parser.parse_args(argv)
42 args.func(args)
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Subparsers let one program expose distinct commands, each with its own arguments and defaults.
- 2Attaching a handler via set_defaults(func=...) turns argument parsing into clean command dispatch.
- 3Keeping parsing in build_parser and logic in cmd_* functions makes the CLI easy to test and extend.
Related explainers
python
from collections.abc import Mapping from typing import Any, Iterator
Flattening nested config into dotted keys
recursion
generators
tree-traversal
Intermediate
7 steps
python
import csv import io from datetime import datetime
Streaming a CSV export in Flask
streaming
generators
csv
Intermediate
9 steps
python
import time from collections import defaultdict from threading import Lock
Sliding-window login rate limiting in Flask
rate-limiting
sliding-window
thread-safety
Intermediate
7 steps
python
from django.conf import settings from django.contrib.auth import get_user_model from django.core.mail import EmailMultiAlternatives from django.db.models.signals import post_save
Sending a welcome email with Django signals
signals
email
user-activation
Intermediate
8 steps
python
import csv import io from datetime import date
Streaming a CSV export in FastAPI
streaming
async-generators
csv
Advanced
8 steps
python
import json import logging import stripe
Handling Stripe webhooks in Django
webhooks
signature-verification
idempotency
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-subcommand-cli-with-argparse-explained-python-2682/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.