Sinvaldo [Profile]

Chief Operating Officer at Square Cloud

Article written by Sinvaldo in 02/05/2024.

Introduction

In this article, we’ll explore the Typer library, a modern tool for creating command-line interfaces (CLI) in Python. We’ll learn how to use Typer to create simple and effective CLIs. Additionally, we’ll discuss the advantages and reasons for using Typer in your projects.

What is Typer?

Typer is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. It’s built on top of Starlette for web routing and Pydantic for data validation and settings management.

Installation

To install Typer, you can use pip:

pip install typer

Creating a Simple Command Line Interface (CLI)

Setting Up Your CLI

Let’s create a simple CLI that greets the user. For this, we’ll use the typer.Typer() function and the @app.command() decorator.

main.py
# We import the Typer library
import typer

# We create an instance of Typer
app = typer.Typer()

# We define a command called 'hello' using the @app.command() decorator
@app.command()
def hello(name: str):  # The function takes one argument 'name' of type string
    # The function typer.echo() is used to print the greeting to the screen
    typer.echo(f"Hello {name}")  # The greeting is formatted to include the provided name

# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call app() to start the CLI
    app()

You can run this script with the command python script.py hello --name YourName. This command will display the greeting “Hello, YourName!”.

Adding More Commands

You can add more commands to your CLI by simply adding more functions decorated with @app.command(). For example:

main.py
# Import the requests and Typer libraries
import requests
import typer

# Create an instance of Typer
app = typer.Typer()

# Define a command named 'get_github_user' using the @app.command() decorator
@app.command()
def get_github_user(username: str):
    # Send a GET request to the GitHub API to get information about the user
    response = requests.get(f"https://api.github.com/users/{username}")
    # Parse the JSON response
    data = response.json()

    # If the response was successful, print the user's information
    if response.status_code == 200:
        typer.echo(f"Username: {data['login']}")
        typer.echo(f"Name: {data['name']}")
        typer.echo(f"Location: {data['location']}")
        typer.echo(f"Public repos: {data['public_repos']}")
    # If the response was not successful, print the error message
    else:
        typer.echo(f"Error: {data['message']}")

# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call app() to start the CLI
    app()

Now you can get information about a GitHub user with the command python script.py get-github-user --username [username]. This will display information about the specified user.

Remember, this is a simple search and does not include advanced search features. For more complex queries, consider using the full GitHub website.

Using Optional Arguments

Harnessing the Power of Optional Arguments in Typer

Typer supports optional arguments. You can define an argument as optional by providing a default value for it:

main.py
# Import the Typer library
import typer

# Create an instance of Typer
app = typer.Typer()

# Define a command named 'greet' using the @app.command() decorator
@app.command()
def greet(name: str = "world"):
    # Print a greeting to the name provided, or 'world' if no name is provided
    typer.echo(f"Hello {name}")

# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call app() to start the CLI
    app()

Now, if you run python main.py greet, the CLI will greet the world.

Enhancing Your CLI with Flags in Typer

Understanding and Using Flags

Flags are a convenient way to pass boolean options to your CLI. They are used to enable or disable certain features or behaviors in your command. In Typer, you can define a flag using the typer.Option function with a default value of False for disabled or True for enabled.

Here’s an example of how you can use flags with Typer:

main.py
# Import the Typer library
import typer

# Create an instance of Typer
app = typer.Typer()

# Define a command named 'greet' using the @app.command() decorator
@app.command()
def greet(name: str, formal: bool = typer.Option(False, "--formal", "-f"), excited: bool = typer.Option(False, "--excited", "-e")):
    # Choose the greeting based on whether the 'formal' flag is set
    greeting = f"Hello, {name}" if formal else f"Hi, {name}"
    # Choose the punctuation based on whether the 'excited' flag is set
    punctuation = "!" if excited else "."
    # Print the greeting and punctuation
    typer.echo(greeting + punctuation)

# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call app() to start the CLI
    app()

In this example, we have two flags: --formal and --excited. The --formal flag changes the greeting from “Hi” to “Hello”, and the --excited flag changes the punctuation from a period to an exclamation mark.

You can run this script with the command python main.py YourName --formal --excited. This will display the greeting “Hello, YourName!”.

Requesting User Input with Prompts

Typer allows you to request user input during command execution. Here’s an example:

main.py
# Import the Typer library
import typer

# Create an instance of Typer
app = typer.Typer()

# Define a command named 'login' using the @app.command() decorator
@app.command()
def login():
    # Prompt the user for their username
    username = typer.prompt("Username")
    # Prompt the user for their password, but hide their input
    password = typer.prompt("Password", hide_input=True)

    # Simulate an authentication check
    if password == "1234":
        # If the password is correct, print a success message
        typer.echo(f"Successfully logged in as {username}!")
    else:
        # If the password is incorrect, print an error message
        typer.echo("Incorrect password. Please try again.")

# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call app() to start the CLI
    app()

Using Callbacks in Typer

Typer allows you to execute a function before any command is executed using callbacks. Here’s an example:

main.py
# We import the Typer library
import typer

# We create an instance of Typer
app = typer.Typer()

# We define a function named 'callback' that returns a function which prints "Running callback"
def callback():
    def print_message():
        typer.echo("Running callback")
    return print_message

# We use the @app.callback() decorator to define a callback function
# The 'invoke_without_command=True' option means that the callback function will be invoked even if no command is called
@app.callback(invoke_without_command=True)
def main(ctx: typer.Context):  # The function takes one argument 'ctx' of type typer.Context
    ctx.obj = callback()  # We assign the 'callback' function to 'ctx.obj'

# We define a command named 'run' using the @app.command() decorator
@app.command()
def run(ctx: typer.Context):  # The function takes one argument 'ctx' of type typer.Context
    ctx.obj()  # We call the function assigned to 'ctx.obj', which is 'callback' in this case

# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call app() to start the CLI
    app()

To execute this script, you can use the command python script.py run. This will display the message “Running callback”.

Handling Arguments of Multiple Types

Typer allows you to handle arguments of multiple types, including lists and dictionaries. Here’s an example of how you can do that:

main.py
# Import the Typer and Typing library
from typing import List
import typer

# Create an instance of Typer
app = typer.Typer()

# Define a command named 'process' using the @app.command() decorator
@app.command()
def process(items: str = typer.Option(..., "--items", "-i")):
    # This function accepts a list of integers as an argument.
    # You can pass the list of integers using the --items or -i flag.
    items = list(map(int, items.split()))  # Convert the string to a list of integers
    for item in items:
        # For each item in the list, print a message saying that the item is being processed
        typer.echo(f"Processing item {item}")
        
# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call app() to start the CLI
    app()

To execute this script, you can use the command python script.py process --items "1 2 3". This will display the messages “Processing item 1”, “Processing item 2”, and “Processing item 3”.

Creating Command Groups

Typer allows you to create command groups, which is useful when you have many related commands. Here’s an example of how you can do that:

main.py
# Import the Typer library
import typer

# Create an instance of Typer
app = typer.Typer()

# Define a command named 'hello' using the @app.command() decorator
@app.command()
def hello(name: str):
    """This command greets the user."""
    typer.echo(f"Hello {name}")

# Define a command named 'goodbye' using the @app.command() decorator
@app.command()
def goodbye(name: str):
    """This command bids farewell to the user."""
    typer.echo(f"Goodbye {name}")

# Create another instance of Typer
cli = typer.Typer()
# Add the 'app' Typer instance to the 'cli' Typer instance under the name 'greetings'
cli.add_typer(app, name="greetings")

# We check if the script is being run directly and not imported as a module
if __name__ == "__main__":
# If the script is being run directly, we call cli() to start the CLI
    cli()

You can run this script with the command python main.py greetings hello YourName. This will display the greeting “Hello, YourName!”. Similarly, you can use the command python main.py greetings hello YourName to bid farewell to the user, displaying “Goodbye, YourName!”.

Conclusion

Typer is a powerful and flexible tool for creating command-line interfaces (CLI) in Python. Its clear and concise syntax makes code more readable and maintainable, which is essential for high-quality software development.

Typer’s flexibility allows you to create anything from simple command-line scripts to complex CLIs. Furthermore, its support for dependency injection makes code organization and reuse easier.

In summary, if you’re looking for a tool to create CLIs in Python, Typer is definitely worth considering. Whether you’re a beginner or experienced developer, Typer has the features and flexibility to meet your needs. Try Typer today and see how it can improve your software development workflow.