tyro
tyro copied to clipboard
Confusion around managing flags like `--version`
I've been impressed with Tyro's capabilities so far. However, I've encountered some ergonomic challenges that I hope to get some help with.
Related Issue: This issue seems to be related to issue #89. Although I attempted the solutions suggested there, I was unable to resolve my problem.
The Problem: I'm in the process of creating a CLI with two subcommands: init and run. Each subcommand requires its own set of arguments. Additionally, I want the CLI to accept global flags such as --version or --status. Here's my initial setup:
Initial Setup
from typing import Annotated, Optional, Union
import tyro
from pydantic import BaseModel
class Init(BaseModel):
env: str = ".env"
force: bool = False
class Run(BaseModel):
headless: bool
days_back: int
short_items: bool
words_per_item: int
class Args(BaseModel):
subcommand: Union[Init, Run]
version: bool = False
def entrypoint() -> None:
tyro.extras.set_accent_color("bright_yellow")
args = tyro.cli(Annotated[Args, tyro.conf.OmitSubcommandPrefixes])
print(args)
(.venv) python cli.py --help
usage: cli.py [-h] [--version | --no-version] {init,run}
╭─ options ─────────────────────────────────────────╮
│ -h, --help show this help message and exit │
│ --version, --no-version │
│ (default: False) │
╰───────────────────────────────────────────────────╯
╭─ subcommands ─────────────────────────────────────╮
│ {init,run} │
│ init │
│ run │
╰───────────────────────────────────────────────────╯
Running cli.py --help displays the help text as expected. However, I've noticed that the --no-version
flag is automatically generated and doesn't align with my requirements. I am looking for a way to disable this automatic generation.
Moreover, when I attempt to use the --version
flag independently, it doesn't function as anticipated:
(.venv) python cli.py --version
╭─ Required options ───────────────────────────────╮
│ The following arguments are required: {init,run} │
│ ──────────────────────────────────────────────── │
│ For full helptext, run cli.py --help │
╰──────────────────────────────────────────────────╯
This command incorrectly demands a subcommand (init or run), which should not be the case for a version check.
When running --version
under one of the subcommands, an error is thrown out, which makes sense but the error doesn't.
(.venv) python cli.py init --version
╭─ Unrecognized options ───────────────────────╮
│ Unrecognized or misplaced options: --version │
│ ──────────────────────────────────────────── │
│ Perhaps you meant: │
│ --version, --no-version │
│ (default: False) │
│ in cli.py --help │
│ ──────────────────────────────────────────── │
│ For full helptext, run cli.py --help │
╰──────────────────────────────────────────────╯
Optional Subcommands
To work around this, I tried making the subcommand optional:
import tyro
from pydantic import BaseModel
class Init(BaseModel):
env: str = ".env"
force: bool = False
class Run(BaseModel):
headless: bool
days_back: int
short_items: bool
words_per_item: int
class Args(BaseModel):
subcommand: Optional[Union[Init, Run]] = None
version: bool = False
def entrypoint() -> None:
tyro.extras.set_accent_color("bright_yellow")
args = tyro.cli(Annotated[Args, tyro.conf.OmitSubcommandPrefixes])
print(args)
if __name__ == "__main__":
entrypoint()
Applying an Optional
type with default value = None
allows --version
to be run by itself but also appends a subcommand None
option, which doesn't make sense in this case.
(.venv) python cli.py --version
subcommand=None version=True
(.venv) python cli.py --help
usage: cli.py [-h] [--version | --no-version] [{init,run,None}]
╭─ options ───────────────────────────────────────────────╮
│ -h, --help show this help message and exit │
│ --version, --no-version │
│ (default: False) │
╰─────────────────────────────────────────────────────────╯
╭─ optional subcommands ──────────────────────────────────╮
│ (default: None) │
│ ───────────────── │
│ [{init,run,None}] │
│ init │
│ run │
│ None │
╰─────────────────────────────────────────────────────────╯
Regarding #89, I've tried modifying the alternatives stated here and here with no success.
1st try
import dataclasses
from typing import Tuple, Union
import tyro
@dataclasses.dataclass
class Checkout:
"""Check out a branch."""
x: int
@dataclasses.dataclass
class Commit:
"""Commit something."""
y: int
@dataclasses.dataclass
class Args:
version: bool = False
"""Print version and exit."""
global_args, checkout_or_commit = tyro.cli(
Tuple[
Args,
Union[Checkout, Commit],
]
)
print(global_args, checkout_or_commit)
(.venv) python cli.py --0.version
╭─ Required options ──────────────────────────────────────────╮
│ The following arguments are required: {1:checkout,1:commit} │
│ ─────────────────────────────────────────────────────────── │
│ For full helptext, run cli.py --help │
╰─────────────────────────────────────────────────────────────╯
2nd try
import dataclasses
from typing import Annotated, Tuple, Union
import tyro
@dataclasses.dataclass
class Checkout:
"""Check out a branch."""
x: int
@dataclasses.dataclass
class Commit:
"""Commit something."""
y: int
@dataclasses.dataclass
class Args:
version: bool = False
"""Print version and exit."""
global_args, checkout_or_commit = tyro.cli(
Annotated[
Tuple[
Union[
Checkout,
Commit,
],
Args,
],
tyro.conf.OmitArgPrefixes,
tyro.conf.OmitSubcommandPrefixes,
]
)
print(global_args, checkout_or_commit)
(.venv) python cli.py --version
╭─ Required options ──────────────────────────────────────╮
│ The following arguments are required: {checkout,commit} │
│ ─────────────────────────────────────────────────────── │
│ For full helptext, run cli.py --help │
╰─────────────────────────────────────────────────────────╯
I'm confused here. Not sure if I'm misunderstanding something from the documentation or how to apply different options, but seems that applying flags that i) do not create --no-x
versions and ii) can be applied alone outside any command, should be fairly straightforward on a CLI tool.
Thanks!