Skip to content

Nested Commands

Probably the most important click feature is represented by command groups, where commands can be nested to create an interface with multiple commands. The following sections provide a detailed view on how this functionality is supported in clidantic.

The Parser class

Command groups are natively supported in clidantic through the Parser class: each Parser acts seamlessly either as command when only one is available, or as a group of commands when more than one is added to it. The following CLI only contains one command:

single.py
from pydantic import BaseModel
from clidantic import Parser


class Item(BaseModel):
    name: str
    price: float = 0.0


cli = Parser(name="items")


@cli.command()
def buy(item: Item):
    print(f"Bought {item.name} for ${item.price:.2f}")


if __name__ == "__main__":
    cli()
When executing the help, since there's no other option, clidantic immediately shows the help for the buy command, thus acting as a single click Command:
$ python single.py --help
> Usage: single.py [OPTIONS]
>
> Options:
>   --name TEXT    [required]
>   --price FLOAT  [default: 0.0]
>   --help         Show this message and exit.

Parsers can handle a list of commands without explicit nesting, internally creating a click group. When there is more than one choice, the function name is necessary to execute the correct command. For instance, with two commands:

multi.py
from pydantic import BaseModel
from clidantic import Parser


class Item(BaseModel):
    name: str
    price: float = 0.0


cli = Parser(name="items")


@cli.command()
def buy(item: Item):
    print(f"Bought {item.name} for ${item.price:.2f}")


@cli.command()
def sell(item: Item):
    print(f"Sold {item.name} for ${item.price:.2f}")


if __name__ == "__main__":
    cli()

The help will provide the following information:

$ python multi.py --help
> Usage: multi.py [OPTIONS] COMMAND [ARGS]...
>
> Options:
>   --help  Show this message and exit.
>
> Commands:
>   buy
>   sell

The input description for the required fields will only be provided on the help of the specific command itself. Of course, input parameters are provided after the command name:

$ python multi.py buy --help
> Usage: multi.py buy [OPTIONS]
>
> Options:
>   --name TEXT    [required]
>   --price FLOAT  [default: 0.0]
>   --help         Show this message and exit.

$ python multi.py buy --name bananas --price 2.0
> Bought bananas for $2.00

Warning

The support for groups and nested commands is still experimental. Please report any advanced functionality that is currently missing.

Combining multiple parsers

In click, commands and groups can be repeatedly combined and nested to form a tree-like structure of functions. This is extremely useful to organize more complex tools, imagine something like git, which provides multiple complex sub-commands.

In clidantic, this feature is supported through the Parser.merge option:

nested.py
from pydantic import BaseModel

from clidantic import Parser


class Item(BaseModel):
    name: str
    price: float = 0.0


# the name is required for nested CLIs
items = Parser(name="items")
store = Parser(name="store")


@items.command()
def buy(item: Item):
    print(f"Bought {item.name} for ${item.price:.2f}")


@items.command()
def sell(item: Item):
    print(f"Sold {item.name} for ${item.price:.2f}")


@store.command()
def add(item: Item):
    print(f"Added {item.name} to the store")


@store.command()
def remove(item: Item):
    print(f"Removed {item.name} from the store")


# now the command groups can be merged together
cli = Parser.merge(items, store)

if __name__ == "__main__":
    cli()

In this specific case, every Parser instance requires its own name, so that the user can select the right group during the execution. Of course, the help and command invocation will reflect this command hierachy:

$ python nested.py --help
> Usage: nested.py [OPTIONS] COMMAND [ARGS]...
>
> Options:
>   --help  Show this message and exit.
>
> Commands:
>   items
>   store

$ python nested.py store --help
> Usage: nested.py store [OPTIONS] COMMAND [ARGS]...
>
> Options:
>   --help  Show this message and exit.
>
> Commands:
>   add
>   remove

$ python nested.py store add --help
Usage: nested.py store add [OPTIONS]
>
> Options:
>   --name TEXT    [required]
>   --price FLOAT  [default: 0.0]
>   --help         Show this message and exit.

$ python nested.py store add --name tomatoes --price 4.0
> Added tomatoes to the store