Basics

In these examples, we show basic examples of using tyro.cli(): functions, dataclasses, supported annotations, and configuration.

Functions

In the simplest case, tyro.cli() can be used to run a function with arguments populated from the CLI.

 1# 01_functions.py
 2import tyro
 3
 4def main(field1: str, field2: int = 3) -> None:
 5    """Function, whose arguments will be populated from a CLI interface.
 6
 7    Args:
 8        field1: A string field.
 9        field2: A numeric field, with a default value.
10    """
11    print(field1, field2)
12
13if __name__ == "__main__":
14    tyro.cli(main)

We can use --help to show the help message, or --field1 and --field2 to set the arguments:

$ python ./01_functions.py --help
usage: 01_functions.py [-h] --field1 STR [--field2 INT]

Function, whose arguments will be populated from a CLI interface.

╭─ options ───────────────────────────────────────────────────────────────╮
 -h, --help          show this help message and exit                     
 --field1 STR        A string field. (required)                          
 --field2 INT        A numeric field, with a default value. (default: 3) 
╰─────────────────────────────────────────────────────────────────────────╯
$ python ./01_functions.py --field1 hello
hello 3
$ python ./01_functions.py --field1 hello --field2 10
hello 10

Dataclasses

In addition to functions, tyro.cli() can also take dataclasses as input.

 1# 02_dataclasses.py
 2from dataclasses import dataclass
 3from pprint import pprint
 4
 5import tyro
 6
 7@dataclass
 8class Args:
 9    """Description.
10    This should show up in the helptext!"""
11
12    field1: str
13    """A string field."""
14
15    field2: int = 3
16    """A numeric field, with a default value."""
17
18if __name__ == "__main__":
19    args = tyro.cli(Args)
20    pprint(args)

To show the help message, we can use the --help flag:

$ python ./02_dataclasses.py --help
usage: 02_dataclasses.py [-h] --field1 STR [--field2 INT]

Description. This should show up in the helptext!

╭─ options ───────────────────────────────────────────────────────────────╮
 -h, --help          show this help message and exit                     
 --field1 STR        A string field. (required)                          
 --field2 INT        A numeric field, with a default value. (default: 3) 
╰─────────────────────────────────────────────────────────────────────────╯

We can override field1 and field2:

$ python ./02_dataclasses.py --field1 hello
Args(field1='hello', field2=3)
$ python ./02_dataclasses.py --field1 hello --field2 5
Args(field1='hello', field2=5)

Multi-value Arguments

Arguments of both fixed and variable lengths can be annotated with standard Python collection types. For Python 3.7 and 3.8, we can use either from __future__ import annotations to support list[T] and tuple[T], or the older typing.List and typing.Tuple.

 1# 03_multivalue.py
 2import pathlib
 3from dataclasses import dataclass
 4from pprint import pprint
 5
 6import tyro
 7
 8@dataclass
 9class Config:
10    # Example of a variable-length tuple. `list[T]`, `set[T]`,
11    # `dict[K, V]`, etc are supported as well.
12    source_paths: tuple[pathlib.Path, ...]
13    """This can be multiple!"""
14
15    # Fixed-length tuples are also okay.
16    dimensions: tuple[int, int] = (32, 32)
17    """Height and width."""
18
19if __name__ == "__main__":
20    config = tyro.cli(Config)
21    pprint(config)

To print help:

$ python ./03_multivalue.py --help
usage: 03_multivalue.py [-h] --source-paths [PATH
                        [PATH ...]] [--dimensions INT INT]

╭─ options ──────────────────────────────────────────────────╮
 -h, --help              show this help message and exit    
 --source-paths [PATH [PATH ...]]                           
                         This can be multiple! (required)   
 --dimensions INT INT    Height and width. (default: 32 32) 
╰────────────────────────────────────────────────────────────╯

We can override arguments:

$ python ./03_multivalue.py --source-paths ./data --dimensions 16 16
Config(source_paths=(PosixPath('data'),), dimensions=(16, 16))
$ python ./03_multivalue.py --source-paths ./data1 ./data2
Config(source_paths=(PosixPath('data1'), PosixPath('data2')),
       dimensions=(32, 32))

Booleans and Flags

Booleans can either be expected to be explicitly passed in, or, if given a default value, automatically converted to flags.

To turn off conversion, see tyro.conf.FlagConversionOff.

 1# 04_flags.py
 2from dataclasses import dataclass
 3from pprint import pprint
 4
 5import tyro
 6
 7@dataclass
 8class Args:
 9    # Boolean. This expects an explicit "True" or "False".
10    boolean: bool
11
12    # Optional boolean. Same as above, but can be omitted.
13    optional_boolean: bool | None = None
14
15    # Pass --flag-a in to set this value to True.
16    flag_a: bool = False
17
18    # Pass --no-flag-b in to set this value to False.
19    flag_b: bool = True
20
21if __name__ == "__main__":
22    args = tyro.cli(Args)
23    pprint(args)
$ python ./04_flags.py --help
usage: 04_flags.py [-h] [OPTIONS]

╭─ options ────────────────────────────────────────────────────────────────╮
 -h, --help                                                               
     show this help message and exit                                      
 --boolean {True,False}                                                   
     Boolean. This expects an explicit "True" or "False". (required)      
 --optional-boolean {None,True,False}                                     
     Optional boolean. Same as above, but can be omitted. (default: None) 
 --flag-a, --no-flag-a                                                    
     Pass --flag-a in to set this value to True. (default: False)         
 --flag-b, --no-flag-b                                                    
     Pass --no-flag-b in to set this value to False. (default: True)      
╰──────────────────────────────────────────────────────────────────────────╯
$ python ./04_flags.py --boolean True
Args(boolean=True, optional_boolean=None, flag_a=False, flag_b=True)
$ python ./04_flags.py --boolean False --flag-a
Args(boolean=False, optional_boolean=None, flag_a=True, flag_b=True)
$ python ./04_flags.py --boolean False --no-flag-b
Args(boolean=False, optional_boolean=None, flag_a=False, flag_b=False)

Choices

typing.Literal[] can be used to restrict inputs to a fixed set of literal choices.

 1# 05_choices.py
 2import dataclasses
 3from pprint import pprint
 4from typing import Literal
 5
 6import tyro
 7
 8@dataclasses.dataclass
 9class Args:
10    # We can use Literal[] to restrict the set of allowable inputs, for example, over
11    # a set of strings.
12    string: Literal["red", "green"] = "red"
13
14    # Integers also work. (as well as booleans, enums, etc)
15    number: Literal[0, 1, 2] = 0
16
17if __name__ == "__main__":
18    args = tyro.cli(Args)
19    pprint(args)
$ python ./05_choices.py --help
usage: 05_choices.py [-h] [--string {red,green}] [--number {0,1,2}]

╭─ options ──────────────────────────────────────────────────────────────────╮
 -h, --help              show this help message and exit                    
 --string {red,green}    We can use Literal[] to restrict the set of        
                         allowable inputs, for example, over a set of       
                         strings. (default: red)                            
 --number {0,1,2}        Integers also work. (as well as booleans, enums,   
                         etc) (default: 0)                                  
╰────────────────────────────────────────────────────────────────────────────╯
$ python ./05_choices.py --string red
Args(string='red', number=0)
$ python ./05_choices.py --string blue
╭─ Parsing error ────────────────────────────────────────────────────────╮
 Argument --string: invalid choice: 'blue' (choose from 'red', 'green') 
 ────────────────────────────────────────────────────────────────────── 
 For full helptext, run 05_choices.py --help                            
╰────────────────────────────────────────────────────────────────────────╯

Enums

In addition to literals, enums can also be used to provide a fixed set of choices.

 1# 06_enums.py
 2import enum
 3from dataclasses import dataclass
 4from pprint import pprint
 5
 6import tyro
 7
 8class Color(enum.Enum):
 9    RED = enum.auto()
10    BLUE = enum.auto()
11
12@dataclass
13class Config:
14    color: Color = Color.RED
15    """Color argument."""
16
17    opacity: float = 0.5
18    """Opacity argument."""
19
20if __name__ == "__main__":
21    config = tyro.cli(Config)
22    pprint(config)
$ python ./06_enums.py --help
usage: 06_enums.py [-h] [--color {RED,BLUE}] [--opacity FLOAT]

╭─ options ────────────────────────────────────────────────╮
 -h, --help              show this help message and exit  
 --color {RED,BLUE}      Color argument. (default: RED)   
 --opacity FLOAT         Opacity argument. (default: 0.5) 
╰──────────────────────────────────────────────────────────╯
$ python ./06_enums.py --color RED
Config(color=<Color.RED: 1>, opacity=0.5)
$ python ./06_enums.py --color BLUE --opacity 0.75
Config(color=<Color.BLUE: 2>, opacity=0.75)

Unions

X | Y or typing.Union can be used to expand inputs to multiple types.

 1# 07_unions.py
 2import dataclasses
 3import enum
 4from pprint import pprint
 5from typing import Literal, Optional
 6
 7import tyro
 8
 9class Color(enum.Enum):
10    RED = enum.auto()
11    GREEN = enum.auto()
12    BLUE = enum.auto()
13
14@dataclasses.dataclass(frozen=True)
15class Args:
16    # Unions can be used to specify multiple allowable types.
17    union_over_types: int | str = 0
18    string_or_enum: Literal["red", "green"] | Color = "red"
19
20    # Unions also work over more complex nested types.
21    union_over_tuples: tuple[int, int] | tuple[str] = ("1",)
22
23    # And can be nested in other types.
24    tuple_of_string_or_enum: tuple[Literal["red", "green"] | Color, ...] = (
25        "red",
26        Color.RED,
27    )
28
29    # Optional[T] is equivalent to `T | None`.
30    integer: Optional[Literal[0, 1, 2, 3]] = None
31
32if __name__ == "__main__":
33    args = tyro.cli(Args)
34    pprint(args)
$ python ./07_unions.py --help
usage: 07_unions.py [-h] [OPTIONS]

╭─ options ──────────────────────────────────────────────────────────────────╮
 -h, --help                                                                 
     show this help message and exit                                        
 --union-over-types INT|STR                                                 
     Unions can be used to specify multiple allowable types. (default: 0)   
 --string-or-enum {red,green,RED,GREEN,BLUE}                                
     Unions can be used to specify multiple allowable types. (default: red) 
 --union-over-tuples {INT INT}|STR                                          
     Unions also work over more complex nested types. (default: 1)          
 --tuple-of-string-or-enum [{red,green,RED,GREEN,BLUE}                      
 [{red,green,RED,GREEN,BLUE} ...]]                                          
     And can be nested in other types. (default: red RED)                   
 --integer {None,0,1,2,3}                                                   
     Optional[T] is equivalent to `T | None`. (default: None)               
╰────────────────────────────────────────────────────────────────────────────╯
$ python ./07_unions.py --union-over-types 3
Args(union_over_types=3,
     string_or_enum='red',
     union_over_tuples=('1',),
     tuple_of_string_or_enum=('red', <Color.RED: 1>),
     integer=None)
$ python ./07_unions.py --union-over-types three
Args(union_over_types='three',
     string_or_enum='red',
     union_over_tuples=('1',),
     tuple_of_string_or_enum=('red', <Color.RED: 1>),
     integer=None)
$ python ./07_unions.py --integer None
Args(union_over_types=0,
     string_or_enum='red',
     union_over_tuples=('1',),
     tuple_of_string_or_enum=('red', <Color.RED: 1>),
     integer=None)
$ python ./07_unions.py --integer 0
Args(union_over_types=0,
     string_or_enum='red',
     union_over_tuples=('1',),
     tuple_of_string_or_enum=('red', <Color.RED: 1>),
     integer=0)

Positional Arguments

Positional-only arguments in functions are converted to positional CLI arguments.

For more general positional arguments, see tyro.conf.Positional.

 1# 08_positional.py
 2from __future__ import annotations
 3
 4import pathlib
 5
 6import tyro
 7
 8def main(
 9    source: pathlib.Path,
10    dest: pathlib.Path,
11    /,  # Mark the end of positional arguments.
12    verbose: bool = False,
13) -> None:
14    """Command-line interface defined using a function signature. This
15    docstring is parsed to generate helptext.
16
17    Args:
18        source: Source path.
19        dest: Destination path.
20        verbose: Explain what is being done.
21    """
22    print(f"{source=}\n{dest=}\n{verbose=}")
23
24if __name__ == "__main__":
25    tyro.cli(main)
$ python 08_positional.py --help
usage: 08_positional.py [-h] [--verbose | --no-verbose] PATH PATH

Command-line interface defined using a function signature. This docstring is
parsed to generate helptext.

╭─ positional arguments ─────────────────────────────────────────╮
 PATH              Source path. (required)                      
 PATH              Destination path. (required)                 
╰────────────────────────────────────────────────────────────────╯
╭─ options ──────────────────────────────────────────────────────╮
 -h, --help        show this help message and exit              
 --verbose, --no-verbose                                        
                   Explain what is being done. (default: False) 
╰────────────────────────────────────────────────────────────────╯
$ python 08_positional.py ./a ./b
source=PosixPath('a')
dest=PosixPath('b')
verbose=False
$ python 08_positional.py ./test1 ./test2 --verbose
source=PosixPath('test1')
dest=PosixPath('test2')
verbose=True

Configuration via typing.Annotated[]

The tyro.conf module contains utilities that can be used in conjunction with typing.Annotated to configure command-line interfaces beyond what is expressible via static type annotations.

Features here are supported, but generally unnecessary and should be used sparingly.

 1# 09_conf.py
 2import dataclasses
 3
 4from typing_extensions import Annotated
 5
 6import tyro
 7
 8@dataclasses.dataclass
 9class Args:
10    # A numeric field parsed as a positional argument.
11    positional: tyro.conf.Positional[int]
12
13    # A boolean field with flag conversion turned off.
14    boolean: tyro.conf.FlagConversionOff[bool] = False
15
16    # A numeric field that can't be changed via the CLI.
17    fixed: tyro.conf.Fixed[int] = 5
18
19    # A field with manually overridden properties.
20    manual: Annotated[
21        str,
22        tyro.conf.arg(
23            name="renamed",
24            metavar="STRING",
25            help="A field with manually overridden properties!",
26        ),
27    ] = "Hello"
28
29if __name__ == "__main__":
30    print(tyro.cli(Args))
$ python ./09_conf.py --help
usage: 09_conf.py [-h] [OPTIONS] INT

╭─ positional arguments ─────────────────────────────────────────────────────╮
 INT                     A numeric field parsed as a positional argument.   
                         (required)                                         
╰────────────────────────────────────────────────────────────────────────────╯
╭─ options ──────────────────────────────────────────────────────────────────╮
 -h, --help              show this help message and exit                    
 --boolean {True,False}  A boolean field with flag conversion turned off.   
                         (default: False)                                   
 --fixed {fixed}         A numeric field that can't be changed via the CLI. 
                         (fixed to: 5)                                      
 --renamed STRING        A field with manually overridden properties!       
                         (default: Hello)                                   
╰────────────────────────────────────────────────────────────────────────────╯
$ python ./09_conf.py 5 --boolean True
Args(positional=5, boolean=True, fixed=5, manual='Hello')

Argument Aliases

tyro.conf.arg() can be used to attach aliases to arguments.

 1# 10_aliases.py
 2from typing import Annotated
 3
 4import tyro
 5
 6def checkout(
 7    branch: Annotated[str, tyro.conf.arg(aliases=["-b"])],
 8) -> None:
 9    """Check out a branch."""
10    print(f"{branch=}")
11
12if __name__ == "__main__":
13    tyro.cli(checkout)
$ python ./10_aliases.py --help
usage: 10_aliases.py [-h] --branch STR

Check out a branch.

╭─ options ───────────────────────────────────────────────╮
 -h, --help              show this help message and exit 
 --branch STR, -b STR    (required)                      
╰─────────────────────────────────────────────────────────╯
$ python ./10_aliases.py --branch main
branch='main'
$ python ./10_aliases.py -b main
branch='main'

Type Aliases (3.12+)

In Python 3.12, the type statement is introduced to create type aliases.

 1# 11_type_aliases_py312.py
 2import dataclasses
 3
 4import tyro
 5
 6# Lazily-evaluated type alias.
 7type Field1Type = Inner
 8
 9@dataclasses.dataclass
10class Inner:
11    a: int
12    b: str
13
14@dataclasses.dataclass
15class Args:
16    """Description.
17    This should show up in the helptext!"""
18
19    field1: Field1Type
20    """A field."""
21
22    field2: int = 3
23    """A numeric field, with a default value."""
24
25if __name__ == "__main__":
26    args = tyro.cli(Args)
27    print(args)
$ python ./11_type_aliases_py312.py --help
usage: 11_type_aliases_py312.py [-h] [--field2 INT] --field1.a INT --field1.b
                                STR

Description. This should show up in the helptext!

╭─ options ─────────────────────────────────────────────────────────────────╮
 -h, --help            show this help message and exit                     
 --field2 INT          A numeric field, with a default value. (default: 3) 
╰───────────────────────────────────────────────────────────────────────────╯
╭─ field1 options ──────────────────────────────────────────────────────────╮
 A field.                                                                  
 ────────────────────────────────                                          
 --field1.a INT        (required)                                          
 --field1.b STR        (required)                                          
╰───────────────────────────────────────────────────────────────────────────╯

Counters

Repeatable ‘counter’ arguments can be specified via tyro.conf.UseCounterAction.

 1# 12_counters.py
 2from typing_extensions import Annotated
 3
 4import tyro
 5from tyro.conf import UseCounterAction
 6
 7def main(
 8    verbosity: UseCounterAction[int],
 9    aliased_verbosity: Annotated[UseCounterAction[int], tyro.conf.arg(aliases=["-v"])],
10) -> None:
11    """Example showing how to use counter actions.
12
13    Args:
14        verbosity: Verbosity level.
15        aliased_verbosity: Same as above, but can also be specified with -v, -vv, -vvv, etc.
16    """
17    print("Verbosity level:", verbosity)
18    print("Verbosity level (aliased):", aliased_verbosity)
19
20if __name__ == "__main__":
21    tyro.cli(main)
$ python ./12_counters.py --help
usage: 12_counters.py [-h] [--verbosity] [--aliased-verbosity]

Example showing how to use counter actions.

╭─ options ──────────────────────────────────────────────────────────────────╮
 -h, --help         show this help message and exit                         
 --verbosity        Verbosity level. (repeatable)                           
 --aliased-verbosity, -v                                                    
                    Same as above, but can also be specified with -v, -vv,  
                    -vvv, etc. (repeatable)                                 
╰────────────────────────────────────────────────────────────────────────────╯
$ python ./12_counters.py --verbosity
Verbosity level: 1
Verbosity level (aliased): 0
$ python ./12_counters.py --verbosity --verbosity
Verbosity level: 2
Verbosity level (aliased): 0
$ python ./12_counters.py -vvv
Verbosity level: 0
Verbosity level (aliased): 3

Instantiating Classes

In addition to functions and dataclasses, we can also generate CLIs from the constructors of standard Python classes.

 1# 13_classes.py
 2import tyro
 3
 4class Args:
 5    def __init__(
 6        self,
 7        field1: str,
 8        field2: int,
 9        flag: bool = False,
10    ):
11        """Arguments.
12
13        Args:
14            field1: A string field.
15            field2: A numeric field.
16            flag: A boolean flag.
17        """
18        self.data = [field1, field2, flag]
19
20if __name__ == "__main__":
21    args = tyro.cli(Args)
22    print(args.data)
$ python ./13_classes.py --help
usage: 13_classes.py [-h] --field1 STR --field2 INT [--flag | --no-flag]

Arguments.

╭─ options ────────────────────────────────────────────────╮
 -h, --help              show this help message and exit  
 --field1 STR            A string field. (required)       
 --field2 INT            A numeric field. (required)      
 --flag, --no-flag       A boolean flag. (default: False) 
╰──────────────────────────────────────────────────────────╯
$ python ./13_classes.py --field1 hello --field2 7
['hello', 7, False]