Run Python script using Command-Line Interface (argparse)

Sharing is caring!

Last Updated on July 14, 2022 by Jay

Because we are smart and efficient (read: lazy) workers, we want to find flexible and practical ways to run our Python programs. Today, we will look at how to run Python scripts using the command-line interface with the argparse library. A command-line interface (CLI) allows us to execute programs by typing a command in Shell (or Command Prompt if you are using Windows). Instead of changing code in the .py file every time we run a script, we can type in different arguments on the command-line and pass those arguments into the script. So using the CLI is very flexible and convenient, also, kicking off programs from a black screen makes you cooler and like a real programmer 🙂

We will continue to build on top of the PDF split & merge tool we developed earlier by adding a command-line interface. We will also tweak the code a little bit. Instead of hard coding all the values in the program, we’ll define three variables: the source PDF file, the pages we want to extract, and the resulting final PDF. This setup will allow us to use the program on any PDF file.

Step 1. Download and import the required libraries

The PDF split & merge tool requires the PyPDF4 library, go ahead and install it if you haven’t already done so.

pip install PyPDF4

We’ll use a few other Python standard libraries, so no further installation is required.

import argparse
from PyPDF4 import PdfFileReader, PdfFileWriter
import os, sys

Step 2. Program structure

To run our Python script using a command-line interface, we need two functions in our Python program, main() and split_merge_pdf().

main() function takes care of getting input from us. split_merge_pdf() is responsible for splitting and merging the PDF file, which is the code we developed previously. Visually, the structure looks like the below.

CLI Split & Merge PDF tool program structure
CLI Split & Merge PDF tool program structure

Step 3. PDF split and merge function

Let’s start by converting our previous code into a function that takes three arguments: input file, output file, and pages. The function will read the input file, extract the desired pages, then save those pages into a new PDF file. Note how we are using type hinting here, both input_file and output_file will be string, and pages is a list.

def split_merge_pdf(input_file:str = None, output_file:str = None, pages:list = None):
    pdf = PdfFileReader(input_file)
    pdf_writer = PdfFileWriter()

    pages = [i-1 for i in pages]
    for p in pages:
        pdf_writer.addPage(pdf.getPage(p))

    with open(output_file, 'wb') as f:
        pdf_writer.write(f)

That was easy. Now on to the interesting part – we want to pass those three variables from the command-line when running the program.

Step 3. Parse arguments from the command-line

The simplest way to parse arguments from the command-line is to use sys.argv, which is the list of command-line arguments passed to a Python script. Let’s see how to use sys.argv by running the following simple script in command-line.

import sys

print(type(sys.argv))
print(len(sys.argv))
for i in sys.argv:
    print(i)

Save the above code into a .py file. Bring up Command Prompt (click on Search bar in Windows, then type “CMD”, enter), go to the folder you saved the .py file at. I saved my file as “sys_argv.py”, but you can name it anything. If you are not familiar with Command Prompt commands, use cd to go to a specific folder. And use ls to see the list of files stored in that folder.

A quick command prompt tutorial
A quick command prompt tutorial

Once we are in the right folder (when you see the “sys_argv.py” file). Type the following in the Command Prompt: sys_argv.py hello world 1,2,3. This will execute the sys_argv.py and pass whatever you type after “sys_argv.py” into the program as arguments. In our little program, we are printing: the data type of sys.argv, the length of it, and the elements in it. We can see that:

  1. sys.argv is a list
  2. there are 4 elements in the list, each element is a string
  3. first element is the script name (the file path), followed by the three other things I typed in
  4. it appears space ‘ ‘ is the deliminator, as it treats “1,2,3” as one item
Output from sys.argv
Output from sys.argv

Now we understand the basics of passing command-line inputs into Python scripts. Note that it’s totally fine to just use sys.argv for our CLI, but let’s move to something slightly more advanced – the argparse library. You will see why it’s a little better.

Step 4. Intro to the argparse library

argparse is a standard Python library, so you don’t need to install it. We can define what arguments the program requires, and argparse will figure out how to parse those out of the sys.argv. The library also automatically generates help and usage messages, which is the reason I prefer argparse over sys.argv due to the added functionalities.

First, we instantiate an ArgumentParser() object.

Then, we add arguments to the parser by using .add_argument(). In the following example:

  • '--input' is the name of the argument.
  • type=file_path is the type to which the command-line argument should be converted. We can customize and convert the argument to anything, we’ll touch on this soon. A reminder – the raw parsed argument will always be a string.
  • help is a short description of what the argument does.

Remember that we have three arguments from the split_merge_pdf() function? We can add all of them to our parser object.

Finally, we assign all the parsed arguments into a variable called args. You can access each argument by calling args.input, args.output, and args.pages.

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--input', type=file_path, default = None,
                        help='What is the path of the input PDF?')

    parser.add_argument('--output', type=file_path, default = None,
                        help='What is the path of the output PDF?')

    parser.add_argument('--pages', type=pages, default = None,
                        help='Which pages do you want to extract?')
    args = parser.parse_args()

Now let’s talk about the type=file_path and type=pages. We know that the parsed argument will always be a string.

For a file path on our (Windows) computer, Python doesn’t like unprocessed file path due to the backslash /. And for pages, a list of integers [1,2,3] makes more sense than a string of "1,2,3". So we have to handle the format of those arguments.

Basically for the file_path, we are converting it into a raw-string so Python can understand it. For pages, we convert the “1,2,3” string into a list of integers.

def file_path(path):
    return rf'{path}'
    
def pages(pages):
    return [int(i) for i in pages.split(',')]

Putting it together

We have all the pieces we need, now it’s the time to piece them together. Below is the full code, and I saved it as “pdf_cli_eg.py”. Feel free to save your code as any name, just make you type in the correct name when you execute the program on command-line.

import argparse
from PyPDF4 import PdfFileReader, PdfFileWriter
import os, sys

def split_merge_pdf(input_file:str = None, output_file:str = None, pages:list = None):
    pdf = PdfFileReader(input_file)
    pdf_writer = PdfFileWriter()

    pages = [i-1 for i in pages]
    for p in pages:
        pdf_writer.addPage(pdf.getPage(p))

    with open(output_file, 'wb') as f:
        pdf_writer.write(f)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--input', type=file_path, default = None,
                        help='What is the path of the input PDF?')

    parser.add_argument('--output', type=file_path, default = None,
                        help='What is the path of the output PDF?')

    parser.add_argument('--pages', type=pages, default = None,
                        help='Which pages do you want to extract?')
    args = parser.parse_args()
    
    split_merge_pdf(args.input, args.output, args.pages)
    print('finished saving PDF')

def file_path(path):
    return rf'{path}'
    
def pages(pages):
    return [int(i) for i in pages.split(',')]

if __name__ == '__main__':
    main()

I’ve added a if __name__ == '__main__' code block at the end. __name__ is a special variable in Python. When the source file is executed as the main program (i.e. you run the script from the IDLE or command-line). __name__ will be set to a string '__main__'. Technically, we don’t need to have this if block, we can just call main() and the code will still work. But it’s a good practice to include it in your code, we’ll talk about why in another article.

Run our Python script using the command-line interface

Let’s test this program in action. Bring out the Command Prompt. You can use any PDF file to test. My testing file name is “data.pdf”. Type in pdf_cli_eg.py --input=data.pdf --output=3_pages.pdf --pages=1,2,3. You should see an output similar to the below, and there should be an additional PDF file called “3_pages.pdf” in the same folder after your program runs.

Testing Python command line interface code
Testing Python command line interface code

One of the advantages of argparse is its automatically generated help messages. In the command line, type pdf_cli_eg.py -h. This will bring out the help messages, including how to use the program’s CLI, and descriptions of the arguments.

Python command line interface help
Python command line interface help

Leave a Reply

Your email address will not be published. Required fields are marked *