Python argparse: Build CLI Tools in 10 Minutes
Source: Dev.to
๐ Free: AI Publishing Checklist โ 7 steps in Python
Full pipeline: (pay what you want, minโฏ$9.99)
The Problem
You write a quick script, hardโcode a filename, then immediately need to change it.
You reach for sys.argv:
import sys
filename = sys.argv[1]
count = int(sys.argv[2])
It worksโฆ until it doesnโt:
- Run it without arguments โ
IndexError. - Pass a nonโinteger where an integer is expected โ crash.
- No help text, no validation, no defaults.
Anyone else who picks up your script has to read the source to know how to run it.
Why argparse?
argparse (standard library) solves all of this.
A single call to parse_args() handles:
- Reading
sys.argv - Validating inputs
- Printing help (
--help)
Basic Boilerplate
import argparse
parser = argparse.ArgumentParser(
description="My CLI tool โ does useful things."
)
args = parser.parse_args()
Positional Arguments
Required and identified by position, not name:
parser.add_argument("filename", help="Path to the input file")
parser.add_argument("count", help="Number of items to process")
Optional (Flag) Arguments
Use -- prefix; short aliases are optional:
parser.add_argument(
"--output", "-o",
help="Output file path",
default="output.txt"
)
parser.add_argument(
"--verbose", "-v",
help="Enable verbose logging",
action="store_true"
)
Type Conversion & Choices
Let argparse do the conversion and validation:
parser.add_argument(
"--count", type=int, default=10,
help="Number of items"
)
parser.add_argument(
"--rate", type=float, default=1.5,
help="Processing rate"
)
parser.add_argument(
"--format",
choices=["json", "csv", "txt"],
default="json",
help="Output format"
)
If a user runs --count hello, argparse prints a clean error and exitsโno stack trace.
nargs and Lists
| Example | Meaning |
|---|---|
parser.add_argument("--title", required=True, help="Article title (required)") | Required optional argument |
parser.add_argument("--tags", nargs="+", help="One or more tags") | One or more values (+) |
parser.add_argument("--tags", nargs="*", help="Zero or more tags") | Zero or more values (*) |
Result is a Python list you can iterate directly:
args = parser.parse_args()
for tag in args.tags:
print(tag)
Boolean Flags (store_true / store_false)
parser.add_argument(
"--dry-run",
action="store_true",
help="Simulate without writing"
)
parser.add_argument(
"--no-color",
action="store_false",
dest="color",
help="Disable color output"
)
Usage
python publish.py --dry-run # args.dry_run is True
python publish.py # args.dry_run is False
python publish.py --no-color # args.color is False
Subโcommands (like git, docker, pip)
parser = argparse.ArgumentParser(description="Publish queue manager")
subparsers = parser.add_subparsers(dest="command", required=True)
# `publish` subcommand
publish_parser = subparsers.add_parser(
"publish",
help="Publish the next article in queue"
)
publish_parser.add_argument(
"--dry-run",
action="store_true",
help="Simulate without publishing"
)
# `list` subcommand
list_parser = subparsers.add_parser(
"list",
help="Show the publish queue"
)
list_parser.add_argument(
"--format",
choices=["table", "json"],
default="table"
)
args.command tells you which subcommand was chosen, and each subcommand has its own arguments.
The --verbose / -v Pattern
A common pattern is using --verbose to set the logging level at runtime:
import argparse
import logging
parser = argparse.ArgumentParser()
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Enable debug logging"
)
args = parser.parse_args()
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format="%(levelname)s: %(message)s"
)
log = logging.getLogger(__name__)
log.info("Starting...")
log.debug("This only shows with --verbose")
Full Example: Article Publish Queue CLI
#!/usr/bin/env python3
"""
publish_queue.py โ CLI for managing the article publish queue.
Usage: python publish_queue.py [options]
"""
import argparse
import json
import logging
import sys
from pathlib import Path
QUEUE_FILE = Path("queue.json")
def load_queue() -> list[dict]:
"""Load the queue from JSON; return empty list if file missing."""
if not QUEUE_FILE.exists():
return []
return json.loads(QUEUE_FILE.read_text())
def save_queue(queue: list[dict]) -> None:
"""Write the queue back to disk."""
QUEUE_FILE.write_text(json.dumps(queue, indent=2))
def cmd_list(args: argparse.Namespace) -> None:
queue = load_queue()
if not queue:
print("Queue is empty.")
return
for i, article in enumerate(queue, 1):
status = "[published]" if article.get("published") else "[pending] "
tags = ", ".join(article.get("tags", []))
print(f"{i}. {status} {article['title']} ({tags})")
def cmd_add(args: argparse.Namespace) -> None:
queue = load_queue()
article = {
"title": args.title,
"tags": args.tags or [],
"published": False,
}
queue.append(article)
save_queue(queue)
logging.info("Added: %s", args.title)
print(f"Added '{args.title}' to queue. Total: {len(queue)} articles.")
def cmd_publish(args: argparse.Namespace) -> None:
queue = load_queue()
pending = [a for a in queue if not a.get("published")]
if not pending:
print("No pending articles.")
return
next_article = pending[0]
if args.dry_run:
print(f"[DRY RUN] Would publish: {next_article['title']}")
return
next_article["published"] = True
save_queue(queue)
print(f"Published: {next_article['title']}")
logging.info("Published: %s", next_article["title"])
def main() -> None:
parser = argparse.ArgumentParser(
description="Publish queue manager"
)
subparsers = parser.add_subparsers(dest="command", required=True)
# add subcommand
add_parser = subparsers.add_parser(
"add",
help="Add a new article to the queue"
)
add_parser.add_argument(
"--title",
required=True,
help="Article title (required)"
)
add_parser.add_argument(
"--tags",
nargs="*",
help="Zero or more tags"
)
add_parser.set_defaults(func=cmd_add)
# list subcommand
list_parser = subparsers.add_parser(
"list",
help="Show the publish queue"
)
list_parser.set_defaults(func=cmd_list)
# publish subcommand
publish_parser = subparsers.add_parser(
"publish",
help="Publish the next pending article"
)
publish_parser.add_argument(
"--dry-run",
action="store_true",
help="Simulate without publishing"
)
publish_parser.set_defaults(func=cmd_publish)
# global verbose flag
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Enable debug logging"
)
args = parser.parse_args()
# configure logging *after* parsing arguments
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format="%(levelname)s: %(message)s"
)
# dispatch to the chosen subcommand
args.func(args)
if __name__ == "__main__":
main()
Run the script with:
python publish_queue.py add --title "My First Article" --tags python tutorial
python publish_queue.py list
python publish_queue.py publish --dry-run
python publish_queue.py publish -v
TL;DR
argparseturns a oneโliner script into a polished CLI.- Define positional and optional arguments, use type conversion, choices, and nargs for lists.
- Use
store_true/store_falsefor flags. - Leverage subโparsers for gitโstyle subโcommands.
- Add a
--verboseflag to toggle logging.
Give it a tryโyour scripts will be more robust, selfโdocumenting, and userโfriendly in minutes!
publish_queue.py โ ArgumentโParser based CLI
import argparse
import logging
# Example logging call used by the script
logging.info("Published: %s", next_article["title"])
def build_parser() -> argparse.ArgumentParser:
"""Create the topโlevel argument parser."""
parser = argparse.ArgumentParser(
prog="publish_queue",
description="Manage your article publish queue.",
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Enable debug logging",
)
subparsers = parser.add_subparsers(dest="command", required=True)
# โโ list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
list_parser = subparsers.add_parser("list", help="Show the publish queue")
list_parser.set_defaults(func=cmd_list)
# โโ add โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
add_parser = subparsers.add_parser("add", help="Add an article to the queue")
add_parser.add_argument("--title", required=True, help="Article title")
add_parser.add_argument("--tags", nargs="*", help="Tags for the article")
add_parser.set_defaults(func=cmd_add)
# โโ publish โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
publish_parser = subparsers.add_parser(
"publish", help="Publish the next pending article"
)
publish_parser.add_argument(
"--dry-run", action="store_true", help="Simulate without writing"
)
publish_parser.set_defaults(func=cmd_publish)
return parser
def main() -> None:
"""Entry point for the CLI."""
parser = build_parser()
args = parser.parse_args()
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format="%(levelname)s: %(message)s",
)
# Dispatch to the appropriate subโcommand function
args.func(args)
if __name__ == "__main__":
main()
--help Output
$ python publish_queue.py --help
usage: publish_queue [-h] [--verbose] {list,add,publish} ...
Manage your article publish queue.
positional arguments:
{list,add,publish}
list Show the publish queue
add Add an article to the queue
publish Publish the next pending article
options:
-h, --help show this help message and exit
--verbose, -v Enable debug logging
$ python publish_queue.py add --help
usage: publish_queue add [-h] --title TITLE [--tags [TAGS ...]]
options:
-h, --help show this help message and exit
--title TITLE Article title
--tags [TAGS ...] Tags for the article
Example Workflow
# Add articles to the queue
python publish_queue.py add --title "Python argparse guide" \
--tags python beginners tutorial
python publish_queue.py add --title "Automate your workflow" \
--tags python automation
# List the queue
python publish_queue.py list
# 1. [pending] Python argparse guide (python, beginners, tutorial)
# 2. [pending] Automate your workflow (python, automation)
# Publish the next article (dryโrun first)
python publish_queue.py publish --dry-run
# [DRY RUN] Would publish: Python argparse guide
python publish_queue.py publish
# Published: Python argparse guide
# Check the updated queue with debug logging
python publish_queue.py list --verbose
Argparse Pattern CheatโSheet
| Pattern | When to use it |
|---|---|
type=int / type=float | Any numeric input |
choices=[...] | Fixed set of valid values |
required=True | Mandatory optional args |
nargs="+" / nargs="*" | Lists of values |
action="store_true" | Boolean flags |
add_subparsers() | Multiโcommand tools |
set_defaults(func=โฆ) | Dispatch to subโcommand functions |
What argparse gives you automatically
-h/--helpโ generated fromhelp=strings- Type validation โ clear error messages, no tracebacks
- Default values โ documented in the help output
- Usage line โ autoโgenerated from the argument definitions
No thirdโparty libraries required. Everything works with the Python standard library.
Further Reading & Resources
- The publishโqueue CLI in the full pipeline โ germy5.gumroad.com/l/xhxkzz (pay what you want, minโฏ$9.99)
- Your First Automated Python Script That Validates and Runs Itself
- Python logging: Stop Using
print()in Your Automation Scripts - How to Schedule Python Scripts with Cron: A Beginnerโs Complete Guide