Parameter types and validation

Click · Practical · 12 min

Declaring types moves validation out of your code and gives users precise error messages.

Every parameter has a type that converts the raw string and validates it. Built-ins beyond int, float, bool and str:

  • click.Choice(["json", "csv"], case_sensitive=False): one of a fixed set, shown in help. Since 8.2 it also accepts an Enum class, and your function receives the enum member.
  • click.IntRange(1, 10) / click.FloatRange(0, 1): numbers within bounds; clamp=True clips instead of failing.
  • click.Path(exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path): validates a filesystem path and returns a Path object if you ask.
  • click.File("r") / click.File("wb"): opens the file for you, supports - for stdin/stdout, and closes it when the command finishes. Output files are opened lazily, so a failed command does not leave empty files.
  • click.DateTime(formats=[...]), click.UUID, and tuples of types such as type=(str, int) with nargs=2.

When conversion fails, Click prints a usage error such as Error: Invalid value for '--level': 12 is not in the range 1<=x<=10. and exits with code 2. Your function never runs with invalid data.

Choosing Path versus File: use File for simple read/write of one stream, especially when supporting -. Use Path when you need the path itself (directories, passing to other libraries, opening several times).

Environment variables and prompts go through the same types, so validation is consistent wherever the value comes from.

Example

import enum
import pathlib
import click

class Fmt(enum.Enum):
    JSON = "json"
    CSV = "csv"

@click.command()
@click.option("--fmt", type=click.Choice(Fmt, case_sensitive=False), default="json")
@click.option("--level", type=click.IntRange(1, 10), default=5)
@click.option("--src", type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path))
@click.option("--out", type=click.File("w"), default="-")
def export(fmt, level, src, out):
    out.write(f"{fmt} level={level} src={src}\n")

export.main(["--fmt", "CSV", "--level", "3"], standalone_mode=False)
# Fmt.CSV level=3 src=None
try:
    export.main(["--level", "12"], standalone_mode=False)
except click.BadParameter as e:
    print(e.format_message())
# Invalid value for '--level': 12 is not in the range 1<=x<=10.

Remember

  • Choice, IntRange, Path and File validate and convert input
  • Invalid values fail with a clear usage error before your code runs
  • File handles '-' and closes files; Path gives you the path

Watch out for

  • Opening files manually from a str option instead of using click.File or click.Path validation.

Check yourself

  1. What does a user see for --level 12 with type=click.IntRange(1, 10)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: click.palletsprojects.com/en/stable/parameter-types/