CLI tool
brentyi/tyro avatar
brentyi/tyro

tyro: an argument parser that reads your type annotations

CLI interfaces & config objects, from types

1,134 stars52 forksPythonMIT

At a glance

What is it?
tyro builds a command line interface from type-annotated Python functions and from dataclasses, pydantic or attrs configs, parsing annotations, docstrings and defaults into helptext and shell completion. It suits research code and throwaway scripts that still have to look typed, and it does not replace argparse for every project.
Who is it for?
Adopt tyro when your CLI surface is already described by Python types, which is the normal case for research tools, internal scripts and anything that wants dataclasses, pydantic models or attrs classes to double as the configuration file. Do not adopt it for a tool that must expose a hand-tuned parser with custom actions, mutually exclusive groups or unusual argument syntax, since that is argparse territory and tyro will not match it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 61 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The parser reads the type, so you stop writing the parser

argparse asks you to declare everything twice. You write a function signature and then you write an add_argument call for each parameter, with a type string, a default, a help string, and a dest name that has to match. tyro removes the second half. Its core entry point, tyro.cli(), turns a type-annotated function into a command line interface, or turns an instance of a config object into one. The project's own framing is that a configurable script can be defined using functions, or by instantiating configs defined with dataclasses, pydantic or attrs. The mechanism is a reflection pass over the annotation: the annotation supplies the type, the docstring supplies the description, and the default supplies the fallback. The result is a parser whose behaviour is defined in one place, the type, rather than in two places that can drift apart. This is a library for people who are already writing typed Python and find argparse to be the one part of the stack that refuses to be typed.

Helptext comes from the docstrings you already wrote

The feature list names helptext generation alongside nested structures, subcommands and shell completion, and the project states that standard type annotations, docstrings and default values are parsed to automatically generate command line interfaces with nice helptext. The dependency list supports the mechanism rather than merely asserting it. docstring-parser is a runtime dependency, so docstrings are read and split at parse time, and typeguard is a runtime dependency, so the values arriving from the command line are checked against the annotation they came from rather than coerced blindly. typing-extensions is there for annotation shapes the running interpreter does not know, and eval_type_backport is installed only on Python versions below 3.10 to make postponed evaluation of annotations work. The consequence for a reader is that the --help text and the type errors share a single source of truth: the signature. When you change a parameter's type, both the help output and the validation follow.

Static types survive the trip through the command line

The second reason the project gives is static typing, and it is the argument that matters most for a typed codebase. Unlike tools that depend on dictionaries, YAML files or dynamic namespaces, the arguments populated by tyro are better understood by IDEs and language servers, and by static checkers such as pyright and mypy. The mechanism is that the config object is a real annotated Python object, so a call site that reads cfg.learning_rate gets a float type from the checker rather than an Any pulled out of a dict. The project uses this framing to position itself against a config-file approach in general, and the dev dependency list shows the scope of the comparison work: pydantic, attrs, omegaconf, msgspec, ml_collections and PyYAML are all present as dev extras, alongside pyright and mypy. That the library is tested against that many configuration systems is a reasonable signal that it is meant to sit on top of a config model you already have rather than replace it with a proprietary one.

Nested configs and subcommands for projects that outgrow one file

The project describes itself as lightweight for throwaway scripts while improving maintainability for larger projects, and the feature that bridges the two is hierarchy. tyro supports hierarchical configurations, which the documentation says make it easy to decentralize definitions, defaults and documentation. That means a config can be assembled from several dataclasses, each owned by a different module, instead of one growing file, and the generated interface reflects the nesting as dotted or nested flags. Subcommands are supported as well, which is how a tool exposes a mode selector without writing a dispatch layer. The examples directory in the repository mirrors that progression with numbered directories for basics, hierarchical structures, subcommands, overriding configs, generics and custom constructors, plus a separate directory for PyTorch and JAX, and there is an _rename_example.py that suggests the CLI name is configurable rather than fixed to the script filename. The generation of a shell completion script is another listed feature, and shtab appears in the dev dependencies, which is the usual way that is implemented.

Installing it is a single pip command

The installation instruction in the documentation is short, and the package is on PyPI under the name tyro:

bash
pip install tyro

The package metadata in pyproject.toml sets the build backend to hatchling, reads the version from src/tyro/__init__.py, and declares requires-python of >=3.8, with classifiers covering Python 3.8 through 3.14. The runtime dependencies are deliberately few: typeguard, docstring-parser and typing-extensions, plus eval_type_backport on interpreters older than 3.10. There is no compiled extension and no platform-specific code, and the classifier for operating system is OS Independent, so the same install works on Linux, macOS and Windows. If you want the extras, the dev group adds pytest, pytest-cov, pytest-xdist, mypy, pyright and the config libraries listed above, and a separate dev-nn group adds torch, numpy and flax for the neural network examples. The licence is MIT, and the project documentation lives at brentyi.github.io/tyro.

Where the reflection approach stops working

tyro's design has a specific set of failure cases, and they are worth naming before you migrate a parser. The first is a parameter whose command line representation is not a simple function of its annotation. argparse supports custom actions, type callables with side effects, mutually exclusive groups and positionals with intricate nargs. Reflection cannot invent those, so a tool that relies on them will fight the library. The second is the environment coupling. A config object is a Python object, so a field whose value depends on the environment, the filesystem or the current time is evaluated once at import, when the object is constructed, and the command line can only override what it can express as a value. The third is that the type is the source of truth, which means an annotation that is broader than the accepted input is a problem: typeguard will reject a value the old parser would have quietly coerced, and the error surfaces at runtime rather than in a schema review. The fourth is the floor on Python versions. A project that has to support an interpreter older than 3.8 cannot use the library at all.

How this differs from argparse with a config file

The alternative pattern most Python CLIs use is argparse plus a separate config layer, typically a dataclass or a pydantic model loaded from YAML or JSON, with the parser and the file each carrying part of the definition. The difference in approach is where the defaults and the documentation live. In the argparse pattern, the parser is a second description of the program that has to be kept in sync with the config class, and the helptext is written as prose in the add_argument call rather than inherited from the docstring the class already carries. tyro collapses those into one description: the class, its annotations and its docstrings. The trade-off runs the other way as well. The argparse pattern can accept input that does not match the config class at all, since the file is read and merged by hand, which gives more control over the exact merge and validation semantics, at the cost of writing that logic. tyro is the better fit when the configuration is already a typed object and the CLI is a view onto it. It is the worse fit when the parser, not the object, is the source of truth for the program's interface.

Editorial conclusion

Adopt tyro when your CLI surface is already described by Python types, which is the normal case for research tools, internal scripts and anything that wants dataclasses, pydantic models or attrs classes to double as the configuration file. Do not adopt it for a tool that must expose a hand-tuned parser with custom actions, mutually exclusive groups or unusual argument syntax, since that is argparse territory and tyro will not match it. Before you commit, check the Python version floor of 3.8 stated in pyproject.toml against your runtime, confirm the config library you already use is covered, and note that the last push was on 2026-08-23 with the current release at v1.0.16.

Frequently asked questions

What is tyro and how is it installed?

tyro is a tool for generating CLI interfaces from type-annotated Python, and it is installed with `pip install tyro`. It requires Python 3.8 or newer and depends on typeguard, docstring-parser and typing-extensions.

Can tyro generate a CLI from a dataclass or pydantic model?

Yes. The documentation says you can instantiate configs defined with tools like dataclasses, pydantic and attrs, and the dev dependencies include pydantic, attrs, omegaconf, msgspec and ml_collections for testing against those systems.

Does tyro work with static type checkers like pyright and mypy?

That is one of the stated reasons to use it. Because arguments are populated into a typed object rather than a dictionary or dynamic namespace, IDEs, language servers and checkers such as pyright and mypy can understand them.

Does tyro support subcommands and shell completion?

Yes. The feature list includes nested structures, subcommands and shell completion, and hierarchical configurations are described as a way to decentralize definitions, defaults and documentation across a larger project.

What licence is tyro released under?

The project is licensed under the MIT License, and pyproject.toml declares the licence text as MIT. The current release is v1.0.16 and the last push to the repository was on 2026-08-23.

Official sources

  1. brentyi/tyro on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/brentyi-tyro.svg)](https://hysenlabs.com/projects/brentyi-tyro)

Was this useful?