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

What is the right way to call set_defaults #109

Open
aucampia opened this issue Jun 12, 2023 · 5 comments
Open

What is the right way to call set_defaults #109

aucampia opened this issue Jun 12, 2023 · 5 comments
Labels
bug Something isn't working

Comments

@aucampia
Copy link

The documentation for argparse [ref] suggests using set_defaults for handling subcommands, but I'm not sure what is the right way to call set_defaults with TAP, it would be great if you can add some examples or documentation for this.

@aucampia
Copy link
Author

The alternative is to use dest from add_subparsers, but if I use this and also add a type hint for the dest then Tap thinks it should add an argument for it.

So if I do this for example:

class MainParser(Tap):
  subparser_flag: str
  
  ...
  
  def configure(self):
    ...
    self.add_subparsers(required=True, dest="subparser_flag")
    ...

And run, then I get error: the following arguments are required: --subparser_flag - it would be very helpful if there is some example of how subparser support is supposed to work given both the normal ways of using it with ArgumentParser is broken.

@aucampia
Copy link
Author

aucampia commented Jun 12, 2023

I also tried with _subparser_flag as follow:

class MainParser(Tap):
  _subparser_flag: str
  
  ...
  
  def configure(self):
    ...
    self.add_subparsers(required=True, dest="_subparser_flag")
    ...

But get the same error.

It seems there is some code that attempts to ignore class variables starting with _:

def _get_class_dict(self) -> Dict[str, Any]:
"""Returns a dictionary mapping class variable names to values from the class dict."""
class_dict = self._get_from_self_and_super(
extract_func=lambda super_class: dict(getattr(super_class, '__dict__', dict()))
)
class_dict = {
var: val
for var, val in class_dict.items()
if not (var.startswith('_')
or callable(val)
or isinstance(val, staticmethod)
or isinstance(val, classmethod)
or isinstance(val, property))
}

But this does not seem to be working as intended, because these still show up in _get_annotations.

Is this another bug? If so I will raise an issue for it.

@aucampia
Copy link
Author

As a workaround I have done this:

class MainParser(Tap):

    subparser_flag: str

    def configure(self):
        self.add_argument(
            # Hack to work around problems with Tap, without this Tap complains
            # that `subparser_flag` is not set even if a subcommand is supplied,
            # for more info see
            # <https://github.com/swansonk14/typed-argument-parser/issues/109>.
            "--subparser_flag",
            action="store",
            # dest="subparser_flag",
            help=argparse.SUPPRESS,
            required=False,
        )
        self.add_subparsers(required=True, dest="subparser_flag", help="sub-command")

@swansonk14 swansonk14 added the bug Something isn't working label Jun 28, 2023
@martinjm97
Copy link
Collaborator

Hi @aucampia,

Unfortunately, we couldn't quite follow this issue. We realize that there's some concern with our current treatment of subparsers. Would you be able to clarify with some example code and then a description of the desired behavior versus the existing behavior?

Thanks,
JK

@ArielHorwitz
Copy link

ArielHorwitz commented Dec 15, 2024

@aucampia
Came here looking for the same issue. It isn't documented, but I managed to guess the solution:

from tap import Tap

class Foo(Tap):
    def configure(self):
        self.set_defaults(func=lambda: print("foo subcommand"))

class Bar(Tap):
    def configure(self):
        self.set_defaults(func=lambda: print("bar subcommand"))

class MyTap(Tap):
    def configure(self):
        self.add_subparsers()
        self.add_subparser("foo", Foo)
        self.add_subparser("bar", Bar)

args = MyTap().parse_args(["foo"])
args.func()  # prints "foo subcommand"


args = MyTap().parse_args(["bar"])
args.func()  # prints "bar subcommand"

Edit: The issue with this solution is that the lines args.func() produce an error in MyPy:
error: "MyTap" has no attribute "func" [attr-defined]

However, I am assuming the typed solution may be out of scope for Tap since this is essentially inheriting the functionality from argparse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants