0

I have an argparse program with multiple subparsers. IF the value of a top-level argument is not specified, I would like it to depend on the subparser selection. This can be partially accomplished by using set_defaults() on each subparser. However, the passed value does NOT act like a true default in that it takes precedence even when the top-level argument is explicitly provided.

Am I missing something in the argparse module that would allow the subparser-specific defaults to yield to the top-level parser? Below is a toy example which demonstrates my desired use case

import argparse

# Top-level parser
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--time', type=str)

# Add subparsers with options
actions = parser.add_subparsers(dest='action', metavar='action')
actions.required = True
eat = actions.add_parser('eat')
eat.add_argument('-f', '--food', type=str, default='pizza')
order = actions.add_parser('order')
order.add_argument('-i', '--item', type=int, required=True)

# Attempt to set main parser's --time depending on subparser selection
eat.set_defaults(time='now')
order.set_defaults(time='later')

# Tests that work as expected
print(parser.parse_args(['order', '--item', 0]))  # Namespace(action='order', item=0, time='later')
print(parser.parse_args(['eat']))                 # Namespace(action='eat', food='pizza', time='now')

# Tests that DO NOT work
# Actual result:  the --time flag of `parser` is ignored in preference of each subparser's default
# Desired result: explicit usage of --time should override the subparser defaults like the comments below
# Namespace(action='order', item=0, time='before')
# Namespace(action='eat', food='pizza', time='after')
print(parser.parse_args(['--time', 'before', 'order', '--item', 0]))
print(parser.parse_args(['--time', 'after', 'eat']))
Addison Klinke
  • 1,024
  • 2
  • 14
  • 23
  • https://stackoverflow.com/questions/62904585/support-global-arguments-before-or-after-sub-command-in-argparse and other SO discuss subparsers and defaults. Basically defaults set by the subparser override anything set by the main. – hpaulj Aug 14 '20 at 22:20
  • I see `argparse.SUPPRESS` mentioned in the thread you linked (among others). Is there no way to apply that flag the each `kwarg` in `set_defaults`? – Addison Klinke Aug 14 '20 at 23:07
  • The subpaerser gets a fresh namespace (it's inherited from the main). And all values set by the subparser, default or otherwise, are written back to the main. I could reference the bug/issue that gave subparser priority if you want. – hpaulj Aug 14 '20 at 23:13
  • `parser.set_defaults(time=argparse.SUPPRESS)` might work, that is, run without error. I haven't tried it, and can't think a circumstance where it would be useful. – hpaulj Aug 14 '20 at 23:35
  • Often it is simpler to use different `dest` names in the main and sub parsers, and reconcile the values after parsing. Optional's flags can still be the same (and metavars). – hpaulj Aug 15 '20 at 03:26

1 Answers1

0

It seems that the sub-parser args, overwrite the Namespace
entries created from the top-level parser, because they have
the same name. So the Namespace ends up with only one 'time'
entry (instead of one for the top-level parser, and one for
the sub-parser); its value is the last value written to it:
that of the sub-parser.

Alternatively, maybe only use the arg in the main parser,
and alter it's default value (in the main parser),
before using a specific subparser?

import argparse

parserMain = argparse.ArgumentParser()
parserMain.add_argument('--arg', type=str)
subparsers = parserMain.add_subparsers()

parserX = subparsers.add_parser('x')
# This would overwrite the value of 'arg' in the Namespace:
#parserX.set_defaults(arg='defaultValForX')
# So, set default in main parser, before using sub-parser:
parserMain.set_defaults(arg='defaultValForX')
print(parserMain.parse_args(['x']))
print(parserMain.parse_args(['--arg', 'explicitVal', 'x']))

parserY = subparsers.add_parser('y')
# This would overwrite the value of 'arg' in the Namespace:
#parserY.set_defaults(arg='defaultValForY')
# So, set default in main parser, before using sub-parser:
parserMain.set_defaults(arg='defaultValForY')
print(parserMain.parse_args(['y']))
print(parserMain.parse_args(['--arg', 'explicitVal', 'y']))