AI News Hub Logo

AI News Hub

Python argparse: Build CLI Tools in 10 Minutes

DEV Community
German Yamil

Python argparse: Build CLI Tools in 10 Minutes ๐ŸŽ Free: AI Publishing Checklist โ€” 7 steps in Python ยท Full pipeline: germy5.gumroad.com/l/xhxkzz (pay what you want, min $9.99) sys.argv[1] You've been there. You write a quick script, hardcode a filename, then immediately need to change it. So you reach for sys.argv: import sys filename = sys.argv[1] count = int(sys.argv[2]) This works โ€” until it doesn't. Run it without arguments and you get an IndexError. Pass a string where you expected an integer and it crashes. There's no help text, no validation, no defaults. Anyone else who picks up your script has to read the source code to know how to run it. argparse solves all of this. It's in the standard library, requires no installation, and turns your script into a proper CLI tool in minutes. Every argparse script starts with a parser: import argparse parser = argparse.ArgumentParser( description="My CLI tool โ€” does useful things." ) args = parser.parse_args() That one call to parse_args() handles everything: reading sys.argv, validating inputs, and printing help when the user passes --help. Positional arguments are 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") --flag and -f) Optional arguments use -- prefix and can have short aliases: parser.add_argument("--output", "-o", help="Output file path", default="output.txt") parser.add_argument("--verbose", "-v", help="Enable verbose logging", action="store_true") Instead of int(sys.argv[1]) wrapped in a try/except, let argparse handle it: 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 passes --count hello, argparse prints a clean error message and exits โ€” no stack trace, no confusion. nargs, and Lists Required Optional Arguments parser.add_argument("--title", required=True, help="Article title (required)") # One or more values: --tags python beginner tutorial parser.add_argument("--tags", nargs="+", help="One or more tags") # Zero or more values: --tags (empty is fine) parser.add_argument("--tags", nargs="*", help="Zero or more tags") The result is a Python list you can iterate directly: args = parser.parse_args() for tag in args.tags: print(tag) store_true and store_false Boolean flags don't take a value โ€” their presence or absence is the value: 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 Real CLI tools like git, docker, and pip use subcommands. add_subparsers() gives you the same structure. 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") Now args.command tells you which subcommand was chosen, and each subcommand has its own arguments. --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") Here's a working CLI for managing an article publish queue โ€” the same pattern used in the full pipeline. #!/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]: if not QUEUE_FILE.exists(): return [] return json.loads(QUEUE_FILE.read_text()) def save_queue(queue: list[dict]) -> None: 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] " print(f"{i}. {status} {article['title']} ({', '.join(article.get('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 build_parser() -> argparse.ArgumentParser: 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: parser = build_parser() args = parser.parse_args() logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, format="%(levelname)s: %(message)s", ) 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 # 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 next (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 updated queue with debug logging python publish_queue.py list --verbose 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 subcommand functions Every argparse-based script automatically has: --help / -h โ€” generated from your help= strings Type validation โ€” with clear error messages, no tracebacks Default values โ€” documented in the help output Usage line โ€” auto-generated from your argument definitions No third-party libraries. No pip install. Just the standard library. The publish queue CLI in the full pipeline uses argparse for --list, --add, and --publish: 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