1

The usually way to define a subparser is to do

master_parser = argparse.ArgumentParser()
subparsers = master_parser.add_subparsers()
parser = subparsers.add_parser('sub')
parser.add_argument('--subopt')

and the subparser would be called with

command sub --subopt

I am implementing a package that calls a number of converters. If I use the usual subparser approach, I would have to do

convert ext1_to_ext2 file.ext1 file.ext2 --args

which is both repetitive and error prone because users might call

convert ext1_to_ext3 file.ext1 file.ext2 --args

I would much prefer that the subparser is automatically determined from the master parser so users can use command

convert file.ext1 file.ext2  EXTRA

and argparse would determine subparser ext1_to_ext2 from file.ext1 and file.ext2 and call the subparser ext1_to_ext2 to parse EXTRA. Of course EXTRA here is subparser specific.

I tried to use groups of parameters for each converter (add_argument_group) but the parameters in argument groups cannot overlap and I got a messy list of combined arguments from all parsers, so using subparser seems to be the way to go.

I tried to use parse_known_args with two positional arguments, determine and use the appropriate subparser to parse the remaining args, but it is difficult to provide users a list of converters and their arguments from help message.

Is there a way to do this?

user2283347
  • 670
  • 8
  • 12
  • Could you use [`parse_known_args`](https://docs.python.org/3.5/library/argparse.html#partial-parsing)? That would let you parse the first part of the arguments, evaluate the proper subparser yourself, then pass the remaining arguments to the proper subparser. – SethMMorton Dec 12 '16 at 16:30
  • This is the approach I am taking right now, but I am struggling on how to provide help message for users. It seems that I need to hijack `convert file.ext1 file.ext2 -h` to print help message from another parser, but still I cannot list all available converters from `convert -h` (perhaps an `epilog` could work). – user2283347 Dec 12 '16 at 16:38
  • The kind of logic that you are asking for is unreasonable for `argparse`. Either parse `sys.argv` directly or get the values as simple strings via `argparse`, and then deduce what action you should take. – hpaulj Dec 12 '16 at 18:51

2 Answers2

1

Inferring the subparser to use is tricky, since it requires reimplementing a lot of the logic used by argparse itself while you are examining each of the following arguments.

A simpler approach is to take the subparser command, which subsquently allows you to "typecheck" the following arguments to ensure they use the correct argument. For example

# This allows a file name ending with any of the given extensions,
# or allows "file.1" in place of "file.1.jpg"
def jpeg_file(s):
    for ext in ("jpg", "jpeg"):
        if s.endswith(ext) or os.path.exists("%s.%s" % (s, ext)):
            return s
    raise argparse.ArgumentTypeError()

def tiff_file(s):
    # similar to jpeg_file

master_parser = argparse.ArgumentParser()
subparsers = master_parser.add_subparsers()
jpg_to_tiff_parser = subparsers.add_parser('sub')
jpg_to_tiff_parser = parse.add_argument('jpg', type=jpg_file)
jpg_to_tiff_parser = parse.add_argument('tiff', type=tiff_file)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • But the command line would still be `convert sub file.jpg file.tiff`. I just had a wonderful (?) idea, how about hijack `sys.argv`? I can check `sys.argv[1]` and `sys.argv[2]`, insert appropriate subparser name before `sys.argv[1]`, and call the `master_parser` with all the subparsers. The `-h` message would be a bit off though. – user2283347 Dec 12 '16 at 16:56
  • No obvious problems to that approach are coming to mind, although the first two positional arguments are not necessarily `sys.argv[1]` and `sys.argv[2]`. – chepner Dec 12 '16 at 16:59
0

This gives you a little more control in my opinion. It's along the way towards what your asking for. Just add file extension checking for your needs.

#prog.py
topParser=argparse.ArgumentParser()

subParsers = topParser.add_subparsers(
    title='SubCommands', 
    description='Use "prog.py <subCommand> (-h | --help)" to learn more about each subcommand', 
    help='I can do stuff')

subParser1 = subParsers.add_parser('use1', help="Use1 does one thing")
subParser2 = subParsers.add_parser('use2', help='Use2 does another thing')

subParser1.add_argument(
    '-f','--first-arg-for-use1',
    help="A text file",
    required=True
    )

subParser1.add_argument(
    '-s','--second-arg-for-use1',
    help="An encoding",
    required=True
    )

subParser2.add_argument(
    '-f','--first-arg-for-use2',
    help="An image format",
    required=True
    )

args = topParser.parse_args()
print(args)

If nothing else, it shows how to handle the help text for the different layers.