0

I have an argparse parser with several subcommands some of which share an option (via a parent parser). Now I want to set a default value for such an option regardless of which subparser will be executed in the end. My non working code looks like this:

from argparse import ArgumentParser

base = ArgumentParser(add_help=False)
base.add_argument('--foo', action='store_true')
parser = ArgumentParser()
subparsers = parser.add_subparsers(dest='action')
s1 = subparsers.add_parser('a', parents=[base])
s2 = subparsers.add_parser('b', parents=[base])

parser.set_defaults(foo=42)
print(parser.parse_args(['a']))
s1.set_defaults(foo=43)
print(parser.parse_args(['a']))

This prints

Namespace(action='a', foo=False)
Namespace(action='a', foo=43)

I have many subparsers and many options so I want to avoid saving every subparser by name and calling set_defauls on it. Can that be done?

I will know the value I want to set there only after creating all the parsers so I can not specify the defaults in the call to add_argument.

Background: what I am actually working on

The defaults I want to set come from a config file. I actually have two parsers, one to find the config file first and one to parse the subcommands. But I need to define both parsers up front in order to overwrite the help method of the first parser with the help method of the second parser in order to display the full --help text before parsing the config (because that might fail and I could not display the help text). A reduced version of my code looks like this:

import argparse
base = argparse.ArgumentParser(add_help=False)
base.add_argument("--config", help="config file to use")
p1 = argparse.ArgumentParser(parents=[base])
p1.add_argument('remainder', nargs=argparse.REMAINDER)
p2 = argparse.ArgumentParser(parents=[base])
s = p2.add_subparsers(dest='action')
s1 = s.add_parser('a')  # add some options
s2 = s.add_parser('b')  # add some options
# and so on
p1.print_help = p2.print_help
a1 = p1.parse_args()
config = load_my_config(a1.config)
p2.set_defaults(**config.get_my_defaults())
a2 = p2.parse_args(a1.remainder)
Lucas
  • 685
  • 4
  • 19
  • `s1` and `s2` are references to the respective parsers. Normally you use those names to add arguments, e.g. `s1.add_argument(....)`. You can treat them like any Python object - such as collect them in a list or lists. Without knowing how you'll choose the individual defaults I don't think we can help you streamline the process. Fell free to write utility functions to streamline repetitive steps. Remember a good programmer is lazy, and will put a lot of effort into saving a few key strokes. – hpaulj Oct 28 '19 at 03:46
  • @hpaulj I know how to add arguments. I load the defaults from a config file because I want to have the value for `foo` from the config file if the user did not specify `--foo`. But the user can change the config file with an option (`-c`) so I have to create one parser which will just find the correct config file and then load the config file. But I also want to define the second parser before parsing the config file because I want to display the `--help` text for *all* subcommands without parsing the config file. I will try to explain that in my question. – Lucas Oct 28 '19 at 06:43
  • I just realized something here. `foo` is coming from the `parent` and has `False` default. With the parent mechanism, each subparser gets the same Action object, not a copy. So changing the default for `s1` changes it for `s2` as well (and `base`). I don't recommend trying to customize an Action inherited from a parent. Things can get messy. – hpaulj Oct 28 '19 at 06:55
  • In the `argparse` docs, `set_defaults` is used to add an extra (callable) attribute that will be different for each subparser. It's a cleaver trick that expands the functionality of subparsers. Otherwise `set_defaults` doesn't get much use. The `default` parameter is usually enough. – hpaulj Oct 28 '19 at 07:01
  • @hpaulj I have seen https://stackoverflow.com/questions/24666197 and some other similar questions. I am not concerned about having the default on s1 overwritten by s2. I want them all (or rather the only one) to be the same. – Lucas Oct 28 '19 at 07:08
  • So what's the question? `s1.set_defaults(...)` sets the value for all the subparsers that share the parent. That's apparently what you want. – hpaulj Oct 28 '19 at 19:35
  • @hpaulj the question is the sentence in my post that ends in a question mark (Maybe including the sentence before it for more context). And to make it clear again: I don't care about the parent / child relation. I have *many* parsers from `s1` to `s_n` and want to set the defaults in an effective way, *no matter which subcommand will be paresed in the end*. – Lucas Oct 29 '19 at 04:55

1 Answers1

0

I found a solution to listing all the subparsers. The solution is not to remember all the variables for the different sub-parsers but only the _SubParsersAction object that was used to create them:

import argparse
p = argparse.ArgumentParser()
s = p.add_subparsers()
a = s.add_parser('a')
a.add_argument(...)
b = s.add_parser('b')
c = s.add_parser('c')
...
# now I don't need to remember all the variables a, b, c, ...
# in order to set the defaults on all of theses sub-parsers
config = load_my_config_file()
defaults = config.get_defaults()
for name, sparser in s.choises:
    print("Setting defaults on sub parser for '{}'").format(name)
    sparser.set_defaults(**defaults)
Lucas
  • 685
  • 4
  • 19