Developing Python CLIs with Typer
Société Générale - Africa Technologies & Services
CLIs (Command Line Interfaces) are one of the most common categories of software. As opposed to GUIs (Graphical User Interfaces), they do not expose visual means of interacting with the program, they can only be interacted with through a terminal. Due to this, CLIs are generally considered harder to use, but they have the advantage of being easily usable for automation purposes. You can easily call CLIs from within scripts or programs.
CLIs can be written in many programming languages. In this article, we will focus on writing CLIs in Python. In particular, we will focus on using the Typer library.
Typer Introduction
Typer is a Python library dedicated to building intuitive, powerful and fun to code CLIs. It relies on Python's type hinting system (a.k.a. annotations) to avoid a lot of boilerplate code generally required to develop CLIs.
To install typer, use the following command in your Python virtual environment (If you are unfamiliar with virtual environments, I recommend checking out conda ):
pip install typer[all]
We install typer[all] instead of just typer to get all of Typer's features such as rich (colorful) text.
Now, let's write a basic CLI that generates a random number:
# file: typer_example.py
import typer
import random
def main(start: int, end: int):
"""Generate a random integer between start and end"""
print(random.randint(start, end))
if __name__ == '__main__':
typer.run(main)
Let's try calling this script without any arguments:
Note: For now, we will call the CLI using the command python typer_example.py. We will see below how we can define our own command name.
As you can see, Typer handles the argument checks and raises an error describing the problem. We didn't have to write any additional code to handle this.
As suggested by the CLI, let's try the --help option:
Once again, Typer automatically generates the help menu. The CLI's description was deduced from the main function's docstring. And the arguments, as well as their type, were deduced from the main function's parameters.
One last call. This time, we will give all the required arguments:
CLI Project: Random Number Generator
In this section, we will build a simple Python project: A CLI that exposes some randomness functionality. This CLI will be exposed through the command rng.
Our CLI will have two sub-commands:
Project Structure:
This will be the structure of our project:
random-number-generator/
├── src/
│ └── rng/
│ ├── __init__.py
│ └── main.py
├── pyproject.toml
└── README.md
In the main.py file, we will move the content of our introductory example while making a few small adjustments:
# file: src/rng/main.py
import typer
import random
app = typer.Typer()
@app.command()
def main(start: int, end: int):
"""Generate a random integer between start and end"""
print(random.randint(start, end))
In contrast to the introductory example, we have now built a Typer app object. This will allow us to do two things:
In the pyproject.toml file, we will find the description of our project:
# file: pyproject.toml
[project]
name = "rng"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
"typer[all]"
]
[project.scripts]
rng = "rng.main:app"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/rng"]
Thanks to the pyproject.toml project format, we can easily install our project in development mode:
pip install -e .
This installs the project in "editable" mode, which means changes made to the project will be reflected automatically in the development environment.
领英推荐
Once the project is installed, we can call our CLI using the rng command directly:
Compared to our first example, two new options have been added:
These options relate to autocompletion but they do not work on all types of shells, so we will not cover them. You can read more about autocompletion here .
We can deactivate these options in our Typer app:
# file: src/rng/main.py
# ...
app = typer.Typer(add_completion=False)
# ...
Typer Subcommands
Now that we have the project and the CLI setup, we can start extending it!
We already have a command that generates a random number. Let's add a command that makes a random choice:
# file src/rng/main.py
import typer
import random
app = typer.Typer(add_completion=False)
@app.command()
def number(start: int, end: int):
"""Generate a random integer between start and end"""
print(random.randint(start, end))
@app.command()
def choice(values: list[str]):
"""Make a random choice from an arbitrary list of value"""
print(random.choice(values))
We added a new command called choice which takes a list of options and prints a random one.
We also renamed the main command to number. Renaming the function is not necessary, but it informs Typer of the name we want to give our sub-command.
Let's see what the help menu shows us now:
As you can see, Typer automatically detected the second command and is now showing both number and choice as sub-command of our rng command.
Let's see what the new choice command shows:
Typer indicates that our command accepts an arbitrary number of arguments: VALUES...
Now let's try the command!
One last thing to do: add the --count option to our number command!
# file: src/rng/main.py
import typer
import random
app = typer.Typer(add_completion=False)
@app.command()
def number(start: int, end: int, count: int = 1):
"""Generate a random integer between start and end"""
for _ in range(count):
print(random.randint(start, end))
@app.command()
def choice(values: list[str]):
"""Make a random choice from an arbitrary list of value"""
print(random.choice(values))
We've added an optional argument count and used it to loop for the number generator.
Let's see what the help menu shows now:
Seeing that the count argument is optional, Typer has automatically generated an equivalent option --count in our CLI.
Let's try using this option:
Works as intended!
Conclusion
As we saw, Typer allows us to create CLIs quickly and intuitively. It is built with developer experience in mind.
This was a simple tutorial to emphasize the ease of working with Typer, but it can do much more than what was shown here. You can find out more about Typer in the official Typer documentation .
If you've enjoyed how the type hinting system of Python was cleverly used to ease the developer experience, you may also be interested in: