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]