0

I have a program with sub commands, but they all have common arguments (e.g. they all require input and output directories) which I included in the parent parser to avoid redundancies. However, I want each subcommand to have a different default value, but this causes the value provided in the command-line to be ignored.

MWE:

import argparse

top_parser = argparse.ArgumentParser()
top_parser.add_argument("--input-dir", type=str)

subparsers = top_parser.add_subparsers()

generate_parser = subparsers.add_parser("generate")
generate_parser.set_defaults(input_dir=".")

process_parser = subparsers.add_parser("process")
process_parser.set_defaults(input_dir="SOME_OTHER_DIR")

generate_args = top_parser.parse_args("--input-dir USE_THIS_DIR generate".split())
print("generate_args = ", generate_args)

process_args = top_parser.parse_args("--input-dir USE_THIS_DIR process".split())
print("process_args = ", process_args)

This gives:

generate_args =  Namespace(input_dir='.')
process_args =  Namespace(input_dir='SOME_OTHER_DIR')

but I want:

generate_args =  Namespace(input_dir='USE_THIS_DIR')
process_args =  Namespace(input_dir='USE_THIS_DIR')

I can circumvent this by separately adding the argument to each subparser, but I would like to avoid this redundancy if possible.

M.A.R.
  • 37
  • 5
  • A change made some years ago to `argparse` makes the subparser's default override the main parser's default and/or value. Figure out some other way, probably in post parsing, to set a custom default (if for example the value is the default default `None`). – hpaulj Apr 19 '19 at 00:01
  • Or use a different `dest` when setting the subparser's default. – hpaulj Apr 19 '19 at 00:24

1 Answers1

0

One workaround would be to check the value of input_dir after parsing, and substitute a subparser-specific default at that time.

import argparse

top_parser = argparse.ArgumentParser()
top_parser.add_argument("--input-dir", type=str)

subparsers = top_parser.add_subparsers()

generate_parser = subparsers.add_parser("generate")
generate_parser.set_defaults(alt_input_dir=".")

process_parser = subparsers.add_parser("process")
process_parser.set_defaults(alt_input_dir="SOME_OTHER_DIR")

args = top_parser.parse_args()
if args.input_dir is None:
    args.input_dir = args.alt_input_dir
del args.alt_input_dir
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thanks! This workaround definitely works. I'm still a bit confused though. Why does the use of the `set_defaults` function with each subparser cause `USE_THIS_DIR` to be ignored completely? – M.A.R. Apr 18 '19 at 22:35
  • That's a good question; I haven't figure out yet if it's intentional or a bug. – chepner Apr 18 '19 at 22:44
  • It was an intentional behavior - by the original developer. See the `_SubParsersAction.__call__` method. – hpaulj Apr 19 '19 at 00:15
  • https://bugs.python.org/issue9351 `argparse set_defaults on subcommands should override top level set_defaults` – hpaulj Apr 19 '19 at 00:21
  • @hpaulj This is slightly different; the subparser default isn't overriding a parser default; it's overriding an explicit use of the option. – chepner Apr 19 '19 at 00:46
  • The code change that I cited affects any value placed in the namespace by the parent. The value set by the subparser replaces it, regardless of how it was set. – hpaulj Apr 19 '19 at 00:53
  • That seems really indefensible. There's no reason a default should override an explicit command-line argument. – chepner Apr 19 '19 at 01:05
  • Looking over the bug/issue discussion, it's possible that the change went beyond what Steven Bethard proposed in 2010, letting the subparser default replace more than the main default. I have cited a number of problems with this code, but it's been this way since 2014. I don't recommend using the same `dest` in both the main and sub parsers. – hpaulj Apr 19 '19 at 01:07
  • Yeah, it's definitely a bug, though not one the maintainers seem eager to fix (or I should say, not one they consider a high priority). – chepner Apr 19 '19 at 01:14
  • There isn't a clean way of distinguishing between a `namespace` value that came from the default versus one set from the commandline, at least not at the point where the subparser is called. There is a `seen_actions` list, but it isn't available at this point. There isn't, as best I can tell, a nuanced middle ground between ignoring the subparser default, and giving it complete control. – hpaulj Apr 19 '19 at 03:12
  • Well, yes; I'd call that a bug in the subparser implementation, though, not an insurmountable obstacle. – chepner Apr 19 '19 at 11:21