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
 Invalid choice ─────────────────────────────────────────────────────────────╮
 invalid choice 'blue' for argument '--string'. Expected one of ('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.

Note: If a double-dash (’–’) appears in a command line, everything afterward will be considered positional.

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] PATH PATH [--verbose | --no-verbose]

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 tuple field parsed as a single argument.
20    tuple_arg: tyro.conf.UsePythonSyntaxForLiteralCollections[tuple[int, float]] = (
21        1,
22        2.0,
23    )
24
25    # A field with manually overridden properties.
26    manual: Annotated[
27        str,
28        tyro.conf.arg(
29            name="renamed",
30            metavar="STRING",
31            help="A field with manually overridden properties!",
32        ),
33    ] = "Hello"
34
35if __name__ == "__main__":
36    print(tyro.cli(Args, config=(tyro.conf.FlagConversionOff,)))
$ python ./09_conf.py --help
usage: ./09_conf.py [-h] [OPTIONS]

 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)                                       
 --tuple-arg (INT,FLOAT)  A tuple field parsed as a single argument.          
                          (default: '(1, 2.0)')                               
 --renamed STRING         A field with manually overridden properties!        
                          (default: Hello)                                    
──────────────────────────────────────────────────────────────────────────────
$ python ./09_conf.py 5 --boolean True
Args(positional=5, boolean=True, fixed=5, tuple_arg=(1, 2.0), 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] -b STR

Check out a branch.

 options ─────────────────────────────────────────────╮
 -h, --help            show this help message and exit 
 -b STR, --branch 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, config=(tyro.conf.CascadeSubcommandArgs,))
$ python ./12_counters.py --help
usage: ./12_counters.py [-h] [--verbosity] [-v]

Example showing how to use counter actions.

 options ────────────────────────────────────────────────────────────────────╮
 -h, --help               show this help message and exit                     
 --verbosity              Verbosity level. (repeatable)                       
 -v, --aliased-verbosity  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]

Mutually Exclusive Groups

tyro.conf.create_mutex_group() can be used to create mutually exclusive argument groups, where either exactly one (required=True) or at most one (required=False) argument from the group can be specified.

The title parameter can be used to customize the group title in the help text.

 1# 14_mutex.py
 2from pathlib import Path
 3from typing import Annotated, Literal
 4
 5import tyro
 6
 7RequiredGroup = tyro.conf.create_mutex_group(required=True, title="output target")
 8OptionalGroup = tyro.conf.create_mutex_group(required=False, title="verbosity level")
 9
10def main(
11    # Exactly one of --target-stream or --target-file must be specified.
12    target_stream: Annotated[Literal["stdout", "stderr"] | None, RequiredGroup] = None,
13    target_file: Annotated[Path | None, RequiredGroup] = None,
14    # Either --verbose or --very-verbose can be specified (but not both).
15    verbose: Annotated[bool, OptionalGroup] = False,
16    very_verbose: Annotated[bool, OptionalGroup] = False,
17) -> None:
18    """Demonstrate mutually exclusive argument groups."""
19    if very_verbose or verbose:
20        print(f"{target_stream=} {target_file=}")
21    if very_verbose:
22        print(f"{target_stream=} {target_file=}")
23
24if __name__ == "__main__":
25    tyro.cli(
26        main,
27        # `DisallowNone` prevents `None` from being a valid choice for
28        # `--target-stream` and `--target-file`.
29        #
30        # `FlagCreatePairsOff` prevents `--no-verbose` and `--no-very-verbose` from
31        # being created.
32        config=(tyro.conf.DisallowNone, tyro.conf.FlagCreatePairsOff),
33    )
$ python ./14_mutex.py
 Required mutex group ──────────────────────╮
 Missing required argument group:            
   • --target-stream, --target-file          
 ─────────────────────────────────────────── 
 For full helptext, run ./14_mutex.py --help 
─────────────────────────────────────────────
$ python ./14_mutex.py --help
usage: ./14_mutex.py [-h] [OPTIONS]

Demonstrate mutually exclusive argument groups.

 options ──────────────────────────────────────────╮
 -h, --help      show this help message and exit    
────────────────────────────────────────────────────
 output target ────────────────────────────────────╮
 Exactly one argument must be passed in. (required) 
 ────────────────────────────────────────────────── 
 --target-stream {stdout,stderr}                    
                 (default: None)                    
 --target-file PATH                                 
                 (default: None)                    
────────────────────────────────────────────────────
 verbosity level ──────────────────────────────────╮
 At most one argument can be overridden.            
 ────────────────────────────────────────────────── 
 --verbose       (default: False)                   
 --very-verbose  (default: False)                   
────────────────────────────────────────────────────
$ python ./14_mutex.py --target-stream stdout
$ python ./14_mutex.py --target-file /tmp/output.txt
$ python ./14_mutex.py --target-stream stdout --verbose
target_stream='stdout' target_file=None
$ python ./14_mutex.py --target-file /tmp/output.txt --very-verbose
target_stream=None target_file=PosixPath('/tmp/output.txt')
target_stream=None target_file=PosixPath('/tmp/output.txt')
$ python ./14_mutex.py --target-stream stdout --target-file /tmp/output.txt
 Mutually exclusive arguments ────────────────────────────────────────╮
 Arguments --target-stream and --target-file are not allowed together! 
 ───────────────────────────────────────────────────────────────────── 
 For full helptext, run ./14_mutex.py --help                           
───────────────────────────────────────────────────────────────────────
$ python ./14_mutex.py --target-stream stdout --verbose --very-verbose
 Mutually exclusive arguments ───────────────────────────────────╮
 Arguments --verbose and --very-verbose are not allowed together! 
 ──────────────────────────────────────────────────────────────── 
 For full helptext, run ./14_mutex.py --help                      
──────────────────────────────────────────────────────────────────