How to Easily Build Command Line Tools in Python?

July 24, 2022

It's so much easy to develop a command line tool than a graphical UI. But it's easier in Python now than it was before.

Typer is a handy tool that helps convert our ordinary functions into command line utilities.

A common need in most CLIs is to accept arguments from the user. Typer makes it super easy to do this. Further, Typer also has the flexibility to style your output.

All this happens in two magical lines of code!

Here's a simple python function that accepts a name and says hello. Two lines of Typer code have changed it to a CLI.

Create your first command line tool in Python.

Before you begin with the rest of the guide, please install typer on your local computer or your virtual environment. The following code will help.

pip install typer[all]
import typer

def main(name: str):
   print(f"Hello {name}")

if __name__ == "__main__":
   typer.run(main)

Say you've named your Python script app.py. If you run the python script on your terminal with an argument, you'd see it printing hello to your input value.

(env) $ python app.py thuwa
Hello thuwa

(env) $ python app.py
Usage: app.py [OPTIONS] NAME
Try 'app.py --help' for help.
╭─ Error ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Missing argument 'NAME'.                                                                                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

In the above example, you could also see it failing when you try to run the script without an argument. But try adjusting your code and give the name parameter a default value. This makes the command line argument 'name' an optional one.

def main(name: str = "world"):
    print(f"Hello {name}")
(env) $ python app.py
Hello world

Create multiple command line functions in the same Python script.

Often you'd want to have more than one function on your Python script. In such cases, you can choose which parts appear as command line utilities.

For instance, the following script has three Python functions. One is to say hi, and the other is to say bye. We also have a third function to tell 'Run Away.'

We want to convert the first two into command line functions and leave the last one.

The following script does it.

import typer

app = typer.Typer()


@app.command()
def say_hi(name: str):
    print(f"Hello {name}")


@app.command()
def say_bye(name: str):
    print(f"Bye {name}!")


if __name__ == "__main__":
    app()

This script may look a bit more complicated than the previous simple example. But it is not.

In this example, we create a Typer app and manually add commands to it. We add commands by annotating functions with `@app.command`.

In the main app, we called our app itself.

Now run the script with --help at the end and see the beautiful help document printing on the screen.

(env) $ python app.py --help
                                                                                                                        
 Usage: app.py [OPTIONS] COMMAND [ARGS]...                                                                              
                                                                                                                        
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --install-completion          Install completion for the current shell.                                              │
│ --show-completion             Show completion for the current shell, to copy it or customize the installation.       │
│ --help                        Show this message and exit.                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ say-bye                                                                                                              │
│ say-hi                                                                                                               │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

In the help menu, you can see the two functions we've annotated with `@app.command`. We can use these with arguments in our command line.

We can also see that the runaway function is not a CLI, although it's there in the script. If we try to run this command, it'll fail as shown below.

env) $ python app.py say-hi thuwa
Hello thuwa
(env) $ python app.py say-bye thuwa
Bye thuwa!
(env) $ python app.py run-away thuwa
Usage: app.py [OPTIONS] COMMAND [ARGS]...
Try 'app.py --help' for help.
╭─ Error ──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ No such command 'run-away'.                                                                                          │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Running shell commands in your CLI.

I've wanted to write a find and replace command. It should go inside every file in the current directory and replace values.

You can do it in Linux using the following command.

find ./ -type f -exec sed -i -e 's/<FIND VALUE>/<REPLACE VALUE>/g' {} \;

Yet, the above function is hard to memorize and reuse. Let's create a convenient Python wrapper around it.

We can run this shell command inside Python.

import typer
import subprocess


def replace(old: str, new: str):
    """Find and replace 'old' with 'new' in every file in the current directory"""

    command = [
        "find",
        "./",
        "-type",
        "f",
        "-exec",
        "sed",
        "-i",
        f"s/{old}/{new}/g",
        "{}",
        ";",
    ]

    subprocess.call(command)


if __name__ == "__main__":
    typer.run(replace)

This may not be a very Pythonic way to find and replace values in a folder. But it explains well how to run shell commands inside your CLI wrapper.

How we work

Readers support The Analytics Club. We earn through display ads. Also, when you buy something we recommend, we may get an affiliate commission. But it never affects your price or what we pick.

Connect with us