.. Comment: this file is automatically generated by `update_example_docs.py`. It should not be modified manually. .. _example-category-subcommands: Subcommands =========== In these examples, we show how :func:`tyro.cli` can be used to create CLI interfaces with subcommands. .. _example-01_subcommands: Subcommands are Unions ---------------------- All of :mod:`tyro`'s subcommand features are built using unions over struct types (typically dataclasses). Subcommands are used to choose between types in the union; arguments are then populated from the chosen type. .. note:: For configuring subcommands beyond what can be expressed with type annotations, see :func:`tyro.conf.subcommand()`. .. code-block:: python :linenos: # 01_subcommands.py from __future__ import annotations import dataclasses import tyro @dataclasses.dataclass(frozen=True) class Checkout: """Checkout a branch.""" branch: str @dataclasses.dataclass(frozen=True) class Commit: """Commit changes.""" message: str if __name__ == "__main__": cmd = tyro.cli(Checkout | Commit) print(cmd) Print the helptext. This will show the available subcommands: .. raw:: html
$ python ./01_subcommands.py --help usage: 01_subcommands.py [-h] {checkout,commit} ╭─ options ───────────────────────────────────────────────╮ │ -h, --help show this help message and exit │ ╰─────────────────────────────────────────────────────────╯ ╭─ subcommands ───────────────────────────────────────────╮ │ {checkout,commit} │ │ checkout Checkout a branch. │ │ commit Commit changes. │ ╰─────────────────────────────────────────────────────────╯The `commit` subcommand: .. raw:: html
$ python ./01_subcommands.py commit --help usage: 01_subcommands.py commit [-h] --message STR Commit changes. ╭─ options ────────────────────────────────────────────╮ │ -h, --help show this help message and exit │ │ --message STR (required) │ ╰──────────────────────────────────────────────────────╯.. raw:: html
$ python ./01_subcommands.py commit --message hello
Commit(message='hello')
The `checkout` subcommand:
.. raw:: html
$ python ./01_subcommands.py checkout --help usage: 01_subcommands.py checkout [-h] --branch STR Checkout a branch. ╭─ options ───────────────────────────────────────────╮ │ -h, --help show this help message and exit │ │ --branch STR (required) │ ╰─────────────────────────────────────────────────────╯.. raw:: html
$ python ./01_subcommands.py checkout --branch main
Checkout(branch='main')
.. _example-02_subcommands_in_func:
Subcommands as Function Arguments
---------------------------------
A subcommand will be created for each input annotated with a union over
struct types.
.. note::
To prevent :func:`tyro.cli()` from converting a Union type into a subcommand,
use :class:`tyro.conf.AvoidSubcommands`.
.. note::
Argument ordering for subcommands can be tricky. In the example below,
``--shared-arg`` must always come *before* the subcommand. As an option for
alleviating this, see :class:`tyro.conf.ConsolidateSubcommandArgs`.
.. code-block:: python
:linenos:
# 02_subcommands_in_func.py
from __future__ import annotations
import dataclasses
import tyro
@dataclasses.dataclass(frozen=True)
class Checkout:
"""Checkout a branch."""
branch: str
@dataclasses.dataclass(frozen=True)
class Commit:
"""Commit changes."""
message: str
def main(
shared_arg: int,
cmd: Checkout | Commit = Checkout(branch="default"),
):
print(f"{shared_arg=}")
print(cmd)
if __name__ == "__main__":
tyro.cli(main)
Print the helptext. This will show the available subcommands:
.. raw:: html
$ python ./02_subcommands_in_func.py --help usage: 02_subcommands_in_func.py [-h] --shared-arg INT [{cmd:checkout,cmd:commit}] ╭─ options ───────────────────────────────────────────────╮ │ -h, --help show this help message and exit │ │ --shared-arg INT (required) │ ╰─────────────────────────────────────────────────────────╯ ╭─ optional subcommands ──────────────────────────────────╮ │ (default: cmd:checkout) │ │ ────────────────────────────────────────── │ │ [{cmd:checkout,cmd:commit}] │ │ cmd:checkout Checkout a branch. │ │ cmd:commit Commit changes. │ ╰─────────────────────────────────────────────────────────╯Using the default subcommand: .. raw:: html
$ python ./02_subcommands_in_func.py --shared-arg 100
shared_arg=100
Checkout(branch='default')
Choosing a different subcommand:
.. raw:: html
$ python ./02_subcommands_in_func.py --shared-arg 100 cmd:commit --cmd.message 'Hello!'
shared_arg=100
Commit(message='Hello!')
.. _example-03_multiple_subcommands:
Sequenced Subcommands
---------------------
Multiple unions over struct types are populated using a series of subcommands.
.. code-block:: python
:linenos:
# 03_multiple_subcommands.py
from __future__ import annotations
import dataclasses
from typing import Literal
import tyro
# Possible dataset configurations.
@dataclasses.dataclass
class Mnist:
binary: bool = False
"""Set to load binary version of MNIST dataset."""
@dataclasses.dataclass
class ImageNet:
subset: Literal[50, 100, 1000]
"""Choose between ImageNet-50, ImageNet-100, ImageNet-1000, etc."""
# Possible optimizer configurations.
@dataclasses.dataclass
class Adam:
learning_rate: float = 1e-3
betas: tuple[float, float] = (0.9, 0.999)
@dataclasses.dataclass
class Sgd:
learning_rate: float = 3e-4
# Train script.
def train(
dataset: Mnist | ImageNet = Mnist(),
optimizer: Adam | Sgd = Adam(),
) -> None:
"""Example training script.
Args:
dataset: Dataset to train on.
optimizer: Optimizer to train with.
Returns:
None:
"""
print(dataset)
print(optimizer)
if __name__ == "__main__":
tyro.cli(train, config=(tyro.conf.ConsolidateSubcommandArgs,))
We apply the :class:`tyro.conf.ConsolidateSubcommandArgs` flag. This
pushes all arguments to the end of the command:
.. raw:: html
$ python ./03_multiple_subcommands.py --help usage: 03_multiple_subcommands.py [-h] {dataset:mnist,dataset:image-net} Example training script. ╭─ options ─────────────────────────────────────────╮ │ -h, --help show this help message and exit │ ╰───────────────────────────────────────────────────╯ ╭─ subcommands ─────────────────────────────────────╮ │ Dataset to train on. │ │ ───────────────────────────────── │ │ {dataset:mnist,dataset:image-net} │ │ dataset:mnist │ │ dataset:image-net │ ╰───────────────────────────────────────────────────╯.. raw:: html
$ python ./03_multiple_subcommands.py dataset:mnist --help usage: 03_multiple_subcommands.py dataset:mnist [-h] {optimizer:adam,optimizer:sgd} ╭─ options ─────────────────────────────────────────╮ │ -h, --help show this help message and exit │ ╰───────────────────────────────────────────────────╯ ╭─ subcommands ─────────────────────────────────────╮ │ Optimizer to train with. │ │ ────────────────────────────── │ │ {optimizer:adam,optimizer:sgd} │ │ optimizer:adam │ │ optimizer:sgd │ ╰───────────────────────────────────────────────────╯.. raw:: html
$ python ./03_multiple_subcommands.py dataset:mnist optimizer:adam --help usage: 03_multiple_subcommands.py dataset:mnist optimizer:adam [-h] [--optimizer.learning-rate FLOAT] [--optimizer.betas FLOAT FLOAT] [--dataset.binary | --dataset.no-binary] ╭─ options ─────────────────────────────────────────────────────────╮ │ -h, --help │ │ show this help message and exit │ ╰───────────────────────────────────────────────────────────────────╯ ╭─ optimizer options ───────────────────────────────────────────────╮ │ --optimizer.learning-rate FLOAT │ │ (default: 0.001) │ │ --optimizer.betas FLOAT FLOAT │ │ (default: 0.9 0.999) │ ╰───────────────────────────────────────────────────────────────────╯ ╭─ dataset options ─────────────────────────────────────────────────╮ │ --dataset.binary, --dataset.no-binary │ │ Set to load binary version of MNIST dataset. (default: False) │ ╰───────────────────────────────────────────────────────────────────╯.. raw:: html
$ python ./03_multiple_subcommands.py dataset:mnist optimizer:adam --optimizer.learning-rate 3e-4 --dataset.binary
Mnist(binary=True)
Adam(learning_rate=0.0003, betas=(0.9, 0.999))
.. _example-04_decorator_subcommands:
Decorator-based Subcommands
---------------------------
:func:`tyro.extras.SubcommandApp()` provides a decorator-based API for
subcommands, which is inspired by `click $ python 04_decorator_subcommands.py --help usage: 04_decorator_subcommands.py [-h] {greet,addition} ╭─ options ───────────────────────────────────────────────╮ │ -h, --help show this help message and exit │ ╰─────────────────────────────────────────────────────────╯ ╭─ subcommands ───────────────────────────────────────────╮ │ {greet,addition} │ │ greet Greet someone. │ │ addition Add two numbers. │ ╰─────────────────────────────────────────────────────────╯.. raw:: html
$ python 04_decorator_subcommands.py greet --help usage: 04_decorator_subcommands.py greet [-h] --name STR [--loud | --no-loud] Greet someone. ╭─ options ───────────────────────────────────────────────╮ │ -h, --help show this help message and exit │ │ --name STR (required) │ │ --loud, --no-loud (default: False) │ ╰─────────────────────────────────────────────────────────╯.. raw:: html
$ python 04_decorator_subcommands.py greet --name Alice
Hello, Alice!
.. raw:: html
$ python 04_decorator_subcommands.py greet --name Bob --loud
HELLO, BOB!
.. raw:: html
$ python 04_decorator_subcommands.py addition --help usage: 04_decorator_subcommands.py addition [-h] --a INT --b INT Add two numbers. ╭─ options ─────────────────────────────────────────╮ │ -h, --help show this help message and exit │ │ --a INT (required) │ │ --b INT (required) │ ╰───────────────────────────────────────────────────╯.. raw:: html
$ python 04_decorator_subcommands.py addition --a 5 --b 3
5 + 3 = 8
.. _example-05_subcommands_func:
Subcommands from Functions
--------------------------
We provide a shorthand for generating a subcommand CLI from a dictionary. This
is a thin wrapper around :func:`tyro.cli()`'s more verbose, type-based API. If
more generality is needed, the internal working are explained in the docs for
:func:`tyro.extras.subcommand_cli_from_dict()`.
.. code-block:: python
:linenos:
# 05_subcommands_func.py
import tyro
def checkout(branch: str) -> None:
"""Check out a branch."""
print(f"{branch=}")
def commit(message: str, all: bool = False) -> None:
"""Make a commit."""
print(f"{message=} {all=}")
if __name__ == "__main__":
tyro.extras.subcommand_cli_from_dict(
{
"checkout": checkout,
"commit": commit,
}
)
.. raw:: html
$ python ./05_subcommands_func.py --help usage: 05_subcommands_func.py [-h] {checkout,commit} ╭─ options ───────────────────────────────────────────────╮ │ -h, --help show this help message and exit │ ╰─────────────────────────────────────────────────────────╯ ╭─ subcommands ───────────────────────────────────────────╮ │ {checkout,commit} │ │ checkout Check out a branch. │ │ commit Make a commit. │ ╰─────────────────────────────────────────────────────────╯.. raw:: html
$ python ./05_subcommands_func.py commit --help usage: 05_subcommands_func.py commit [-h] --message STR [--all | --no-all] Make a commit. ╭─ options ──────────────────────────────────────────────╮ │ -h, --help show this help message and exit │ │ --message STR (required) │ │ --all, --no-all (default: False) │ ╰────────────────────────────────────────────────────────╯.. raw:: html
$ python ./05_subcommands_func.py commit --message hello --all
message='hello' all=True
.. raw:: html
$ python ./05_subcommands_func.py checkout --help usage: 05_subcommands_func.py checkout [-h] --branch STR Check out a branch. ╭─ options ───────────────────────────────────────────╮ │ -h, --help show this help message and exit │ │ --branch STR (required) │ ╰─────────────────────────────────────────────────────╯.. raw:: html
$ python ./05_subcommands_func.py checkout --branch main
branch='main'