Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiple independent subcommands #61

Open
Linusnie opened this issue Aug 3, 2023 · 2 comments
Open

multiple independent subcommands #61

Linusnie opened this issue Aug 3, 2023 · 2 comments

Comments

@Linusnie
Copy link

Linusnie commented Aug 3, 2023

hi, first off thanks for making this great library! I've run into the following issue: I'd like to have a setting structure with two subcommands, each with separate settings and defaults:

from dataclasses import dataclass
from typing import Union
import tyro

@dataclass
class A1:
    a1: int = 1

@dataclass
class A2:
    a2: int = 1

@dataclass
class B1:
    b1: int = 1

@dataclass
class B2:
    b2: int = 1

@dataclass
class Config:
    a: Union[A1, A2] = A1()
    b: Union[B1, B2] = B1()


if __name__ == '__main__':
    print(tyro.cli(Config))

I'd like to be able to set the value for config.b while leaving config.a as its default. However if I run python tyro_test.py b:b2 I get the following error:

tyro_test.py: error: argument [{a:a1,a:a2}]: invalid choice: 'b:b2' (choose from 'a:a1', 'a:a2')

and the only way(?) to set config.b is to set config.a first with python tyro_test.py a:a1 b:b2. Is there a way to set config.b via the command line without having to set config.a?

@brentyi
Copy link
Owner

brentyi commented Aug 3, 2023

Hi @Linusnie!

I appreciate the detailed issue.

Unfortunately, what you described is the best that we can do right now. I think #60 (comment) addresses the same problem:

This is one of a handful of remaining things in tyro that I'm not super happy with; it mostly reduces to the limited choices we have when mapping types to argparse functionality, which we use for all of the low-level CLI stuff.

For union types over structures like dataclasses, subparsers make sense because they let us only accept arguments from the chosen types.

The downside is that each parser or subparser can only contain a single set of "child" subparsers. This results in a tree structure that doesn't map exactly to the case where we have multiple Union types. In your case, the tree structure we generate looks like:

# (subcommands have been shortened a bit for brevity)

[root parser]
├─ output-head:None
│  ├─ optimizer:None
│  ├─ optimizer:optimizer
├─ output-head:output-head
│  ├─ optimizer:None
│  ├─ optimizer:optimizer

...where optimizer:* is always actually a subcommand of the output-head:*, and not the root parser. And so we need to pass in output-head:* in order to traverse a step down the tree, where we can then pass in the optimizer:* subcommand.

Of course open to suggestions here. :)

The best I've been able to come up with is to tear out argparse and replace it with an in-house solution — or try to fork argparse to somehow add this functionality — but of course either would take a lot of development energy, while breaking compatibility with argparse-reliant tools (for example, we use shtab for tab completion in the shell).

If you really want this behavior, I guess you could annotate the first field with Union[A1, A2, B1, B2], and add some post-processing logic, but this would also add a lot of complexity + downsides.

@Linusnie
Copy link
Author

Linusnie commented Aug 3, 2023

@brentyi I see, thank you for the explanation! That's unfortunate but I'll try to reduce it to just one subcommand somehow for now then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants