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)