Tools: Python argparse: Build CLI Tools in 10 Minutes (2026)

Tools: Python argparse: Build CLI Tools in 10 Minutes (2026)

Python argparse: Build CLI Tools in 10 Minutes

The Problem with sys.argv[1]

The Basics: ArgumentParser

Positional Arguments

Optional Arguments (--flag and -f)

Type Validation: No More Manual Casting

Required Arguments, nargs, and Lists

Required Optional Arguments

Accepting Multiple Values

Boolean Flags: store_true and store_false

Subcommands: One Tool, Many Commands

The --verbose / -v Pattern

Complete Example: Publish Queue CLI

--help Output

Running It

Key Patterns to Remember

What You Get for Free

Further Reading ๐ŸŽ Free: AI Publishing Checklist โ€” 7 steps in Python ยท Full pipeline: germy5.gumroad.com/l/xhxkzz (pay what you want, min $9.99) You've been there. You write a quick script, hardcode a filename, then immediately need to change it. So you reach for sys.argv: 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: 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: Optional arguments use -- prefix and can have short aliases: Instead of int(sys.argv[1]) wrapped in a try/except, let argparse handle it: If a user passes --count hello, argparse prints a clean error message and exits โ€” no stack trace, no confusion. The result is a Python list you can iterate directly: Boolean flags don't take a value โ€” their presence or absence is the value: Real CLI tools like git, docker, and pip use subcommands. add_subparsers() gives you the same structure. Now args.command tells you which subcommand was chosen, and each subcommand has its own arguments. A common pattern is using --verbose to set the logging level at runtime: Here's a working CLI for managing an article publish queue โ€” the same pattern used in the full pipeline. Every argparse-based script automatically has: 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. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

import sys filename = sys.argv[1] count = int(sys.argv[2]) import sys filename = sys.argv[1] count = int(sys.argv[2]) import sys filename = sys.argv[1] count = int(sys.argv[2]) import argparse parser = argparse.ArgumentParser( description="My CLI tool โ€” does useful things." ) args = parser.parse_args() import argparse parser = argparse.ArgumentParser( description="My CLI tool โ€” does useful things." ) args = parser.parse_args() import argparse parser = argparse.ArgumentParser( description="My CLI tool โ€” does useful things." ) args = parser.parse_args() parser.add_argument("filename", help="Path to the input file") parser.add_argument("count", help="Number of items to process") parser.add_argument("filename", help="Path to the input file") parser.add_argument("count", help="Number of items to process") parser.add_argument("filename", help="Path to the input file") parser.add_argument("count", help="Number of items to process") parser.add_argument("--output", "-o", help="Output file path", default="output.txt") parser.add_argument("--verbose", "-v", help="Enable verbose logging", action="store_true") parser.add_argument("--output", "-o", help="Output file path", default="output.txt") parser.add_argument("--verbose", "-v", help="Enable verbose logging", action="store_true") parser.add_argument("--output", "-o", help="Output file path", default="output.txt") parser.add_argument("--verbose", "-v", help="Enable verbose logging", action="store_true") 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" ) 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" ) 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" ) parser.add_argument("--title", required=True, help="Article title (required)") parser.add_argument("--title", required=True, help="Article title (required)") 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") # 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") # 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") args = parser.parse_args() for tag in args.tags: print(tag) args = parser.parse_args() for tag in args.tags: print(tag) args = parser.parse_args() for tag in args.tags: print(tag) 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") 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") 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") 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 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 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 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") 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") 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") 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") 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") 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") #!/usr/bin/env python3 """ publish_queue.py โ€” CLI for managing the article publish queue. Usage: python publish_queue.py <command> [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() #!/usr/bin/env python3 """ publish_queue.py โ€” CLI for managing the article publish queue. Usage: python publish_queue.py <command> [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() #!/usr/bin/env python3 """ publish_queue.py โ€” CLI for managing the article publish queue. Usage: python publish_queue.py <command> [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() $ 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 $ 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 $ 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 # 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 # 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 - --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 - 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