Skip to content

Parameter declarations with Annotated

Use typing.Annotated to keep CLI configuration in a parameter's type annotation and its default value in the Python function signature:

from __future__ import annotations

from typing import Annotated

import typer_cloup as typer


def main(
    name: Annotated[str, typer.Argument(help="Person to greet")] = "World",
    greeting: Annotated[
        str, typer.Option("--label", "-l", help="Greeting text")
    ] = "Hello",
):
    typer.echo(f"{greeting}, {name}!")


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

Here, typer.Argument() makes name a positional argument, while typer.Option("--label", "-l") gives greeting long and short option names. The defaults are ordinary Python values, so calling main() directly uses "World" and "Hello" too.

$ python main.py
Hello, World!

$ python main.py Camila --label Welcome
Welcome, Camila!

With Annotated, every positional value passed to typer.Option() is an option name. Set the parameter's default with =. Omitting the default makes the CLI parameter required:

from typing import Annotated

import typer_cloup as typer


def main(name: Annotated[str, typer.Option("--name", "-n")]):
    typer.echo(name)

Annotated also supports the existing parameter settings, including envvar, callback, type, group, and shell_quoted_list. For optional values, use an annotation such as str | None and set the default to None. An annotation that allows None does not by itself make a CLI parameter optional.

Dynamic defaults

Use default_factory to compute a default when the CLI parameter is omitted:

from datetime import datetime
from typing import Annotated

import typer_cloup as typer


def current_year() -> int:
    return datetime.now().year


def main(year: Annotated[int, typer.Option(default_factory=current_year)]):
    typer.echo(year)

default_factory also works with declarations in the older style:

def main(year: int = typer.Option(default_factory=current_year)):
    typer.echo(year)

Existing callable defaults, such as typer.Option(current_year), continue to work. default_factory makes the intent explicit without putting a callable in the function's default value when using Annotated.

Declaration rules

Use at most one typer.Option() or typer.Argument() metadata object per parameter. Other metadata is ignored by Typer-Cloup.

When using Annotated, set ordinary defaults with =, rather than inside typer.Argument(). Do not also assign typer.Option() or typer.Argument() as the function's default value. A parameter cannot have both an ordinary default and default_factory.

Metadata can be reused across parameters. Each parameter gets its own copy of the configuration, while objects such as Cloup option groups retain their identity. Forward references and from __future__ import annotations are supported.

The older declaration style, such as name: str = typer.Option("World", "--label"), remains supported.

You can ask questions about Typer. Try:
How can I terminate a program?
How to launch applications?
How to add help to CLI argument?