Skip to content

Path

You can declare a CLI parameter to be a standard Python pathlib.Path.

This is what you would do for directory paths, file paths, etc:

from pathlib import Path
from typing import Optional

import typer


def main(config: Optional[Path] = typer.Option(None)):
    if config is None:
        typer.echo("No config file")
        raise typer.Abort()
    if config.is_file():
        text = config.read_text()
        typer.echo(f"Config file contents: {text}")
    elif config.is_dir():
        typer.echo("Config is a directory, will use all its config files")
    elif not config.exists():
        typer.echo("The config doesn't exist")


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

And again, as you receive a standard Python Path object the same as the type annotation, your editor will give you autocompletion for all its attributes and methods.

Check it:

// No config
$ python main.py

No config file
Aborted!

// Pass a config that doesn't exist
$ python main.py --config config.txt

The config doesn't exist

// Now create a quick config
$ echo "some settings" > config.txt

// And try again
$ python main.py --config config.txt

Config file contents: some settings

// And with a directory
$ python main.py --config ./

Config is a directory, will use all its config files

Path validations

You can perform several validations for Path CLI parameters:

  • exists: if set to true, the file or directory needs to exist for this value to be valid. If this is not required and a file does indeed not exist, then all further checks are silently skipped.
  • file_okay: controls if a file is a possible value.
  • dir_okay: controls if a directory is a possible value.
  • writable: if true, a writable check is performed.
  • readable: if true, a readable check is performed.
  • resolve_path: if this is true, then the path is fully resolved before the value is passed onwards. This means that it’s absolute and symlinks are resolved.

Technical Details

It will not expand a tilde-prefix (something with ~, like ~/Documents/), as this is supposed to be done by the shell only.

Tip

All these parameters come directly from Click.

For example:

from pathlib import Path

import typer


def main(
    config: Path = typer.Option(
        ...,
        exists=True,
        file_okay=True,
        dir_okay=False,
        writable=False,
        readable=True,
        resolve_path=True,
    )
):
    text = config.read_text()
    typer.echo(f"Config file contents: {text}")


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

Check it:

$ python main.py --config config.txt

Usage: main.py [OPTIONS]
Try "main.py --help" for help.

Error: Invalid value for '--config': File 'config.txt' does not exist.

// Now create a quick config
$ echo "some settings" > config.txt

// And try again
$ python main.py --config config.txt

Config file contents: some settings

// And with a directory
$ python main.py --config ./

Usage: main.py [OPTIONS]
Try "main.py --help" for help.

Error: Invalid value for '--config': File './' is a directory.

Advanced Path configurations

Advanced Details

You probably won't need these configurations at first, you may want to skip it.

They are used for more advanced use cases.

  • allow_dash: If this is set to True, a single dash to indicate standard streams is permitted.
  • path_type: optionally a string type that should be used to represent the path. The default is None which means the return value will be either bytes or unicode depending on what makes most sense given the input data Click deals with.