Elevate Your Python CLI Development with Typer
Written on
Chapter 1: Introduction to Typer
In the previous week, I introduced one of Python's built-in modules, Argparse, which simplifies the creation of command-line interface (CLI) tools. This module allows users to define both positional and optional arguments effortlessly. Additionally, it generates help documentation automatically, requiring only a brief description for each argument. If you missed that post, you can find it here.
While Argparse is user-friendly, it does come with certain limitations. Thus, in this article, I will introduce Typer, a third-party library that I consider one of the best options for developing Python CLI applications.
Drawbacks of Argparse
The Argparse module has some limitations, which I highlighted in the previous article:
- It has a built-in mechanism to determine if an input is an argument or an option, which can lead to unpredictable behavior when commands are incomplete.
- It can be challenging to implement more advanced features, such as complex validation of argument values.
These issues are effectively addressed by the widely recognized third-party library, Click. Typer, the main focus of this article, is built on Click's foundation, offering even greater ease of use. With a wealth of tutorials available on Click, I prefer to highlight this less popular but highly promising tool.
It is important to note that Typer builds upon Click, inheriting nearly all its benefits while simplifying usability.
1. Quick Start
To begin, we need to install the Typer library. As is typical, we can use pip:
pip install typer
It's worth noting that Click is Typer's sole dependency, and it will be installed alongside Typer.
Boilerplate Code
Let’s create a simple Python CLI program to send greeting messages, where the user inputs their name. The Python script, greeting.py, is as follows:
import typer
def main(name: str):
typer.echo(f"Hello {name}!")
if __name__ == "__main__":
typer.run(main)
Here’s a brief explanation of the code. First, we import the Typer library. Next, we define a main() function that serves as the entry point for the command-line application, taking a parameter called name. It’s recommended to use type hints to specify the argument type. Finally, we inform Typer to execute the main() function when the script runs.
This approach is simpler than Click and significantly easier than Argparse. Typer automatically infers options and flags from the parameters of the main() function.
Why Use echo()?
You may notice we utilize typer.echo() for output instead of the standard print() function. This feature is inherited from the Click library. The advantage of using echo() is that Click aims to provide consistent support across different environments, ensuring functionality even when configurations are flawed. Thus, using echo() in Typer is advisable for maintaining this robustness. However, we can still resort to using print() if the additional features of echo() aren't necessary.
Demonstration
If we run the script without any arguments, Typer will generate an error message indicating a missing argument:
$ python greeting.py
Of course, it also provides the help documentation:
$ python greeting.py --help
Now, let’s see it in action by passing my name to the program:
$ python greeting.py Chris
These few lines of code can serve as our boilerplate for a Typer-based Python CLI project.
2. Optional Arguments
In the boilerplate example, the name argument is required. To make an argument optional, we can easily assign a default value:
def main(name: str, verbose: bool=False):
typer.echo(f"Hello {name}!")
if verbose:
typer.echo("Program Finished.")
In this case, we’ve added a boolean parameter, verbose, to the main() function and provided it a default value. The help documentation will automatically include this flag, allowing us to control program behavior accordingly.
3. Nesting Commands
One of Click's most notable features is the ability to nest commands, and Typer not only supports this but also simplifies its implementation. Nesting commands allows us to create "sub-commands" within a CLI application. For instance, we might want our greeting.py program to include a farewell feature. With nested commands, we can easily define this separately from the "hello" command, resulting in cleaner code:
import typer
main = typer.Typer()
@main.command()
def hello(name: str, verbose: bool=False):
typer.echo(f"Hello {name}!")
if verbose:
typer.echo("Program Finished.")
@main.command()
def bye(name: str, verbose: bool=False):
typer.echo(f"Goodbye {name}!")
if verbose:
typer.echo("Thank you for using this program.")
if __name__ == "__main__":
main()
In this code, we instantiate a Typer object named main and define two commands, hello and bye, which are both registered accordingly. When we access the help documentation, it will clearly indicate the available commands.
To see the arguments for one of the sub-commands, we can run:
$ python greeting.py bye --help
This nesting command feature enables us to implement complex CLI options requirements with ease.
Summary
In this article, I introduced a third-party Python library for creating CLI programs. Typer significantly simplifies the code required to define CLI applications with options and flags. While it is intuitive and user-friendly, it also possesses the scalability needed to develop more complex applications.
If you find my articles beneficial, please consider supporting me and countless other writers by joining Medium Membership!
The first video titled "The Best way to build a Python command line tool - Python Typer Tutorial" provides an excellent overview of using Typer for CLI development.
The second video, "Click: the Command Line Interface Creation Kit for Python," offers insights into the Click library, which serves as a foundation for Typer.