Skip to content

Customization

The help directive

As in every command line interface, before any actual command is executed it is often required to consult the help. As usual, to print the available commands and their options it is sufficient to execute the command with the --help/-h option. For instance, a script like the following:

main.py
from pydantic import BaseModel

from clidantic import Parser

cli = Parser()


class Config(BaseModel):
    name: str


@cli.command()
def hello(args: Config):
    print(f"Hi, {args.name}!")


if __name__ == "__main__":
    cli()
Will provide the following output:
$ python main.py --help
> Usage: main.py [OPTIONS]
>
> Options:
>   --name TEXT  [required]
>   --help       Show this message and exit.

As you can notice, every field from the configuration model parsed by the command is converted into a click option and displayed in the help. Each field has a name, a type, an optional required flag and its description. In this specific example, the description is present for the help command only, while the name is missing one.

Adding descriptions

Defining configurations through types only does not allow for descriptive definitions. Pydantic allows for field descriptions through the Field class:

main.py
from pydantic import BaseModel, Field

from clidantic import Parser

cli = Parser()


class Config(BaseModel):
    name: str = Field(description="How I should call you")


@cli.command()
def hello(args: Config):
    print(f"Oh, hi {args.name}!")


if __name__ == "__main__":
    cli()
This will allow fields to have more details in the help output:
$ python main.py --help
> Usage: main.py [OPTIONS]
>
> Options:
>  --name TEXT  How I should call you  [required]
>  --help       Show this message and exit.

Default values

Models support default values by simply assigning them to the field:

class Config(BaseModel):
    name: str = "Mark"
    age: int = 42

It is also possible to combine default values and descriptions through the Field class, for instance:

main.py
from pydantic import BaseModel, Field

from clidantic import Parser

cli = Parser()


class Config(BaseModel):
    name: str = Field("Mark", description="How I should call you")


@cli.command()
def hello(args: Config):
    print(f"Oh, hi {args.name}!")


if __name__ == "__main__":
    cli()

When executed, this script will result in:

$ python main.py --help
> Usage: main.py [OPTIONS]
>
> Options:
> --name TEXT  How I should call you  [default: Mark]
> --help       Show this message and exit.
Informing the user that the name field is not strictly required, and in case it is not provided it will assume the value Mark.

Command descriptions

Likewise, it is also possible to provide a description to the command itself to be shown in as additional information in the help content. Following click, this is as simple as inserting docstrings under the decorated function:

main.py
from pydantic import BaseModel, Field

from clidantic import Parser

cli = Parser()


class Config(BaseModel):
    name: str = Field(description="How I should call you")


@cli.command()
def hello(args: Config):
    """Greets the user with the given name"""
    print(f"Oh, hi {args.name}!")


if __name__ == "__main__":
    cli()
Executing the help command will result in:

$ python main.py --help
> Usage: main.py [OPTIONS]
>
>   Greets the user with the given name
>
> Options:
>   --name TEXT  How I should call you  [required]
>   --help       Show this message and exit.

Additional names and aliases

Option names, especially in heavily nested models, can become difficult to type. As common practice in command line tools, clidantic offers the possibility to add additional names to each field. This feature is allowed by the custom CLIField operator:

main.py
from pydantic import BaseModel

from clidantic import Parser
from clidantic.fields import CLIField

cli = Parser()


class Config(BaseModel):
    name: str = CLIField("-n", "--nombre", default="Mark", description="How I should call you")


@cli.command()
def hello(args: Config):
    """Greets the user with the given name"""
    print(f"Oh, hi {args.name}!")


if __name__ == "__main__":
    cli()

In this case, the output of the help command will be the following:

$ python main.py --help
> Usage: main.py [OPTIONS]
>
>   Greets the user with the given name
>
> Options:
>   -n, --name, --nombre TEXT  How I should call you  [default: Mark]
>   --help                     Show this message and exit.

In the example, you may have noticed a few things: - CLIField is not part of pydantic, it is a customized version present in clidantic. - The default argument has become a kwarg, instead of being the first positional argument. - The new field takes as first (optional) arguments a variable list of additional names for the field.

Aside from these differences, the CLIField operator directly mirrors the standard Field functionality, therefore any other input or mechanism can be effectively used, see the description argument for instance.

Warning

When using optional names, keep in mind that the uniqueness is not verified, and nested models will not affect the result. In practice, fields such as main.subfield-a, if renamed sa will not need the main. prefix. However, this can be done manually. Consider this when providing additional names.