1

Let's say you want to use subcommands and at its core the subcommands want the same object data points to be stored in Namespace but perhaps grouped by subcommands. How can one extend argparse but not lose any of its standard behavior while achieving this?

For example:

import argparse

parser = argparse.ArgumentParser()

subparser = parser.add_subparsers()

fooparser = subparser.add_parser('foo')
fooparser.add_argument('rawr', dest='rawr')

barparser = subparser.add_parser('bar')
barparser.add_argument('rawr', dest='rawr')

# It would be nice that in the Namespace object this shows up as the following: 
# args: foo 0
# Namespace(foo.rawr=0)
# args: bar 1
# Namespace(bar.rawr=1)

The above example just tries to explain my point but the main issue is that what happens is that, when the above code executes parse_args() returns a Namespace that just has rawr=N but what if my code distinguishes behavior based on the subcommand so its important that there be an object that has an attribute rawr within the Namespace object. For example:

if args.foo.rawr: 
   # do action 1
   pass

if args.bar.rawr:
   # do action 2
   pass

If args only has args.rawr, then you cannot discriminate action 1 or action 2, they both are legal actions without the additional nested layer.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
LeanMan
  • 474
  • 1
  • 4
  • 18
  • `rawr` is just a local argument to the current function. It's lifetime should not live beyond the end of the actions, so there's no need to have separate variables. – Tim Roberts Dec 18 '21 at 02:59
  • I suppose I agree and I can see that the sub-command example for using function as default callable is a good argument for that. I'm starting to also see that this code isn't quite extensible as anytime I add a new argument with the same dest, my if statement grows by 1 which can become pretty ugly. – LeanMan Dec 18 '21 at 03:04
  • Beside the point, but this code doesn't work. `ValueError: dest supplied twice for positional argument`. Remove the `dest` kwargs. – wjandrea Dec 18 '21 at 04:56
  • I think I am desiring the antithesis of this code which is the ability to nest namespaces such that the syntax is `subcommand.attr`. But it seems like it can only be done with your own custom Actions. In my defense, I can change the code such that dest is `dest=foo.rawr`. It'll run and it'll put in the namespace a string attributes that is `'foo.rawr'`, either way its not desired. – LeanMan Dec 18 '21 at 06:32

2 Answers2

1

To save the subcommand name, use .add_subparsers(dest=), like this:

subparser = parser.add_subparsers(dest='command')

fooparser = subparser.add_parser('foo')
fooparser.add_argument('rawr')

barparser = subparser.add_parser('bar')
barparser.add_argument('rawr')

for a in ['foo', '0'], ['bar', '1']:
    args = parser.parse_args(a)
    print(args)
    if args.command == 'foo':
        print('doing foo!')
    elif args.command == 'bar':
        print('doing bar!')

Output:

Namespace(command='foo', rawr='0')
doing foo!
Namespace(command='bar', rawr='1')
doing bar!

Thanks to George Shuklin for pointing this out on Medium

wjandrea
  • 28,235
  • 9
  • 60
  • 81
0

Supplying a dest for subparser is desirable, though not required. But it may be enough to further identify the arguments.

Positionals can take any name you want to supply; you can't supply an extra dest. That name will be used in the args Namespace. Use metavar to control the string used in the help.

For flagged arguments (optionals), use the dest.

subparser = parser.add_subparsers(dest='cmd')

fooparser = subparser.add_parser('foo')
fooparser.add_argument('-b','--baz', dest='foo_baz')
fooparser.add_argument('foo_rawr', metavar='rawr')

barparser = subparser.add_parser('bar')
barparser.add_argument('-b','--baz', dest='bar_baz')
barparser.add_argument('bar_rawr', metavar='rawr')

Include a print(args) during debugging to get a clear idea of what the parser does.

In previous SO we have discussed using custom Namespace class and custom Action subclasses to create some sort of nesting or dict like behavior, but I think that's more work than most people need.

Docs also illustrate the use of

parser_foo.set_defaults(func=foo)

to set an extra argument based on the subparser. In this example the value may be an actual function object. The use of the dest is also mentioned in the docs, though perhaps as too much of an afterthought.

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • I agree. I found the GroupedAction example here https://stackoverflow.com/a/18677482/10421103. I liked it but it fell short in case of not using an argument. If you did not use an argument it would not show up in the Namespace so you would have had to do `hasattr('attr')` to protect from Attribute Exceptions. Other than that its a nice solution. – LeanMan Dec 18 '21 at 06:22
  • Talk about ancient history :) For ordinary arguments, defaults are placed in the namespace right at the start, and over written it used. Since 2013 there has also been a change in how the subparse namespace is initialed,that might affect older answers like this. – hpaulj Dec 18 '21 at 08:07