.. 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 class Checkout: """Checkout a branch.""" branch: str @dataclasses.dataclass 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 ─────────────────────────────────╮
    │ (required)                                    │
    │   • 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.CascadeSubcommandArgs`. .. code-block:: python :linenos: # 02_subcommands_in_func.py from __future__ import annotations import dataclasses import tyro @dataclasses.dataclass class Checkout: """Checkout a branch.""" branch: str @dataclasses.dataclass 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)                      │
    ╰───────────────────────────────────────────────────╯
    ╭─ subcommands ─────────────────────────────────────╮
    │ (default: cmd:checkout)                           │
    │   • 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.""" binaries: bool = False # 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.CascadeSubcommandArgs,)) We apply the :class:`tyro.conf.CascadeSubcommandArgs` flag. This allows arguments to be intermixed more flexibly: .. raw:: html
    $ python ./03_multiple_subcommands.py --help
    usage: ./03_multiple_subcommands.py [-h] [OPTIONS] [DATASET] [OPTIMIZER]
    
    Example training script.
    
    ╭─ options ────────────────────────────────────────────────────────────────────╮
    │ -h, --help             show this help message and exit                       │
    ╰──────────────────────────────────────────────────────────────────────────────╯
    ╭─ dataset options ────────────────────────────────────────────────────────────╮
    │ (source subcommand: dataset:mnist)                                           │
    │ ──────────────────────────────────────────────────────────────────────────── │
    │ --dataset.binary, --dataset.no-binary                                        │
    │                        Set to load binary version of MNIST dataset.          │
    │                        (default: False)                                      │
    ╰──────────────────────────────────────────────────────────────────────────────╯
    ╭─ optimizer options ──────────────────────────────────────────────────────────╮
    │ (source subcommand: optimizer:adam)                                          │
    │ ──────────────────────────────────────────────────────────────────────────── │
    │ --optimizer.learning-rate FLOAT                                              │
    │                        (default: 0.001)                                      │
    │ --optimizer.betas FLOAT FLOAT                                                │
    │                        (default: 0.9 0.999)                                  │
    ╰──────────────────────────────────────────────────────────────────────────────╯
    ╭─ subcommands ────────────────────────────────────────────────────────────────╮
    │ Dataset to train on. (default: dataset:mnist)                                │
    │   • dataset:mnist                                                            │
    │   • dataset:image-net                                                        │
    │ ──────────────────────────────────────────────────────────────────────────── │
    │ Optimizer to train with. (default: optimizer:adam)                           │
    │   • optimizer:adam                                                           │
    │   • optimizer:sgd                                                            │
    ╰──────────────────────────────────────────────────────────────────────────────╯
    
.. raw:: html
    $ python ./03_multiple_subcommands.py dataset:mnist --help
    usage: ./03_multiple_subcommands.py dataset:mnist [-h] [DATASET:MNIST OPTIONS] [{optimizer:adam,optimizer:sgd}]
    
    ╭─ options ────────────────────────────────────────────────────────────────────╮
    │ -h, --help          show this help message and exit                          │
    ╰──────────────────────────────────────────────────────────────────────────────╯
    ╭─ dataset options ────────────────────────────────────────────────────────────╮
    │ --dataset.binary, --dataset.no-binary                                        │
    │                     Set to load binary version of MNIST dataset. (default:   │
    │                     False)                                                   │
    ╰──────────────────────────────────────────────────────────────────────────────╯
    ╭─ optimizer options ──────────────────────────────────────────────────────────╮
    │ (source subcommand: optimizer:adam)                                          │
    │ ──────────────────────────────────────────────────────────────────────────── │
    │ --optimizer.learning-rate FLOAT                                              │
    │                     (default: 0.001)                                         │
    │ --optimizer.betas FLOAT FLOAT                                                │
    │                     (default: 0.9 0.999)                                     │
    ╰──────────────────────────────────────────────────────────────────────────────╯
    ╭─ subcommands ────────────────────────────────────────────────────────────────╮
    │ Optimizer to train with. (default: optimizer:adam)                           │
    │   • 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:ADAM OPTIONS]
    
    ╭─ options ─────────────────────────────────────────────────────────────────╮
    │ -h, --help  show this help message and exit                               │
    ╰───────────────────────────────────────────────────────────────────────────╯
    ╭─ dataset options ─────────────────────────────────────────────────────────╮
    │ --dataset.binary, --dataset.no-binary                                     │
    │             Set to load binary version of MNIST dataset. (default: False) │
    ╰───────────────────────────────────────────────────────────────────────────╯
    ╭─ optimizer options ───────────────────────────────────────────────────────╮
    │ --optimizer.learning-rate FLOAT                                           │
    │             (default: 0.001)                                              │
    │ --optimizer.betas FLOAT FLOAT                                             │
    │             (default: 0.9 0.999)                                          │
    ╰───────────────────────────────────────────────────────────────────────────╯
    
.. 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, inspired by `Typer `_ and `cyclopts `_. .. code-block:: python :linenos: # 04_decorator_subcommands.py from tyro.extras import SubcommandApp app = SubcommandApp() @app.command def greet(name: str, loud: bool = False) -> None: """Greet someone.""" greeting = f"Hello, {name}!" if loud: greeting = greeting.upper() print(greeting) @app.command(name="addition", aliases=["sum"]) def add(a: int, b: int) -> None: """Add two numbers.""" print(f"{a} + {b} = {a + b}") if __name__ == "__main__": app.cli() .. raw:: html
    $ python 04_decorator_subcommands.py --help
    usage: 04_decorator_subcommands.py [-h] {greet,addition}
    
    ╭─ options ─────────────────────────────────────╮
    │ -h, --help    show this help message and exit │
    ╰───────────────────────────────────────────────╯
    ╭─ subcommands ─────────────────────────────────╮
    │ (required)                                    │
    │   • greet     Greet someone.                  │
    │   • addition (sum)                            │
    │               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
    
.. raw:: html
    $ python 04_decorator_subcommands.py sum --a 5 --b 3 '#' via alias
    ╭─ Unrecognized options ────────────────────────────────────╮
    │ Unrecognized options: #, via, alias                       │
    │ ───────────────────────────────────────────────────────── │
    │ For full helptext, run 04_decorator_subcommands.py --help │
    ╰───────────────────────────────────────────────────────────╯
    
.. _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 ─────────────────────────────────╮
    │ (required)                                    │
    │   • 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'
    
.. _example-06_nested_subcommands: Nested subcommands ------------------ Unions over struct types can be nested to create hierarchical subcommand structures. By default, nested unions wrapped in ``Annotated`` are flattened into the parent union. To create a named subcommand group, annotate the nested union with :func:`tyro.conf.subcommand(name=...)`. .. code-block:: python :linenos: # 06_nested_subcommands.py from __future__ import annotations import dataclasses from typing_extensions import Annotated import tyro @dataclasses.dataclass class Checkout: """Checkout a branch.""" branch: str @dataclasses.dataclass class Commit: """Commit changes.""" message: str @dataclasses.dataclass class Push: """Push commits to a remote.""" remote: str = "origin" branch: str = "main" @dataclasses.dataclass class Pull: """Pull commits from a remote.""" remote: str = "origin" Remote = Annotated[ Push | Pull, tyro.conf.subcommand(name="remote", description="Remote operations."), ] if __name__ == "__main__": cmd = tyro.cli(Checkout | Commit | Remote) print(cmd) Show top-level subcommands: .. raw:: html
    $ python ./06_nested_subcommands.py --help
    usage: ./06_nested_subcommands.py [-h] {checkout,commit,remote}
    
    ╭─ options ─────────────────────────────────────╮
    │ -h, --help    show this help message and exit │
    ╰───────────────────────────────────────────────╯
    ╭─ subcommands ─────────────────────────────────╮
    │ (required)                                    │
    │   • checkout  Checkout a branch.              │
    │   • commit    Commit changes.                 │
    │   • remote    Remote operations.              │
    ╰───────────────────────────────────────────────╯
    
The `checkout` subcommand: .. raw:: html
    $ python ./06_nested_subcommands.py checkout --branch main
    Checkout(branch='main')
    
`remote` is a named group containing `push` and `pull`: .. raw:: html
    $ python ./06_nested_subcommands.py remote --help
    usage: ./06_nested_subcommands.py remote [-h] {push,pull}
    
    Remote operations.
    
    ╭─ options ───────────────────────────────────╮
    │ -h, --help  show this help message and exit │
    ╰─────────────────────────────────────────────╯
    ╭─ subcommands ───────────────────────────────╮
    │ (required)                                  │
    │   • push    Push commits to a remote.       │
    │   • pull    Pull commits from a remote.     │
    ╰─────────────────────────────────────────────╯
    
.. raw:: html
    $ python ./06_nested_subcommands.py remote push --remote origin --branch main
    Push(remote='origin', branch='main')
    
.. raw:: html
    $ python ./06_nested_subcommands.py remote pull --remote origin
    Pull(remote='origin')
    
.. _example-07_decorator_nested: Nested decorator-based subcommands ---------------------------------- A :class:`tyro.extras.SubcommandApp` can be registered as a subcommand on another :class:`tyro.extras.SubcommandApp`, producing a hierarchical CLI. Use ``aliases=`` for short names and ``is_default=True`` to pick which subcommand runs when none is named. .. code-block:: python :linenos: # 07_decorator_nested.py from tyro.extras import SubcommandApp # Nested app for database commands. db = SubcommandApp() @db.command(aliases=["m"]) def migrate(version: int = 1) -> None: """Apply schema migrations.""" print(f"migrating to version {version}") @db.command def seed(rows: int = 10) -> None: """Populate with seed data.""" print(f"seeding {rows} rows") # Top-level app. app = SubcommandApp() app.command(db, name="db") @app.command(is_default=True) def greet(name: str = "world") -> None: """Default action when no subcommand is given.""" print(f"hello {name}") if __name__ == "__main__": app.cli() .. raw:: html
    $ python 07_decorator_nested.py --help
    usage: 07_decorator_nested.py [-h] [{db,greet}]
    
    ╭─ options ───────────────────────────────────────────────╮
    │ -h, --help  show this help message and exit             │
    ╰─────────────────────────────────────────────────────────╯
    ╭─ subcommands ───────────────────────────────────────────╮
    │ (default: greet)                                        │
    │   • db                                                  │
    │   • greet   Default action when no subcommand is given. │
    ╰─────────────────────────────────────────────────────────╯
    
.. raw:: html
    $ python 07_decorator_nested.py db --help
    usage: 07_decorator_nested.py db [-h] {migrate,seed}
    
    ╭─ options ────────────────────────────────────╮
    │ -h, --help   show this help message and exit │
    ╰──────────────────────────────────────────────╯
    ╭─ subcommands ────────────────────────────────╮
    │ (required)                                   │
    │   • migrate (m)                              │
    │              Apply schema migrations.        │
    │   • seed     Populate with seed data.        │
    ╰──────────────────────────────────────────────╯
    
.. raw:: html
    $ python 07_decorator_nested.py db migrate --version 7
    migrating to version 7
    
.. raw:: html
    $ python 07_decorator_nested.py db seed --rows 5
    seeding 5 rows
    
.. raw:: html
    $ python 07_decorator_nested.py greet --name Alice
    hello Alice