Closed dsedivec closed 1 month ago
Hi!
We don't support this right now, primarily because there's no standard Python syntax that this behavior would correspond to.
If you feel there's a clean way to integrate this I'd be open to suggestions! We could, as an example, add something to tyro.conf
or tyro.conf.arg()
.
We don't support this right now, primarily because there's no standard Python syntax that this behavior would correspond to.
If you feel there's a clean way to integrate this I'd be open to suggestions! We could, as an example, add something to
tyro.conf
otyro.conf.arg()
.
OK, thanks! IMHO they're an essential part of argument parsing, so they're worth adding even at the cost of adding something clunky. I use them often enough in argparse that I consider it a downgrade to switch to an argument parsing library that lacks support for them (and most do lack support for them).
All that said... I don't really have a great suggestion even for something clunky. Click doesn't really have these, but Cloup sits on top of Click and implements it by adding "constraints" to option groups. I think the closest Tyro gets to groups is probably "hierarchical configs", and I take a strong moral position against option names with .
(dot) in them. 😉
Typer doesn't really implement these either, though I believe the author suggested using a validator to enforce mutual exclusivity. You can't really inspect a validator method to produce your help message, though; you'd be unable to show the mutually exclusive options as mutually exclusive in --help
.
In C, if I was implementing my options in a struct, I'd probably define mutually exclusive arguments in a union. I can't think of how that really translates to Python, since C unions crucially have differently-named members—it starts to look a lot like hierarchical configs.
You could do it with subclasses, where you've got a base class with common options then subclasses, one per mutually exclusive option. Aside from being kind of annoying to work with, god help you if you want two or more sets of mutually exclusive options. I guess you could use multiple inheritance?
As you suggested, I was originally thinking along the lines of using tyro.conf.arg
to group mutually exclusive options together, kind of like Cloup does with option groups and constraints. That does not translate to Python's type system, but it accomplishes the end goal of leaving Tyro able to both enforce mutual exclusivity, and also to document this in the --help
.
Anyway, thank you for your fast answer! You may feel free close this now if you want, as my question has been answered. 🙂
Thanks for your thoughts! I'll keep this issue open while I'm thinking about this.
tyro
's likely not the best tool if you need this soon, but your C-style union note is actually a really interesting one; since we can already rename arguments with tyro.conf.arg()
, semantics like this could make sense:
# (mockup, not a real runnable example)
from typing import Annotated
import tyro
from tyro.conf import arg
def main(
mutually_exclusive: (
Annotated[int, arg(name="foo")]
| Annotated[str, arg(name="bar")]
)
) -> None:
...
if __name__ == "__main__":
tyro.cli(main)
where mutually_exclusive
can take the value of either --foo INT
or --bar STR
, but not both.
Another option is to build on typing.TypedDict
's total=False
: we could have something like a tyro.conf.MutuallyExclusiveTypedDictKeys[]
to communicate that only one key in the dictionary should be populated.
In either case, the question becomes whether the advantages here outweigh the complexity + maintenance effort. I'm not sure, since (as per the Typer suggestion) this kind of mutual exclusivity can also be added to the helptext + validated after the tyro.cli()
call, it's just much worse UX.
Hi! Thanks for making Tyro! I'd like to use it, but I can't figure out how to do mutually exclusive options. I've searched the docs and issues and I haven't turned anything up. Is it possible to make mutually exclusive options with Tyro?