In our previous lessons, we've built a solid foundation for creating reliable scripts by configuring them through files—config.ini for general settings and .env for secrets. This is excellent for settings that don't change often. Today, we'll add another layer of flexibility by exploring how to control your scripts dynamically each time you run them.
This lesson focuses on creating command-line interfaces (CLIs) for your Python scripts. You will learn how to use Python's built-in argparse module to define and handle command-line options. Specifically, we will cover how to specify essential information like input and output files, and how to implement a "dry-run" mode—a crucial safety feature for any automation that modifies files or systems. This skill is fundamental to transforming a simple script into a powerful and reusable automation tool.
Why Not Just Hardcode Paths?
You're already familiar with functions, which take arguments to produce results. A script with a command-line interface operates on a similar principle. Instead of changing the code every time you want to process a different file, you pass the file path as an argument when you run the script.
Consider a simple script that processes a monthly sales report. Without command-line arguments, you might have something like this:
# Hardcoded path - inflexible!
file_path = "C:/Users/You/Documents/Reports/sales_january.csv"
def process_report(path):
# ... logic to process the report ...
print(f"Processing {path}...")
process_report(file_path)
To process February's report, you'd have to edit the script. This is slow and error-prone. A command-line interface allows you to do this instead:
python process_report.py "C:/Users/You/Documents/Reports/sales_february.csv"
This makes the script a general-purpose tool, not a one-off piece of code. While Python's sys.argv can access these arguments, it's just a raw list of strings. You'd have to manually parse it, check for missing arguments, and convert types, which gets complicated quickly. The argparse module handles all of this for you.
The argparse Basics
Using argparse follows a simple, three-step pattern:
- Create a parser: You instantiate
argparse.ArgumentParser. - Add arguments: You use the
parser.add_argument()method to define each command-line option your script accepts. - Parse arguments: You call
parser.parse_args()to read the command line, process the arguments, and return them in an object.
Let's see the simplest possible example:
import argparse
# Step 1: Create a parser
parser = argparse.ArgumentParser(description="A simple example script.")
# Step 2: We haven't added any custom arguments yet!
# Step 3: Parse the arguments
args = parser.parse_args()
print("Script has run.")
If you save this as my_script.py and run it from your terminal, it doesn't do much. But argparse has already given you a professional feature for free. Try running it with the --help or -h flag:
python my_script.py --help
You'll get a nicely formatted help message, generated automatically. This is the power of argparse: it handles the tedious parts of creating a user-friendly CLI.
Defining Inputs: Positional Arguments
The most common type of argument is a positional argument. It's required, and its meaning is determined by its position on the command line. This is perfect for essential inputs, like a source file your script needs to operate on.
The official Python documentation provides an excellent tutorial on argparse. Let's start by reading about the basics and how to add positional arguments.
This reading from the official Python documentation will introduce the fundamental concepts of argparse and show you how to define the two main types of arguments.
Please read the introduction and the first two main sections of the tutorial. Start at the beginning of the document, reading through the initial example that introduces the basic structure and the free --help option. The section to focus on is called The basics. Continue to the next section, Introducing Positional arguments. This part is key. Pay close attention to how add_argument() is used to define a required input, how the help parameter adds descriptions, and how the type parameter ensures the input is in the correct format (like an integer or, as we'll see, a path).
As the tutorial shows, you define a positional argument by calling add_argument() with a name (e.g., "input_file"). argparse then ensures the user provides this argument and makes it available to your script.
Since you've already covered pathlib in a previous lesson, you can use it to make your script even more robust. By setting type=pathlib.Path, argparse will automatically convert the user-provided string into a Path object for you!
import argparse
import pathlib
parser = argparse.ArgumentParser(description="Process a file.")
# A positional argument that expects a Path object
parser.add_argument("input_file", type=pathlib.Path, help="The path to the input file.")
args = parser.parse_args()
# Now args.input_file is a pathlib.Path object!
if args.input_file.exists():
print(f"Success! The file '{args.input_file.name}' exists.")
else:
print(f"Error: The file at '{args.input_file}' was not found.")
Defining Outputs and Flags: Optional Arguments
Positional arguments are for required inputs. But what about optional settings? This is where optional arguments come in. They are identified by a prefix, typically - for a short version and -- for a longer, more descriptive name (e.g., -o, --output). They can appear in any order and are, as the name implies, optional.
This is perfect for specifying an output file or enabling special modes like a "dry run." An argument that doesn't take a value, but simply being present turns on a behavior, is called a flag. The dry-run mode is a classic example of a flag.
Let's return to the documentation to see how to create these.
This next reading will show you how to create optional arguments and flags, which are essential for adding flexibility and safety features like a dry-run mode.
Continue in the same document with the next two sections. Read the section Introducing Optional arguments. Focus on how optional arguments are defined with -- prefixes. The most important concept here is action="store_true". This is the exact mechanism you'll use to create flags like --dry-run or --verbose. Next, read the brief section on Short options. This shows how to provide both a short (-v) and long (--verbose) version of the same option, which is a standard convention for command-line tools.
Putting It All Together
Now we have all the pieces to build a script that fits our learning outcome: defining inputs, outputs, and a dry-run mode. Let's create a script that simulates processing a file.
import argparse
import pathlib
import time
# 1. Create the parser with a description
parser = argparse.ArgumentParser(
description="A script to process a data file and save the results."
)
# 2. Add arguments
# A required positional argument for the input file
parser.add_argument(
"input_file",
type=pathlib.Path,
help="Path to the input data file to be processed."
)
# An optional argument for the output file
parser.add_argument(
"-o", "--output_file",
type=pathlib.Path,
default="result.txt", # Provides a default value if not specified
help="Path to save the output file (default: result.txt)."
)
# A flag for enabling dry-run mode
parser.add_argument(
"--dry-run",
action="store_true", # This makes it a flag
help="Simulate the script run without creating/modifying files."
)
# 3. Parse the arguments from the command line
args = parser.parse_args()
# --- Main script logic ---
print("--- Automation Script Starting ---")
print(f"Input file: {args.input_file}")
print(f"Output file: {args.output_file}")
print(f"Dry run enabled: {args.dry_run}")
print("-" * 20)
# The core of the dry-run logic
if args.dry_run:
print("MODE: Dry Run")
print("ACTION: Would check for input file existence.")
print(f"ACTION: Would process data from '{args.input_file.name}'.")
print(f"ACTION: Would save results to '{args.output_file}'.")
else:
print("MODE: Live Run")
if not args.input_file.exists():
print(f"ERROR: Input file not found: '{args.input_file}'")
# In the next lesson, we'll learn to exit with an error code here.
else:
print(f"Processing '{args.input_file.name}'...")
time.sleep(2) # Simulate work
processed_data = f"This is the processed result from {args.input_file.name}."
print(f"Saving results to '{args.output_file}'...")
args.output_file.write_text(processed_data)
print("Save complete.")
print("--- Automation Script Finished ---")
Save this as process_data.py. Create a dummy file named data.csv in the same directory. Now, you can experiment with it from your terminal:
- Basic run (uses default output):
python process_data.py data.csv - Specify an output file:
python process_data.py data.csv --output_file report.txt - Run in dry-run mode:
python process_data.py data.csv -o special_report.txt --dry-run - See the help message:
python process_data.py --help
Notice how --dry-run prevents any files from being written. This is an incredibly important pattern for testing your automation scripts safely before letting them modify real data or systems.
Conclusion
In this lesson, you've learned how to give your scripts a professional and flexible command-line interface using argparse. This is a significant step towards building robust automation tools.
Here are the key takeaways:
argparseautomates the parsing of command-line arguments, including generating help messages and handling errors.- Positional arguments are used for required inputs, like a source file.
- Optional arguments (e.g.,
--output) provide flexibility for non-essential settings. - Flags, created with
action="store_true", are perfect for toggling behaviors like a--dry-runmode, which allows you to test your script's logic without performing any permanent actions.
Your scripts are now configurable at runtime, making them far more powerful. However, when an automation runs unsupervised (for example, as a scheduled task), we need a clear signal to know if it succeeded or failed. In our next lesson, we will learn how to make our scripts return meaningful exit codes to communicate their final status to the operating system and any other tools in our workflow.