typed-argument-parser
typed-argument-parser copied to clipboard
`parse_args` should return the chosen sub parser object
Hi,
I'm using sub parsers and I assumed that the Tap for the chosen sub parser would be returned from parse_args() however the parent parser is actually returned. The motivation for having the sub parser returned is as follows:
- allow additional functionality to be loaded on to the parser
- get code completion for the sub parser as you do for the parent parser
- determine which sub parser was chosen in a manner that typing can understand.
For example:
class SubParserA(Tap):
foo: str
def do_foo(self):
# Do something with self.foo here
class SubParserB(Tap)
bar: str
def do_bar(self):
# Do something with self.bar here
class MainParser(Tap):
def configure(self) -> None:
self.add_subparsers()
self.add_subparser('a', SubParserA)
self.add_subparser('b', SubParserB)
if __name__ == '__main__':
main_parser = MainParser()
args = main_parser.parse_args()
if isinstance(args, SubParserA):
args.do_foo()
elif isinstance(args, SubParserB):
args.do_bar()
Would be great to hear your thoughts!
Cheers Jase
Hi @JasonGilholme,
Thank you for raising this issue! We agree that the current behavior is not ideal. We've already put some thought into this problem https://github.com/swansonk14/typed-argument-parser/issues/17#issuecomment-809587061, but we're still thinking about it.
For future us and perhaps others, we'll leave some runnable code that breaks (heavily inspired by your code) when you run python file.py a --foo biz:
from tap import Tap
class SubParserA(Tap):
foo: str
def do_foo(self):
pass
class SubParserB(Tap):
bar: str
def do_bar(self):
pass
class MainParser(Tap):
def configure(self) -> None:
self.add_subparsers()
self.add_subparser('a', SubParserA)
self.add_subparser('b', SubParserB)
if __name__ == '__main__':
main_parser = MainParser()
args = main_parser.parse_args()
args.do_foo()
Best, Jesse and Kyle
I would like to second this. With the current implementation I am not sure how this is supposed to work, but in the example you provide, the code to access args.foo or args.bar gives a type exception (VS Code/pylance). Which makes sense since they are defined in separate classes.
What I expected:
from tap import Tap
class MainParser(Tap):
shared: str
def configure(self) -> None:
self.add_subparser('a', SubParserA)
self.add_subparser('b', SubParserB)
class SubParserA(MainParser):
foo: str
class SubParserB(MainParser):
bar: str
if __name__ == '__main__':
args = MainParser().parse_args()
if isinstance(args, SubParserA):
print(args.shared)
print(args.foo)
That code does not show any errors in the editor, but when run goes into an infinite loop which seems to involve _get_class_variables and _get_from_self_and_super. And without the subclasses, the "isinstance" part will never be true. Is there a trick for that?
As an aside, it would be nice to allow "None" as the subparse class, for subparsers without additional arguments. I created a NoArgs object instead, so maybe that's good enough, but maybe include as an example?
Hi all,
We’re working on sketching a plan for a Tap version 2 release. We have a sketch of the solution to a number of issues including this one. This includes a breaking change to the treatment of subparsers and a different way that we treat comments to support argument groups. We’re excited to start the implementation and would love feedback and recommendations from everyone. We’re so appreciative of all of the wonderful pull requests, issues, and ideas from all of you!
Best, Kyle and Jesse
See my comments at https://github.com/swansonk14/typed-argument-parser/issues/69#issuecomment-1036417593. Thanks!