Skip to content

Version CLI Option, is_eagerΒΆ

You could use a callback to implement a --version CLI option.

It would show the version of your CLI program and then it would terminate it. Even before any other CLI parameter is processed.

First version of --versionΒΆ

Let's see a first version of how it could look like:

from typing import Optional

import typer
from typing_extensions import Annotated

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def main(
    name: Annotated[str, typer.Option()] = "World",
    version: Annotated[
        Optional[bool], typer.Option("--version", callback=version_callback)
    ] = None,
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Tip

Prefer to use the Annotated version if possible.

from typing import Optional

import typer

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def main(
    name: str = typer.Option("World"),
    version: Optional[bool] = typer.Option(
        None, "--version", callback=version_callback
    ),
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Tip

Notice that we don't have to get the typer.Context and check for ctx.resilient_parsing for completion to work, because we only print and modify the program when --version is passed, otherwise, nothing is printed or changed from the callback.

If the --version CLI option is passed, we get a value True in the callback.

Then we can print the version and raise typer.Exit() to make sure the program is terminated before anything else is executed.

We also declare the explicit CLI option name --version, because we don't want an automatic --no-version, it would look awkward.

Check it:

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

Options:
--version
--name TEXT
--help Show this message and exit.


python main.py --name Camila
Hello Camila

python main.py --version
Awesome CLI Version: 0.1.0


restart ↻

Previous parameters and is_eagerΒΆ

But now let's say that the --name CLI option that we declared before --version is required, and it has a callback that could exit the program:

from typing import Optional

import typer
from typing_extensions import Annotated

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")


def main(
    name: Annotated[str, typer.Option(callback=name_callback)],
    version: Annotated[
        Optional[bool], typer.Option("--version", callback=version_callback)
    ] = None,
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Tip

Prefer to use the Annotated version if possible.

from typing import Optional

import typer

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")


def main(
    name: str = typer.Option(..., callback=name_callback),
    version: Optional[bool] = typer.Option(
        None, "--version", callback=version_callback
    ),
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Then our CLI program could not work as expected in some cases as it is right now, because if we use --version after --name then the callback for --name will be processed before and we can get its error:

fast β†’python main.py --name Rick --version
Only Camila is allowed
Aborted!

restart ↻

Tip

We don't have to check for ctx.resilient_parsing in the name_callback() for completion to work, because we are not using typer.echo(), instead we are raising a typer.BadParameter.

Technical Details

typer.BadParameter prints the error to "standard error", not to "standard output", and because the completion system only reads from "standard output", it won't break completion.

Info

If you need a refresher about what is "standard output" and "standard error" check the section in Printing and Colors: "Standard Output" and "Standard Error".

Fix with is_eagerΒΆ

For those cases, we can mark a CLI parameter (a CLI option or CLI argument) with is_eager=True.

That will tell Typer (actually Click) that it should process this CLI parameter before the others:

from typing import Optional

import typer
from typing_extensions import Annotated

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return name


def main(
    name: Annotated[str, typer.Option(callback=name_callback)],
    version: Annotated[
        Optional[bool],
        typer.Option("--version", callback=version_callback, is_eager=True),
    ] = None,
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Tip

Prefer to use the Annotated version if possible.

from typing import Optional

import typer

__version__ = "0.1.0"


def version_callback(value: bool):
    if value:
        print(f"Awesome CLI Version: {__version__}")
        raise typer.Exit()


def name_callback(name: str):
    if name != "Camila":
        raise typer.BadParameter("Only Camila is allowed")
    return name


def main(
    name: str = typer.Option(..., callback=name_callback),
    version: Optional[bool] = typer.Option(
        None, "--version", callback=version_callback, is_eager=True
    ),
):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Check it:

fast β†’python main.py --name Rick --version
Awesome CLI Version: 0.1.0

restart ↻
Was this page helpful?