1

Given an argparse.ArgumentParser, e.g.

parser.py

from argparse import ArgumentParser
p = ArgumentParser()

subs = p.add_subparsers()
a = subs.add_parser('a')
a.add_argument('aa')
b = subs.add_parser('b')
b.add_argument('bb')

p.parse_args()

Now, I can call this with python parser.py -h / b -h / a -h, and each call with give me all options relevant for said sub parser. Adding them up together, I can get a full list of all args.

I can do this like this:

from parser import p
p.print_help()

and then start parsing the output, which is obviously not a good idea.

I can access p._subparsers but that didn't give me too much.

- How to do this the right way?

What I eventually want to get: A pytest test, where I can test all options yield a valid response, e.g. python parser a aa will actually do aa

CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
  • Supplying the argument in the `p.parser_args(['arg_a', 'arg_b'])` is not what you need? Are you also trying to find what arguments exists? You will be able to do: ```def some_test(): p.parse_args(['a', 'aa']) p.parse_args(['b', 'bb'])``` You will then be able to either `except SystemExit` or derive from ArgumentParser and override the `error` method. – Or Y Aug 16 '20 at 07:39
  • In test-time, I don't know the args, so I can't give them to the `parse_args`. I can only access the initialized parser, and work my way down from there – CIsForCookies Aug 16 '20 at 07:42
  • When accessing `p`. You can access its `_actions` list. You can iterate over it and check the types. if its of type `_StoreAction` you can access its dest to check its name. if its `_SubParsersAction` - You can iterate over the inner `choices` and get its Actions. A bit pain but it will work – Or Y Aug 16 '20 at 07:52
  • @OrY Accessing any attributes beginning with `_` is inherently fragile because these can be changed without notice in future releases. Is there a way using "public" methods? – alani Aug 16 '20 at 07:58
  • @alaniwi I know that it's dangerous but sadly there is no public way to do that. A safer way to achieve what OP wants might be to derive from the ArgumentParser and change the add_argument and (Also add_subparsers and later add_subparser) in a way that each call to those function will keep the new arguments in an externally exposed list. – Or Y Aug 16 '20 at 08:17

1 Answers1

1
from argparse import ArgumentParser
p = ArgumentParser()

subs = p.add_subparsers()
a = subs.add_parser('a')
aa = a.add_argument('aa')
b = subs.add_parser('b')
bb = b.add_argument('bb')

p.parse_args()

add_argument returns an Action object. These objects are also referenced in the parser._actions list, but you can collect the references yourself, either by name or in a list (or dict). add_subparsers is a variant on add_argument that creates a special subclass of Action, one which has the add_parser method.

Try running this code in an interactive session, and look at the objects created. Most have a repr method that shows the key (but not all) attributes. For actions this will include things like dest, nargs, default, etc. You can even modify those attributes after creation. argparse is written as a normal Python hierarchy of classes.

As for the _ and __ attributes - they aren't documented, but that doesn't mean they will be changed at a moment's notice. Changes appear in argparse at a snails pass, with a lot thought given to backward compatibility (though sometimes not enough). It's easier to change the documentation than the code itself, especially not core things like _actions.

But I'm not entirely sure what you are trying to do. Running unittests with argparse is somewhat awkward. You can simulate args = parse.parse_args(argv) where argv is a list of strings of your choice. Or you can set sys.argv[1:] to a new set of strings. Or you could create a new args=argparse.Namespace(...) object. Trying to generate all possible argument combinations just based on the parser setup sounds like a lot of work, especially when using subparsers.

hpaulj
  • 221,503
  • 14
  • 230
  • 353