Skip to content

Commit

Permalink
Less restricted type expansion, more robust subcommand matching, impr…
Browse files Browse the repository at this point in the history
…oved error messages (#204)

* Improve mismatched type for StrEnum

* Overhaul subcommand matching

* Additional subcommand matching tests

* Min 3.11 for StrEnum tests

* fix test ignore

* Test nits

* typeguard compatibility

* Add minimum typeguard version

* fix typeguard errors

* typeguard refactor, more subcommand matching tests

* Add 3.13 to CI (fixes #206)

* Add pydantic version exclude, waiting for patch
Related: pydantic/pydantic#10912, pydantic/pydantic#10909

* Improve type warnings + errors

* Generic subcommand example, docs updates

* Remove unused

* `build` => `pytest`, closes #206

* mypy
  • Loading branch information
brentyi authored Nov 23, 2024
1 parent 9c5dd26 commit ae0ab2c
Show file tree
Hide file tree
Showing 36 changed files with 875 additions and 262 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml → .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: build
name: pytest

on:
push:
Expand All @@ -7,11 +7,11 @@ on:
branches: [main]

jobs:
build:
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 2 additions & 2 deletions docs/source/examples/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ To turn off conversion, see :class:`tyro.conf.FlagConversionOff`.
Choices
-------

:code:`typing.Literal[]` can be used to restrict inputs to a fixed set of literal choices.
:py:data:`typing.Literal[]` can be used to restrict inputs to a fixed set of literal choices.


.. code-block:: python
Expand Down Expand Up @@ -443,7 +443,7 @@ choices.
Unions
------

:code:`X | Y` or :code:`typing.Union[X, Y]` can be used to expand inputs to
:code:`X | Y` or :py:data:`typing.Union` can be used to expand inputs to
multiple types.


Expand Down
9 changes: 5 additions & 4 deletions docs/source/examples/custom_constructors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ Simple Constructors
-------------------

For simple custom constructors, we can pass a constructor function into
:func:`tyro.conf.arg` or :func:`tyro.conf.subcommand`. Arguments for will be
:func:`tyro.conf.arg` or :func:`tyro.conf.subcommand`. Arguments will be
generated by parsing the signature of the constructor function.

In this example, we use this pattern to define custom behavior for
instantiating a NumPy array.
In this example, we define custom behavior for instantiating a NumPy array.


.. code-block:: python
Expand Down Expand Up @@ -288,7 +287,9 @@ Custom Structs (Registry)
-------------------------

In this example, we use a :class:`tyro.constructors.ConstructorRegistry` to
add support for a custom type.
add support for a custom struct type. Each struct type is broken down into
multiple fields, which themselves can be either primitive types or nested
structs.

.. warning::

Expand Down
116 changes: 114 additions & 2 deletions docs/source/examples/generics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ This example uses syntax introduced in Python 3.12 (`PEP 695 <https://peps.pytho
</pre>
.. _example-02_generics:

Generics (Legacy)
-----------------
Generics (Python <3.12)
-----------------------

The legacy :py:class:`typing.Generic` and :py:class:`typing.TypeVar` syntax for
generic types is also supported.
Expand Down Expand Up @@ -154,4 +154,116 @@ generic types is also supported.
<span style="font-weight: lighter">│</span> --shape.c.z <span style="font-weight: bold">FLOAT</span> <span style="font-weight: bold; color: #e60000">(required)</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">│</span> --shape.c.frame-id <span style="font-weight: bold">STR</span> <span style="font-weight: bold; color: #e60000">(required)</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">╰─────────────────────────────────────────────────────────╯</span>
</pre>
.. _example-03_generic_subcommands:

Generic Subcommands
-------------------




.. code-block:: python
:linenos:
# 03_generic_subcommands.py
import dataclasses
from pathlib import Path
import tyro
@dataclasses.dataclass
class Sgd:
lr: float = 1e-4
@dataclasses.dataclass
class Adam:
lr: float = 3e-4
betas: tuple[float, float] = (0.9, 0.999)
@dataclasses.dataclass(frozen=True)
class Experiment[OptimizerT: (Adam, Sgd)]:
path: Path
opt: OptimizerT
if __name__ == "__main__":
args = tyro.cli(Experiment[Adam] | Experiment[Sgd])
print(args)
.. raw:: html

<pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
<strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./03_generic_subcommands.py --help</strong>
<span style="font-weight: bold">usage</span>: 03_generic_subcommands.py [-h] <span style="font-weight: bold">{experiment-adam,experiment-sgd}</span>

<span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> options </span><span style="font-weight: lighter">────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
<span style="font-weight: lighter">│</span> -h, --help <span style="font-weight: lighter">show this help message and exit</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">╰───────────────────────────────────────────────────╯</span>
<span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> subcommands </span><span style="font-weight: lighter">────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
<span style="font-weight: lighter">│</span> {experiment-adam,experiment-sgd} <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">│</span> experiment-adam <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">│</span> experiment-sgd <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">╰───────────────────────────────────────────────────╯</span>
</pre>



.. raw:: html

<pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
<strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./03_generic_subcommands.py experiment-adam --help</strong>
<span style="font-weight: bold">usage</span>: 03_generic_subcommands.py experiment-adam [-h] --path <span style="font-weight: bold">PATH</span>
[--opt.lr <span style="font-weight: bold">FLOAT</span>]
[--opt.betas <span style="font-weight: bold">FLOAT FLOAT</span>]

<span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> options </span><span style="font-weight: lighter">────────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
<span style="font-weight: lighter">│</span> -h, --help <span style="font-weight: lighter">show this help message and exit</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">│</span> --path <span style="font-weight: bold">PATH</span> <span style="font-weight: bold; color: #e60000">(required)</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">╰───────────────────────────────────────────────────────╯</span>
<span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> opt options </span><span style="font-weight: lighter">────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
<span style="font-weight: lighter">│</span> --opt.lr <span style="font-weight: bold">FLOAT</span> <span style="color: #008080">(default: 0.0003)</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">│</span> --opt.betas <span style="font-weight: bold">FLOAT FLOAT</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">│</span> <span style="color: #008080">(default: 0.9 0.999)</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">╰───────────────────────────────────────────────────────╯</span>
</pre>



.. raw:: html

<pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
<strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./03_generic_subcommands.py experiment-sgd --help</strong>
<span style="font-weight: bold">usage</span>: 03_generic_subcommands.py experiment-sgd [-h] --path <span style="font-weight: bold">PATH</span>
[--opt.lr <span style="font-weight: bold">FLOAT</span>]

<span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> options </span><span style="font-weight: lighter">────────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
<span style="font-weight: lighter">│</span> -h, --help <span style="font-weight: lighter">show this help message and exit</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">│</span> --path <span style="font-weight: bold">PATH</span> <span style="font-weight: bold; color: #e60000">(required)</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">╰───────────────────────────────────────────────────────╯</span>
<span style="font-weight: lighter">╭─</span><span style="font-weight: lighter"> opt options </span><span style="font-weight: lighter">────────────────────────────────────────</span><span style="font-weight: lighter">─╮</span>
<span style="font-weight: lighter">│</span> --opt.lr <span style="font-weight: bold">FLOAT</span> <span style="color: #008080">(default: 0.0001)</span> <span style="font-weight: lighter">│</span>
<span style="font-weight: lighter">╰───────────────────────────────────────────────────────╯</span>
</pre>



.. raw:: html

<pre class="highlight" style="padding: 1em; box-sizing: border-box; font-size: 0.85em; line-height: 1.2em;">
<strong style="opacity: 0.7; padding-bottom: 0.5em; display: inline-block"><span style="user-select: none">$ </span>python ./03_generic_subcommands.py experiment-adam --path /tmp --lr 1e-3</strong>
<span style="color: #e60000">╭─</span><span style="color: #e60000"> </span><span style="font-weight: bold; color: #e60000">Unrecognized options</span><span style="color: #e60000"> </span><span style="color: #e60000">─────────────────────────────────────────</span><span style="color: #e60000">─╮</span>
<span style="color: #e60000">│</span> Unrecognized options: --lr <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> <span style="color: #800000">───────────────────────────────────────────────────────────────</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> Perhaps you meant: <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> <span style="font-weight: bold">--opt.lr FLOAT</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> <span style="color: #008080">(default: 0.0003)</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> in <span style="color: #008000">03_generic_subcommands.py experiment-adam --help</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> <span style="color: #008080">(default: 0.0001)</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> in <span style="color: #008000">03_generic_subcommands.py experiment-sgd --help</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> <span style="color: #800000">───────────────────────────────────────────────────────────────</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">│</span> For full helptext, run <span style="font-weight: bold">03_generic_subcommands.py --help</span> <span style="color: #e60000">│</span>
<span style="color: #e60000">╰─────────────────────────────────────────────────────────────────╯</span>
</pre>
10 changes: 5 additions & 5 deletions docs/source/examples/hierarchical_structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ objects. This helps with modularity and grouping in larger projects.
Structures as Function Arguments
--------------------------------

Structures can also be used as input to functions.
Structures can be used as input to functions.


.. code-block:: python
Expand Down Expand Up @@ -275,10 +275,10 @@ Dictionaries and TypedDict
--------------------------

Dictionary inputs can be specified using either a standard ``dict[K, V]``
annotation, or a :code:`TypedDict` subclass.
annotation, or a :py:class:`TypedDict` subclass.

For configuring :code:`TypedDict`, we also support :code:`total={True/False}`,
:code:`typing.Required`, and :code:`typing.NotRequired`. See the `Python docs <https://docs.python.org/3/library/typing.html#typing.TypedDict>`_ for all :code:`TypedDict` features.
For configuring :py:class:`TypedDict`, we also support :code:`total={True/False}`,
:py:data:`typing.Required`, and :py:data:`typing.NotRequired`. See the `Python docs <https://docs.python.org/3/library/typing.html#typing.TypedDict>`_ for all :py:class:`TypedDict` features.


.. code-block:: python
Expand Down Expand Up @@ -384,7 +384,7 @@ For configuring :code:`TypedDict`, we also support :code:`total={True/False}`,
Tuples and NamedTuple
---------------------

Example using :func:`tyro.cli()` to instantiate tuple types. :code:`tuple`,
Example using :func:`tyro.cli()` to instantiate tuple types. :py:class:`tuple`,
:py:data:`typing.Tuple`, and :py:class:`typing.NamedTuple` are all supported.


Expand Down
18 changes: 4 additions & 14 deletions docs/source/examples/pytorch_jax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The :code:`console_outputs=` argument can be set to :code:`False` to suppress he
error message printing.

This is useful in PyTorch for distributed training scripts, where you only want
to print the helptext from the main process:
to print helptext from the main process:


.. code-block:: python
Expand All @@ -37,23 +37,13 @@ to print the helptext from the main process:
:linenos:
# 01_pytorch_parallelism.py
import dataclasses
import tyro
@dataclasses.dataclass
class Args:
"""Description.
This should show up in the helptext!"""
field1: int
"""A field."""
field2: int = 3
"""A numeric field, with a default value."""
def train(foo: int, bar: str) -> None:
"""Description. This should show up in the helptext!"""
if __name__ == "__main__":
args = tyro.cli(Args, console_outputs=False)
args = tyro.cli(train, console_outputs=False)
print(args)
Expand Down
5 changes: 1 addition & 4 deletions docs/source/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# tyro

|build| |nbsp| |ruff| |nbsp| |mypy| |nbsp| |pyright| |nbsp| |coverage| |nbsp| |versions|
|ruff| |nbsp| |mypy| |nbsp| |pyright| |nbsp| |coverage| |nbsp| |versions|

:func:`tyro.cli()` is a tool for generating CLI interfaces in Python.

Expand Down Expand Up @@ -114,9 +114,6 @@ shell completion.



.. |build| image:: https://github.com/brentyi/tyro/actions/workflows/build.yml/badge.svg
:alt: Build status icon
:target: https://github.com/brentyi/tyro
.. |mypy| image:: https://github.com/brentyi/tyro/actions/workflows/mypy.yml/badge.svg
:alt: Mypy status icon
:target: https://github.com/brentyi/tyro
Expand Down
2 changes: 1 addition & 1 deletion examples/01_basics/05_choices.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Choices
:code:`typing.Literal[]` can be used to restrict inputs to a fixed set of literal choices.
:py:data:`typing.Literal[]` can be used to restrict inputs to a fixed set of literal choices.
Usage:
Expand Down
2 changes: 1 addition & 1 deletion examples/01_basics/07_unions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Unions
:code:`X | Y` or :code:`typing.Union[X, Y]` can be used to expand inputs to
:code:`X | Y` or :py:data:`typing.Union` can be used to expand inputs to
multiple types.
Usage:
Expand Down
2 changes: 1 addition & 1 deletion examples/02_hierarchical_structures/02_nesting_in_func.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Structures as Function Arguments
Structures can also be used as input to functions.
Structures can be used as input to functions.
Usage:
Expand Down
6 changes: 3 additions & 3 deletions examples/02_hierarchical_structures/04_dictionaries.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Dictionaries and TypedDict
Dictionary inputs can be specified using either a standard ``dict[K, V]``
annotation, or a :code:`TypedDict` subclass.
annotation, or a :py:class:`TypedDict` subclass.
For configuring :code:`TypedDict`, we also support :code:`total={True/False}`,
:code:`typing.Required`, and :code:`typing.NotRequired`. See the `Python docs <https://docs.python.org/3/library/typing.html#typing.TypedDict>`_ for all :code:`TypedDict` features.
For configuring :py:class:`TypedDict`, we also support :code:`total={True/False}`,
:py:data:`typing.Required`, and :py:data:`typing.NotRequired`. See the `Python docs <https://docs.python.org/3/library/typing.html#typing.TypedDict>`_ for all :py:class:`TypedDict` features.
Usage:
Expand Down
2 changes: 1 addition & 1 deletion examples/02_hierarchical_structures/05_tuples.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tuples and NamedTuple
Example using :func:`tyro.cli()` to instantiate tuple types. :code:`tuple`,
Example using :func:`tyro.cli()` to instantiate tuple types. :py:class:`tuple`,
:py:data:`typing.Tuple`, and :py:class:`typing.NamedTuple` are all supported.
Usage:
Expand Down
2 changes: 1 addition & 1 deletion examples/05_generics/02_generics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Generics (Legacy)
"""Generics (Python <3.12)
The legacy :py:class:`typing.Generic` and :py:class:`typing.TypeVar` syntax for
generic types is also supported.
Expand Down
40 changes: 40 additions & 0 deletions examples/05_generics/03_generic_subcommands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# mypy: ignore-errors
#
# Passing a Union type directly to tyro.cli() doesn't type-check correctly in
# mypy. This will be fixed by `typing.TypeForm`: https://peps.python.org/pep-0747/
"""Generic Subcommands
Usage:
python ./03_generic_subcommands.py --help
python ./03_generic_subcommands.py experiment-adam --help
python ./03_generic_subcommands.py experiment-sgd --help
python ./03_generic_subcommands.py experiment-adam --path /tmp --lr 1e-3
"""

import dataclasses
from pathlib import Path

import tyro


@dataclasses.dataclass
class Sgd:
lr: float = 1e-4


@dataclasses.dataclass
class Adam:
lr: float = 3e-4
betas: tuple[float, float] = (0.9, 0.999)


@dataclasses.dataclass(frozen=True)
class Experiment[OptimizerT: (Adam, Sgd)]:
path: Path
opt: OptimizerT


if __name__ == "__main__":
args = tyro.cli(Experiment[Adam] | Experiment[Sgd])
print(args)
Loading

0 comments on commit ae0ab2c

Please sign in to comment.