.. 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 `_. .. 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") 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 ───────────────────────────────────────────╮
     {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'