Skip to main content

Parameter Definition Validation

Typer performs strict validation on CLI parameter definitions during the inspection of command functions. This validation ensures that parameters are defined unambiguously, following a "single source of truth" principle for default values and metadata.

Validation occurs within typer.utils.get_params_from_function when a command or callback is registered. If a parameter definition violates Typer's rules, one of several specific exceptions is raised.

Single Source of Truth for Default Values

Typer enforces that default values for CLI parameters must be defined using standard Python assignment syntax (=) rather than being embedded within type annotations.

Annotated Parameters with Default Values

When using typing.Annotated, the Typer metadata object (like typer.Argument or typer.Option) must not contain a default value as its first positional argument. If it does, Typer raises AnnotatedParamWithDefaultValueError.

Incorrect:

@app.command()
def main(name: Annotated[str, typer.Argument("Jane Doe")]):
...

Correct:

@app.command()
def main(name: Annotated[str, typer.Argument()] = "Jane Doe"):
...

In typer/utils.py, the logic checks if the default attribute of the ParameterInfo object extracted from Annotated is set. If it is not the sentinel value ..., the exception is raised.

Mixed Annotation and Default Styles

Typer forbids mixing the Annotated style with the "default value" style for the same parameter. You cannot provide a Typer info object in both the type hint and the assignment. Doing so raises MixedAnnotatedAndDefaultStyleError.

Incorrect:

@app.command()
def main(
name: Annotated[str, typer.Argument()] = typer.Argument("Jane Doe")
):
...

Correct:

@app.command()
def main(name: Annotated[str, typer.Argument()] = "Jane Doe"):
...

This validation ensures that the parameter configuration is not split across two different Typer objects, which could lead to conflicting settings for attributes like help, envvar, or show_default.

Annotation Constraints

Typer's type system integration relies on extracting exactly one piece of Typer-specific metadata from a parameter's type hint.

Multiple Typer Annotations

If a parameter's Annotated type hint contains more than one Typer metadata object (e.g., multiple Argument or Option instances), Typer raises MultipleTyperAnnotationsError.

Incorrect:

@app.command()
def main(
name: Annotated[str, typer.Argument(), typer.Argument()]
):
...

The _split_annotation_from_typer_annotations helper in typer/utils.py identifies all instances of ParameterInfo within the Annotated args. If the count exceeds one, validation fails.

Default Value Conflicts

Even when not using Annotated, Typer validates the internal consistency of the Argument and Option objects.

Default vs. Default Factory

Typer allows providing either a static default value or a default_factory (a callable that returns a value). Providing both is ambiguous and raises DefaultFactoryAndDefaultValueError.

Incorrect:

@app.command()
def main(
name: str = typer.Argument("Jane Doe", default_factory=get_default_name)
):
...

In typer/utils.py, the validation logic checks if parameter_info.default is already set when a default_factory is present.

Special Case: Options in Annotated

There is a notable exception to the "no positional arguments in Annotated" rule for typer.Option.

In a standard assignment, the first positional argument to typer.Option is the default value: name: str = typer.Option("Jane Doe", "--name")

However, when used in Annotated, Typer interprets positional arguments to typer.Option as option names (e.g., --name, -n) rather than a default value. This is handled in get_params_from_function:

# From typer/utils.py
if (
isinstance(parameter_info, OptionInfo)
and parameter_info.default is not ...
):
parameter_info.param_decls = (
cast(str, parameter_info.default),
*(parameter_info.param_decls or ()),
)
parameter_info.default = ...

This allows Annotated[str, typer.Option("--name")] to be valid, whereas Annotated[str, typer.Argument("some-val")] remains invalid because Argument does not use positional arguments for names.