Week 4. You have a collection of analysis functions. Now you're building the tool that wraps them — a real CLI program the ops team can run without editing Python files. Right now, how do they run your scripts?
They call me or Ahmad, we edit the hardcoded parameters at the top of the script, save the file, run it. It works, but Diane has started asking about running it on different date ranges and I can't be available every time.
argparse turns your script into a control panel. Each argument is a labeled switch with a type, a default, and help text. The module validates input, generates usage messages, and hands your script clean Python values. Here's the basic setup:
import argparse
parser = argparse.ArgumentParser(
description="Analyze server log files and generate reports."
)
parser.add_argument("log_file", help="Path to the log file")
parser.add_argument("--service", help="Filter by service name")
parser.add_argument("--level", default="ERROR",
help="Minimum log level (default: ERROR)")
parser.add_argument("--hours", type=int, default=24,
help="Look back N hours (default: 24)")
parser.add_argument("--output", help="Output file path (default: stdout)")
parser.add_argument("--verbose", action="store_true",
help="Enable verbose output")
args = parser.parse_args()
print(args.log_file, args.service, args.level, args.hours, args.verbose)type=int — argparse converts "24" from the command line to the integer 24 for me? And if someone passes --hours abc, argparse rejects it with an error message automatically? I've been doing int(sys.argv[2]) and getting ValueError crashes with no explanation.
Type conversion, validation, and error messages — all automatic. action="store_true" makes --verbose a boolean flag: present means True, absent means False. No value needed after the flag. And there's a choice validator when you want to restrict to specific values:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--format",
choices=["json", "csv", "text"],
default="text",
help="Output format"
)
parser.add_argument(
"--level",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="ERROR"
)choices — argparse validates the value against the list and prints an error if it's not one of the options. No manual if args.level not in VALID_LEVELS: sys.exit(1) needed.
Built in. And the --help flag is automatic — no code required:
$ python3 analyze.py --help
usage: analyze.py [-h] [--service SERVICE] [--level LEVEL] [--hours HOURS] log_file
Analyze server log files and generate reports.
positional arguments:
log_file Path to the log file
options:
-h, --help show this help message and exit
--service SERVICE Filter by service name
--level LEVEL Minimum log level (default: ERROR)
--hours HOURS Look back N hours (default: 24)The ops team can run python3 analyze.py --help and see the entire interface without calling me. That's a real tool. Diane can add this to the cron job with the right arguments and never need to edit the script.
That's the transition from "Maya's script" to "the team's tool." And today's problem simulates parse_args() without running a real script — you build the parser and call parser.parse_args(argv_list) with a list of strings instead of reading sys.argv. This makes it testable:
import argparse
def make_parser():
parser = argparse.ArgumentParser()
parser.add_argument("log_file")
parser.add_argument("--level", default="ERROR",
choices=["DEBUG","INFO","WARNING","ERROR","CRITICAL"])
parser.add_argument("--hours", type=int, default=24)
parser.add_argument("--verbose", action="store_true")
return parser
# In your tests:
parser = make_parser()
args = parser.parse_args(["auth.log", "--level", "WARNING", "--hours", "72"])
print(args.level) # WARNING
print(args.hours) # 72 (int, not string)
print(args.verbose) # Falseparse_args(["auth.log", "--level", "WARNING", "--hours", "72"]) — I pass in a list of strings, get back a namespace with typed values. I can unit test the argument parsing logic without running the script from the command line.
Make the parser in a function, pass it lists in tests, pass sys.argv in production. Same parser, two call sites. Tomorrow: logging — replacing all your print() statements with a proper logging framework that supports levels, multiple destinations, and timestamps without manual string formatting.
argparse is the standard library's command-line argument parsing module, and it embodies a philosophy: the interface between a script and its caller should be as explicit and documented as any other API. A script that accepts sys.argv without parsing is an undocumented interface. A script that uses argparse has a usage message, typed arguments, validated choices, and sensible defaults — all generated from the same declarations.
Positional arguments are required and order-dependent: parser.add_argument("log_file") declares a required positional. Optional arguments use --name flags: parser.add_argument("--hours"). Short forms are conventional: parser.add_argument("-v", "--verbose"). The distinction is syntactic — the parser knows whether an argument is positional or optional from whether the name starts with -.
The type parameter is any callable that accepts a string and returns the converted value: type=int, type=float, type=pathlib.Path, or a custom function. If conversion fails, argparse prints a descriptive error and exits with code 2. choices validates against an explicit list. nargs controls how many values the argument consumes: nargs="+" requires one or more, nargs="*" accepts zero or more, nargs=2 requires exactly two.
action="store_true" stores True when the flag is present, False when absent. action="store_false" is the inverse. action="append" collects multiple uses of a flag into a list: --service auth --service api becomes ["auth", "api"]. action="count" counts repetitions: -vvv becomes 3. These cover the common patterns without custom code.
parser.parse_args() reads from sys.argv[1:] by default. parser.parse_args(["--hours", "48"]) reads from the provided list instead. This makes argparse parsers fully testable without subprocess or monkeypatching. The canonical pattern: define the parser in a make_parser() function, call make_parser().parse_args() in main(), and test by calling make_parser().parse_args(["..."]) in tests.
By default, argparse exits with code 2 on error — appropriate for a CLI tool where argument errors are user errors. For library code that wraps argument parsing, subclass ArgumentParser and override error() to raise an exception instead of calling sys.exit(2). This is necessary when you want argparse's validation logic inside a function that is called programmatically.
Sign up to write and run code in this lesson.
Week 4. You have a collection of analysis functions. Now you're building the tool that wraps them — a real CLI program the ops team can run without editing Python files. Right now, how do they run your scripts?
They call me or Ahmad, we edit the hardcoded parameters at the top of the script, save the file, run it. It works, but Diane has started asking about running it on different date ranges and I can't be available every time.
argparse turns your script into a control panel. Each argument is a labeled switch with a type, a default, and help text. The module validates input, generates usage messages, and hands your script clean Python values. Here's the basic setup:
import argparse
parser = argparse.ArgumentParser(
description="Analyze server log files and generate reports."
)
parser.add_argument("log_file", help="Path to the log file")
parser.add_argument("--service", help="Filter by service name")
parser.add_argument("--level", default="ERROR",
help="Minimum log level (default: ERROR)")
parser.add_argument("--hours", type=int, default=24,
help="Look back N hours (default: 24)")
parser.add_argument("--output", help="Output file path (default: stdout)")
parser.add_argument("--verbose", action="store_true",
help="Enable verbose output")
args = parser.parse_args()
print(args.log_file, args.service, args.level, args.hours, args.verbose)type=int — argparse converts "24" from the command line to the integer 24 for me? And if someone passes --hours abc, argparse rejects it with an error message automatically? I've been doing int(sys.argv[2]) and getting ValueError crashes with no explanation.
Type conversion, validation, and error messages — all automatic. action="store_true" makes --verbose a boolean flag: present means True, absent means False. No value needed after the flag. And there's a choice validator when you want to restrict to specific values:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--format",
choices=["json", "csv", "text"],
default="text",
help="Output format"
)
parser.add_argument(
"--level",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="ERROR"
)choices — argparse validates the value against the list and prints an error if it's not one of the options. No manual if args.level not in VALID_LEVELS: sys.exit(1) needed.
Built in. And the --help flag is automatic — no code required:
$ python3 analyze.py --help
usage: analyze.py [-h] [--service SERVICE] [--level LEVEL] [--hours HOURS] log_file
Analyze server log files and generate reports.
positional arguments:
log_file Path to the log file
options:
-h, --help show this help message and exit
--service SERVICE Filter by service name
--level LEVEL Minimum log level (default: ERROR)
--hours HOURS Look back N hours (default: 24)The ops team can run python3 analyze.py --help and see the entire interface without calling me. That's a real tool. Diane can add this to the cron job with the right arguments and never need to edit the script.
That's the transition from "Maya's script" to "the team's tool." And today's problem simulates parse_args() without running a real script — you build the parser and call parser.parse_args(argv_list) with a list of strings instead of reading sys.argv. This makes it testable:
import argparse
def make_parser():
parser = argparse.ArgumentParser()
parser.add_argument("log_file")
parser.add_argument("--level", default="ERROR",
choices=["DEBUG","INFO","WARNING","ERROR","CRITICAL"])
parser.add_argument("--hours", type=int, default=24)
parser.add_argument("--verbose", action="store_true")
return parser
# In your tests:
parser = make_parser()
args = parser.parse_args(["auth.log", "--level", "WARNING", "--hours", "72"])
print(args.level) # WARNING
print(args.hours) # 72 (int, not string)
print(args.verbose) # Falseparse_args(["auth.log", "--level", "WARNING", "--hours", "72"]) — I pass in a list of strings, get back a namespace with typed values. I can unit test the argument parsing logic without running the script from the command line.
Make the parser in a function, pass it lists in tests, pass sys.argv in production. Same parser, two call sites. Tomorrow: logging — replacing all your print() statements with a proper logging framework that supports levels, multiple destinations, and timestamps without manual string formatting.
argparse is the standard library's command-line argument parsing module, and it embodies a philosophy: the interface between a script and its caller should be as explicit and documented as any other API. A script that accepts sys.argv without parsing is an undocumented interface. A script that uses argparse has a usage message, typed arguments, validated choices, and sensible defaults — all generated from the same declarations.
Positional arguments are required and order-dependent: parser.add_argument("log_file") declares a required positional. Optional arguments use --name flags: parser.add_argument("--hours"). Short forms are conventional: parser.add_argument("-v", "--verbose"). The distinction is syntactic — the parser knows whether an argument is positional or optional from whether the name starts with -.
The type parameter is any callable that accepts a string and returns the converted value: type=int, type=float, type=pathlib.Path, or a custom function. If conversion fails, argparse prints a descriptive error and exits with code 2. choices validates against an explicit list. nargs controls how many values the argument consumes: nargs="+" requires one or more, nargs="*" accepts zero or more, nargs=2 requires exactly two.
action="store_true" stores True when the flag is present, False when absent. action="store_false" is the inverse. action="append" collects multiple uses of a flag into a list: --service auth --service api becomes ["auth", "api"]. action="count" counts repetitions: -vvv becomes 3. These cover the common patterns without custom code.
parser.parse_args() reads from sys.argv[1:] by default. parser.parse_args(["--hours", "48"]) reads from the provided list instead. This makes argparse parsers fully testable without subprocess or monkeypatching. The canonical pattern: define the parser in a make_parser() function, call make_parser().parse_args() in main(), and test by calling make_parser().parse_args(["..."]) in tests.
By default, argparse exits with code 2 on error — appropriate for a CLI tool where argument errors are user errors. For library code that wraps argument parsing, subclass ArgumentParser and override error() to raise an exception instead of calling sys.exit(2). This is necessary when you want argparse's validation logic inside a function that is called programmatically.