-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement custom constructor/proxy types (#82)
* Add ability to specify custom constructor * Tests, error refinement * Edge cases, more comprehensive tests, examples * Add back missing example (this commit doesn't really belong in this branch) * Add `constructor=` and `constructor_factory=` arguments to `tyro.conf.subcommand()` * Fix helptext edge case * Add mypy-compatible overload for subcommand_cli_from_dict() * Improve help, more tests * Optional subcommands are now handled automatically by `none_proxy` * More tests, improve errors * Fix test for Python 3.7 * Tweak example comments * Add test for subcommands from dict helper
- Loading branch information
Showing
19 changed files
with
869 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
.. Comment: this file is automatically generated by `update_example_docs.py`. | ||
It should not be modified manually. | ||
Subcommands from Functions | ||
========================================== | ||
|
||
|
||
:func:`tyro.extras.subcommand_cli_from_dict()` provides a shorthand that generates a | ||
subcommand CLI from a dictionary. | ||
|
||
|
||
|
||
.. code-block:: python | ||
:linenos: | ||
import tyro | ||
def checkout(branch: str) -> None: | ||
"""Check out a branch.""" | ||
print(f"{branch=}") | ||
def commit(message: str, all: bool = False) -> None: | ||
"""Make a commit.""" | ||
print(f"{message=} {all=}") | ||
if __name__ == "__main__": | ||
tyro.extras.subcommand_cli_from_dict( | ||
{ | ||
"checkout": checkout, | ||
"commit": commit, | ||
} | ||
) | ||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 02_nesting/05_subcommands_func.py --help</kbd> | ||
|
||
.. program-output:: python ../../examples/02_nesting/05_subcommands_func.py --help | ||
|
||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 02_nesting/05_subcommands_func.py commit --help</kbd> | ||
|
||
.. program-output:: python ../../examples/02_nesting/05_subcommands_func.py commit --help | ||
|
||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 02_nesting/05_subcommands_func.py commit --message hello --all</kbd> | ||
|
||
.. program-output:: python ../../examples/02_nesting/05_subcommands_func.py commit --message hello --all | ||
|
||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 02_nesting/05_subcommands_func.py checkout --help</kbd> | ||
|
||
.. program-output:: python ../../examples/02_nesting/05_subcommands_func.py checkout --help | ||
|
||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 02_nesting/05_subcommands_func.py checkout --branch main</kbd> | ||
|
||
.. program-output:: python ../../examples/02_nesting/05_subcommands_func.py checkout --branch main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
docs/source/examples/04_additional/10_custom_constructors.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
.. Comment: this file is automatically generated by `update_example_docs.py`. | ||
It should not be modified manually. | ||
Custom Constructors | ||
========================================== | ||
|
||
|
||
For additional flexibility, :func:`tyro.conf.arg()` accepts a ``constructor`` argument, | ||
which makes it easier to load complex objects. | ||
|
||
|
||
|
||
.. code-block:: python | ||
:linenos: | ||
import dataclasses | ||
import json as json_ | ||
from typing import Dict | ||
from typing_extensions import Annotated | ||
import tyro | ||
def dict_json_constructor(json: str) -> dict: | ||
"""Construct a dictionary from a JSON string. Raises a ValueError if the result is | ||
not a dictionary.""" | ||
out = json_.loads(json) | ||
if not isinstance(out, dict): | ||
raise ValueError(f"{json} is not a dictionary!") | ||
return out | ||
# A dictionary type, but `tyro` will expect a JSON string from the CLI. | ||
JsonDict = Annotated[dict, tyro.conf.arg(constructor=dict_json_constructor)] | ||
def main( | ||
dict1: JsonDict, | ||
dict2: JsonDict = {"default": None}, | ||
) -> None: | ||
print(f"{dict1=}") | ||
print(f"{dict2=}") | ||
if __name__ == "__main__": | ||
tyro.cli(main) | ||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 04_additional/10_custom_constructors.py --help</kbd> | ||
|
||
.. program-output:: python ../../examples/04_additional/10_custom_constructors.py --help | ||
|
||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 04_additional/10_custom_constructors.py --dict1.json '{"hello": "world"}'</kbd> | ||
|
||
.. program-output:: python ../../examples/04_additional/10_custom_constructors.py --dict1.json '{"hello": "world"}' | ||
|
||
------------ | ||
|
||
.. raw:: html | ||
|
||
<kbd>python 04_additional/10_custom_constructors.py --dict1.json '{"hello": "world"}`' --dict2.json '{"hello": "world"}'</kbd> | ||
|
||
.. program-output:: python ../../examples/04_additional/10_custom_constructors.py --dict1.json '{"hello": "world"}`' --dict2.json '{"hello": "world"}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
"""Multi-value Arguments | ||
Arguments of both fixed and variable lengths can be annotated with standard Python | ||
collection types: `typing.List[T]`, `typing.Tuple[T1, T2]`, etc. In Python >=3.9, | ||
`list[T]` and `tuple[T]` are also supported. | ||
Usage: | ||
`python ./03_collections.py --help` | ||
`python ./03_collections.py --dataset-sources ./data --image-dimensions 16 16` | ||
`python ./03_collections.py --dataset-sources ./data` | ||
""" | ||
|
||
import dataclasses | ||
import pathlib | ||
from typing import Tuple | ||
|
||
import tyro | ||
|
||
|
||
@dataclasses.dataclass(frozen=True) | ||
class TrainConfig: | ||
# Example of a variable-length tuple. `typing.List`, `typing.Sequence`, | ||
# `typing.Set`, `typing.Dict`, etc are all supported as well. | ||
dataset_sources: Tuple[pathlib.Path, ...] | ||
"""Paths to load training data from. This can be multiple!""" | ||
|
||
# Fixed-length tuples are also okay. | ||
image_dimensions: Tuple[int, int] = (32, 32) | ||
"""Height and width of some image data.""" | ||
|
||
|
||
if __name__ == "__main__": | ||
config = tyro.cli(TrainConfig) | ||
print(config) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
"""Subcommands from Functions | ||
:func:`tyro.extras.subcommand_cli_from_dict()` provides a shorthand that generates a | ||
subcommand CLI from a dictionary. | ||
Usage: | ||
`python ./05_subcommands_func.py --help` | ||
`python ./05_subcommands_func.py commit --help` | ||
`python ./05_subcommands_func.py commit --message hello --all` | ||
`python ./05_subcommands_func.py checkout --help` | ||
`python ./05_subcommands_func.py checkout --branch main` | ||
""" | ||
|
||
import tyro | ||
|
||
|
||
def checkout(branch: str) -> None: | ||
"""Check out a branch.""" | ||
print(f"{branch=}") | ||
|
||
|
||
def commit(message: str, all: bool = False) -> None: | ||
"""Make a commit.""" | ||
print(f"{message=} {all=}") | ||
|
||
|
||
if __name__ == "__main__": | ||
tyro.extras.subcommand_cli_from_dict( | ||
{ | ||
"checkout": checkout, | ||
"commit": commit, | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
"""Custom Constructors | ||
For additional flexibility, :func:`tyro.conf.arg()` accepts a `constructor` argument, | ||
which makes it easier to load complex objects. | ||
Usage: | ||
`python ./10_custom_constructors.py --help` | ||
`python ./10_custom_constructors.py --dict1.json "{\"hello\": \"world\"}"` | ||
`python ./10_custom_constructors.py --dict1.json "{\"hello\": \"world\"}"` --dict2.json "{\"hello\": \"world\"}"` | ||
""" | ||
|
||
import dataclasses | ||
import json as json_ | ||
from typing import Dict | ||
|
||
from typing_extensions import Annotated | ||
|
||
import tyro | ||
|
||
|
||
def dict_json_constructor(json: str) -> dict: | ||
"""Construct a dictionary from a JSON string. Raises a ValueError if the result is | ||
not a dictionary.""" | ||
out = json_.loads(json) | ||
if not isinstance(out, dict): | ||
raise ValueError(f"{json} is not a dictionary!") | ||
return out | ||
|
||
|
||
# A dictionary type, but `tyro` will expect a JSON string from the CLI. | ||
JsonDict = Annotated[dict, tyro.conf.arg(constructor=dict_json_constructor)] | ||
|
||
|
||
def main( | ||
dict1: JsonDict, | ||
dict2: JsonDict = {"default": None}, | ||
) -> None: | ||
print(f"{dict1=}") | ||
print(f"{dict2=}") | ||
|
||
|
||
if __name__ == "__main__": | ||
tyro.cli(main) |
Oops, something went wrong.