argparse conflict resolver for options in subcommands turns keyword argument into positional argument

argparse, python

Solution

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).

Problem

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?

Original source

Related problems