Remember the last time you had to build a command-line tool? If you’re like me, you probably started with argparse
or click
, wrote boilerplate code, and still ended up with something that felt clunky. That’s where typer comes in – it’s a game-changer that lets you build CLI apps with minimal code. Although there are several other options, typer stands out because it leverages Python’s type hints to do the heavy lifting. No more manual argument parsing! The following snippet shows how to use typer in its simplest form:
import typer app = typer.Typer() @app.command() def hello(name: str): typer.echo(f"Hello {name}!") if __name__ == "__main__": app()
And you will be able to execute it with just:
$ python hello.py Pedro Hello Pedro!
In this simple example, we were only defining positional arguments, but having optional arguments is as easy as setting default values in the function signature.
import typer app = typer.Typer() @app.command() def hello2( name: str, count: int = 1, favorite_color: str = None ): for _ in range(count): message = f"Hello {name}!" if favorite_color: message += f" I like {favorite_color} too!" typer.echo(message) if __name__ == "__main__": app()
$ python hello2.py --help Usage: hello2.py [OPTIONS] NAME ╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────── │ * name TEXT [default: None] [required] ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────── │ --count INTEGER [default: 1] │ --favorite-color TEXT [default: None] │ --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. ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
And you can also provide additional metadata, like preferred parameter names or help information by using the Annotated
type hint in combination with typer.Argument
for positional arguments and typer.Option
for optional arguments. And it also allows for some very useful options such as file path validation.
from pathlib import Path import typer from typing import Annotated app = typer.Typer() @app.command() def process( input_file: Annotated[ Path, typer.Argument( exists=True, file_okay=True, dir_okay=False, help="Input file to process" ) ], output_dir: Annotated[ Path, typer.Option( "--output", "-o", help="Output directory", ) ] = Path("output"), backup: Annotated[ bool, typer.Option( help="Create backup before processing" ) ] = False, ): """Process a file with progress tracking.""" # Ensure output directory exists output_dir.mkdir(parents=True, exist_ok=True) # Create backup if requested if backup: backup_file = output_dir / f"{input_file.name}.bak" typer.echo(f"Creating backup at {backup_file}") backup_file.write_bytes(input_file.read_bytes()) n = 0 with open(input_file) as f: for line in f: n+= len(line) with open(output_dir / "count.txt", "w") as f: f.write(f"{n}\n") typer.echo(f"Processing complete! N lines= {n}") if __name__ == "__main__": app()
Whether you’re building a simple utility or a complex CLI application, typer’s type-driven approach will save you time and result in better tools. Next time you need to build a CLI tool, give typer a try. Your users (and future self) will thank you!