I have a Python script that runs two sub-commands who accept the same option, --config
. I would like to create a third sub-command that can run the first two subcommands together, sequentially.
Using argparse, I've created a subparser for each sub-command, as well as a third subparser, whose parents are the two sub-commands. Just to clarify:
subcommand1 = subparsers.add_parser('subcommand1')
subcommand1.add_argument('--config', help="The config")
subcommand2 = subparsers.add_parser('subcommand2')
subcommand2.add_argument('--config', help="The config")
wrappercommand = subparsers.add_parser('wrappercommand',
parents=[subcommand1, subcommand2],
conflict_handler='resolve')
Everything works when I run wrappercommand, or subcommand2. However, subcommand1 breaks, with this as the output:
$ run_command.py subcommand1 --config path_to_config.ini
usage: run_command.py subcommand1 config
optional arguments:
help show this help message and exit
config The config
It looks like argparse has turned a keyword arg ("--config") into a positional one ("config"). Is this the expected behavior when conflicting options are resolved by argparse?
I think you are pushing this conflict handler into unintended and untested territory. Normally parents
are standalone parsers that don't get used. They are just a source for Actions
. And conflicts regarding -h
are handled with add_help=False
.
By way of background: with the default conflict_handler
(error) you'd get error messages when creating the wrappercommand
subparser:
argparse.ArgumentError: argument -h/--help: conflicting option string(s): -h, --help
and after adding some add_help=False
, you'd still get:
argparse.ArgumentError: argument --config: conflicting option string(s): --config
The resolve
conflict handler replaces the error messages with some sort of 'resolution'. The script below demonstrates what is happening.
The resolve
handler deleted the option_strings
for the subcommand1
actions , while leaving the actions in place. In effect it turns both into positionals. And since help
has nargs=0
, it is always run. Hence, the help display.
The intention of _handle_conflict_resolve
is to remove evidence of the first argument, so the new argument can be added. That works fine when the conflict is produced by two add_argument
commands with the same option strings. But here the conflict is produced by 'copying' actions from 2 parents. But parent actions are copied by reference, so changes in the 'child' end up affecting the 'parent'.
Some possible solutions:
add the arguments to wrappercommand
directly. This parents
mechanism just adds arguments from the parents to the child. It does not 'run' the parents sequentially.
write your own _handle_conflict_...
function to correctly resolve the conflict.
remove the conflicts so you can use the parents
without using the resolve
handler.
I have filed a bug report with this example http://bugs.python.org/issue22401 :
parent1 = argparse.ArgumentParser(add_help=False)
parent1.add_argument('--config')
parent2 = argparse.ArgumentParser(add_help=False)
parent2.add_argument('--config')
parser = argparse.ArgumentParser(parents=[parent1,parent2],
conflict_handler='resolve')
def foo(parser):
print [(id(a), a.dest, a.option_strings) for a in parser._actions]
foo(parent1)
foo(parent2)
foo(parser)
which produces:
[(3077384012L, 'config', [])]
[(3076863628L, 'config', ['--config'])]
[(3076864428L, 'help', ['-h', '--help']), (3076863628L, 'config', ['--config'])]
Note the missing option_strings
for parent1
, and the matching id
for the other 2. parent1
cannot be used again, either as a parent or a parser.
argparse - Combining parent parser, subparsers and default values is another case where copying parent's actions by reference creates complications (in changing defaults).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With