Einar
Einar

Reputation: 4933

Python argparse - commands not correctly parsed when using subparsers and parents

In my application, I have a parser like this:

description = ("Cluster a matrix using bootstrap resampling or "
               "Bayesian hierarchical clustering.")
sub_description = ("Use these commands to cluster data depending on "
                   "the algorithm.")

parser = argparse.ArgumentParser(description=description, add_help=False)
subparsers = parser.add_subparsers(title="Sub-commands",
                                   description=sub_description)
parser.add_argument("--no-logfile", action="store_true", default=False,
                    help="Don't log to file, use stdout")
parser.add_argument("source", metavar="FILE",
                    help="Source data to cluster")
parser.add_argument("destination", metavar="FILE",
                     help="File name for clustering results")

Then I add a series of sub parsers like this (using functions because they're long):

setup_pvclust_parser(subparsers, parser)
setup_native_parser(subparsers, parser)

These call (example with one):

def setup_pvclust_parser(subparser, parent=None):

    pvclust_description = ("Perform multiscale bootstrap resampling "
                           "(Shimodaira et al., 2002)")
    pvclust_commands = subparser.add_parser("bootstrap",
        description=pvclust_description, parents=[parent])
     pvclust_commands.add_argument("-b", "--boot", type=int,
                                   metavar="BOOT",
                                   help="Number of permutations",
                                   default=100)

    # Other long list of options...

    pvclust_commands.set_defaults(func=cluster_pvclust) # The function doing the processing

The issue is that somehow the parsing of the command line fails, and I'm sure it's my fault, somewhere. Example when run:

  my_program.py bootstrap --boot 10 --no-logfile test.txt test.pdf

  my_program.py bootstrap: error: too few arguments

As if the parsing is somehow wrong. This behavior disappears if I remove parents=[] in the subparser call, but I'd rather avoid it as it creates massive duplication.

EDIT: Moving the subparsers call after the add_argument calls fixes part of the problem. However, now the parser cannot parse properly the subcommands:

my_program.py bootstrap --boot 100 --no-logfile test.txt test.pdf

my_program.py: error: invalid choice: 'test.txt' (choose from 'bootstrap', 'native')

Upvotes: 1

Views: 1339

Answers (1)

hpaulj
hpaulj

Reputation: 231395

The fundamental problem is that you are confusing the arguments that the parser is supposed to handle, with those that the subparsers should handle. In fact by passing the parser as parent to the subparser you end up defining those arguments in both places.

In addition source and destination are positional arguments, as is the subparsers. If they are all defined in the base parser, their order matters.

I'd suggest defining a separate parent parser

parent = argparse.ArgumentParser(add_help=False)
parent.add_argument("--no-logfile", action="store_true". help="...")
parent.add_argument("source", metavar="FILE", help="...")
parent.add_argument("destination", metavar="FILE", help="...")

parser = argparse.ArgumentParser(description=description)
subparsers = parser.add_subparsers(title="Sub-commands", description=sub_description)
setup_pvclust_parser(subparsers, parent)
setup_native_parser(subparsers, parent)

Upvotes: 3

Related Questions