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 │ ╰──────────────────────────────────────────────────────────────────╯