DEV Community

Cover image for How to Easily Build Command Line Tools in Python?
Thuwarakesh Murallie
Thuwarakesh Murallie

Posted on • Edited on • Originally published at the-analytics.club

How to Easily Build Command Line Tools in Python?

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.

If you're not using Typer, you can use the standard module, argparser to create CLIs. But, Typer is so much easier than argparser.

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]
Enter fullscreen mode Exit fullscreen mode
import typer

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

if __name__ == "__main__":
   typer.run(main)
Enter fullscreen mode Exit fullscreen mode

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'.                                                                                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode
(env) $ python app.py
Hello world
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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                                                                                                               │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter fullscreen mode Exit fullscreen mode

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'.                                                                                          │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.


Did you like what you read? Consider subscribing to my email newsletter because I post more like this frequently.

Thanks for reading, friend! Say Hi to me on LinkedIn, Twitter, and Medium.

Top comments (0)