Skip to content

Typer CallbackΒΆ

When you create an app = typer.Typer() it works as a group of commands.

And you can create multiple commands with it.

Each of those commands can have their own CLI parameters.

But as those CLI parameters are handled by each of those commands, they don't allow us to create CLI parameters for the main CLI application itself.

But we can use @app.callback() for that.

It's very similar to @app.command(), but it declares the CLI parameters for the main CLI application (before the commands):

import typer

app = typer.Typer()
state = {"verbose": False}


@app.command()
def create(username: str):
    if state["verbose"]:
        print("About to create a user")
    print(f"Creating user: {username}")
    if state["verbose"]:
        print("Just created a user")


@app.command()
def delete(username: str):
    if state["verbose"]:
        print("About to delete a user")
    print(f"Deleting user: {username}")
    if state["verbose"]:
        print("Just deleted a user")


@app.callback()
def main(verbose: bool = False):
    """
    Manage users in the awesome CLI app.
    """
    if verbose:
        print("Will write verbose output")
        state["verbose"] = True


if __name__ == "__main__":
    app()

Here we create a callback with a --verbose CLI option.

Tip

After getting the --verbose flag, we modify a global state, and we use it in the other commands.

There are other ways to achieve the same, but this will suffice for this example.

And as we added a docstring to the callback function, by default it will be extracted and used as the help text.

Check it:

fast β†’python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...

Manage users in the awesome CLI app.

Options:
--verbose / --no-verbose [default: False]
--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:
create
delete


python main.py create Camila
Creating user: Camila

python main.py --verbose create Camila
Will write verbose output
About to create a user
Creating user: Camila
Just created a user

python main.py create --verbose Camila
Usage: main.py create [OPTIONS] USERNAME
Try "main.py create --help" for help.

Error: No such option: --verbose

restart ↻

Adding a callback on creationΒΆ

It's also possible to add a callback when creating the typer.Typer() app:

import typer


def callback():
    print("Running a command")


app = typer.Typer(callback=callback)


@app.command()
def create(name: str):
    print(f"Creating user: {name}")


if __name__ == "__main__":
    app()

That achieves the same as with @app.callback().

Check it:

fast β†’python main.py create Camila
Running a command
Creating user: Camila

restart ↻

Overriding a callbackΒΆ

If you added a callback when creating the typer.Typer() app, it's possible to override it with @app.callback():

import typer


def callback():
    print("Running a command")


app = typer.Typer(callback=callback)


@app.callback()
def new_callback():
    print("Override callback, running a command")


@app.command()
def create(name: str):
    print(f"Creating user: {name}")


if __name__ == "__main__":
    app()

Now new_callback() will be the one used.

Check it:

fast β†’python main.py create Camila
Override callback, running a command
Creating user: Camila

restart ↻

Adding a callback only for documentationΒΆ

You can also add a callback just to add the documentation in the docstring.

It can be convenient especially if you have several lines of text, as the indentation will be automatically handled for you:

import typer

app = typer.Typer()


@app.callback()
def callback():
    """
    Manage users CLI app.

    Use it with the create command.

    A new user with the given NAME will be created.
    """


@app.command()
def create(name: str):
    print(f"Creating user: {name}")


if __name__ == "__main__":
    app()

Now the callback will be used mainly to extract the docstring for the help text.

Check it:

fast β†’python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...

Manage users CLI app.

Use it with the create command.

A new user with the given NAME will be created.

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:
create

python main.py create Camila
Creating user: Camila

restart ↻

Click GroupΒΆ

If you come from Click, this Typer callback is the equivalent of the function in a Click Group.

For example:

import click

@click.group()
def cli():
    pass

The original function cli would be the equivalent of a Typer callback.

Technical Details

When using Click, it converts that cli variable to a Click Group object. And then the original function no longer exists in that variable.

Typer doesn't do that, the callback function is not modified, only registered in the typer.Typer app. This is intentional, it's part of Typer's design, to allow having editor auto completion and type checks.

Was this page helpful?