Skip to main content

Custom Parsing and Validation

To implement custom logic for converting CLI strings into Python objects or to enforce specific validation rules, you can use the parser or click_type parameters in typer.Argument and typer.Option. These parameters are stored in the ArgumentInfo and OptionInfo models (which inherit from ParameterInfo) and are used by Typer to process user input.

Using a Custom Parser Function

The most straightforward way to implement custom parsing is to provide a callable to the parser argument. This function should take a single string as input and return the parsed value.

import typer
from typing_extensions import Annotated

class CustomClass:
def __init__(self, value: str):
self.value = value

def __str__(self):
return f"<CustomClass: value={self.value}>"

def parse_custom_class(value: str):
# Custom logic: double the input string
return CustomClass(value * 2)

app = typer.Typer()

@app.command()
def main(
custom_arg: Annotated[CustomClass, typer.Argument(parser=parse_custom_class)],
custom_opt: Annotated[CustomClass, typer.Option(parser=parse_custom_class)] = "Foo",
):
typer.echo(f"custom_arg is {custom_arg}")
typer.echo(f"--custom-opt is {custom_opt}")

if __name__ == "__main__":
app()

In this example, typer.Argument and typer.Option receive the parser argument, which is then stored in ArgumentInfo.parser and OptionInfo.parser respectively. Internally, Typer wraps this function in a click.types.FuncParamType.

Advanced Validation with Click Types

For scenarios requiring access to the Click context or more complex validation logic, you can provide a subclass of click.ParamType to the click_type argument.

import click
import typer

class BaseNumberParamType(click.ParamType):
name = "base_integer"

def convert(self, value, param, ctx):
try:
# Parse string as integer with base detection (e.g., 0x for hex)
return int(value, 0)
except ValueError:
self.fail(f"{value} is not a valid integer", param, ctx)

app = typer.Typer()

@app.command()
def main(
hex_value: int = typer.Argument(None, click_type=BaseNumberParamType()),
):
typer.echo(f"The integer value is: {hex_value}")

if __name__ == "__main__":
app()

Built-in Numeric Validation

The ParameterInfo base class (and thus Argument and Option) includes built-in support for numeric range validation using min, max, and clamp. These are automatically translated into Click's IntRange or FloatRange.

import typer

app = typer.Typer()

@app.command()
def main(
# Value must be between 0 and 100
score: int = typer.Option(50, min=0, max=100),
# Value will be clamped to the range [0.0, 1.0]
probability: float = typer.Option(0.5, min=0.0, max=1.0, clamp=True),
):
typer.echo(f"Score: {score}")
typer.echo(f"Probability: {probability}")

Handling Parsing Errors

When a custom parser fails, you can raise an exception. If you want to provide a user-friendly error message that identifies the problematic parameter, raise typer.BadParameter.

import typer

def parse_even_number(value: str) -> int:
try:
val = int(value)
if val % 2 != 0:
raise typer.BadParameter("Only even numbers are allowed")
return val
except ValueError:
raise typer.BadParameter("Must be a valid integer")

app = typer.Typer()

@app.command()
def main(
even_num: int = typer.Argument(..., parser=parse_even_number)
):
typer.echo(f"Even number: {even_num}")

Troubleshooting

Mutual Exclusivity

You cannot provide both parser and click_type for the same parameter. The ParameterInfo constructor (found in typer/models.py) explicitly checks for this and raises a ValueError if both are present:

# From typer/models.py
if parser and click_type:
raise ValueError(
"Multiple custom type parsers provided. "
"`parser` and `click_type` may not both be provided."
)

Lambda Parsers

For simple conversions, you can use a lambda function as a parser:

# Example from tests/test_type_conversion.py
@app.command()
def main(
hex_value: int = typer.Argument(None, parser=lambda x: int(x, 0)),
):
typer.echo(hex_value)