Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 639
toothgrinder Avatar asked Sep 13 '14 00:09

toothgrinder


1 Answers

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

like image 135
hpaulj Avatar answered Sep 30 '22 09:09

hpaulj