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
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. To apply options globally, these same flags can also be passed via the config argument of tyro.cli().

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.
14    boolean: 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, config=(tyro.conf.FlagConversionOff,)))
$ 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. (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]