Want to talk about space and Python at PyCon?
Join us for at Varda @ PyCon In-Space Happy Hour Friday, May 15, 2026, 7:30pm.
RSVP for venue details at https://luma.com/7kta2vei
Code samples from my PyCon US 2026 talk "How To Write a Great Command Line Application"
Good patterns to follow with argparse:
- Use a shebang with uv to specify your third party dependencies.
- Use a
main()function that takes a list of raw arguments and returns the exit code. - Use
main.__doc__to deduplicate your docstrings and your help text. - Call your main function under dunder main and pass its return value to
sys.exit(). - Catch and handle
KeyboardInterruptandBrokenPipeErrorsto make your program play nicely with others. - Use a
do()function that takes all of your argparse arguments using keyword-only arguments with**vars(), and yield each line or chunk of output. - Use logging for verbose debugging output.
Demonstrating all the patterns
#! /usr/bin/env -S uv run --script
#
# /// script
# requires-python = "==3.13"
# dependencies = ["requests"]
# ///
import logging
import os
import sys
from argparse import ArgumentParser, FileType
logger = logging.getLogger(__name__)
def do(*, infiles, upper: bool):
for infile in infiles:
for line in infile:
if upper:
yield line.upper()
else:
yield line
def main(raw_args: list[str]) -> int:
"""Repeats a line"""
parser = ArgumentParser(description=main.__doc__)
parser.add_argument("infiles", type=FileType("r"), default=[sys.stdin], nargs="*")
parser.add_argument("-v", "--verbosity", action="count", default=0)
parser.add_argument("-u", "--upper", action="store_true")
args = vars(parser.parse_args(raw_args))
verbosity = args.pop("verbosity")
level = max(logging.ERROR - verbosity * 10, logging.NOTSET)
logging.basicConfig(level=level, format="%(levelname)s %(message)s")
for output in do(**args):
print(output, end="", flush=True)
return 0
if __name__ == "__main__":
try:
sys.exit(main(sys.argv[1:]))
except KeyboardInterrupt:
sys.exit(1)
except BrokenPipeError:
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, sys.stdout.fileno())
sys.exit(0)Alternatives to FileType?
import sys
from argparse import ArgumentParser
from contextlib import nullcontext
from io import IOBase
from pathlib import Path
def open_file_or_io(file_path_or_io, *args, **kwargs):
"""Return the open() context manager for a file,
or a null context manager an existing file object."""
if isinstance(file_path_or_io, IOBase):
return nullcontext(file_path_or_io)
return open(file_path_or_io, *args, **kwargs)
def PathOrStreamType(stream):
"""Factory function to create a type that will
use a stream if the argument is "-", otherwise
cast the argument into a Path."""
def _path_or_stream(arg):
if arg == "-":
return stream
return Path(arg)
return _path_or_streamparser = ArgumentParser()
parser.add_argument(
"outfile",
type=PathOrStreamType(sys.stdout),
default=sys.stdout,
nargs="?"
)
args = parser.parse_args([])
args = parser.parse_args(["-"])
args = parser.parse_args(["foo.txt"])
with open_file_or_io(args.outfile, 'w') as fobj:
fobj.write("foo")