I'm encountering a simple use-case that argparse
surprisingly doesn't seem to handle. I would like to have a required positional argument in addition to having multiple subparsers. The rationale is that this CLI application supports one use-case very readily with a concise syntax, with more granular options being nested under subcommands. For this post, imagine that I am writing a CLI application for tagging files with the syntax:
# tag a file
argparse_test.py <file> [<tags>...]
# list tags
argparse_test.py tags <file>
Usage example:
$ argparse_test.py ./cat.jpg cute funny
$ argparse_test.py tags ./cat.jpg
> cute, funny
My implementation is as follows:
import argparse
# initialize main parser
parser = argparse.ArgumentParser()
parser.add_argument('file', help="The file to index.")
parser.add_argument('tags', nargs='*', help="Tags to append.")
# initialize `tags` subparser
subparsers = parser.add_subparsers()
tag_subparser = subparsers.add_parser("tags")
tag_subparser.add_argument('file', help="The file to list tags for.")
# 1. Failing test case
args = parser.parse_args(["./cat.jpg", "cute", "funny"])
print(args)
# 2. Failing test case (comment out #1 to reach this)
args = parser.parse_args(["tags", "./cat.jpg"])
print(args)
Surprisingly, this fails in both cases!
For the first test case:
usage: argparse_test.py [-h] file [tags ...] {tags} ...
argparse_test.py: error: argument {tags}: invalid choice: 'funny' (choose from 'tags')
For the second test case:
usage: argparse_test.py tags [-h] file
argparse_test.py tags: error: the following arguments are required: file
Commenting out either the main parser or the subparser will fix one (but not both) test case every time. It looks like this issue stems from some conflict argparse
has in dealing with required positionals and subcommands.
Is there any known way to implement support for this CLI syntax via argparse
in Python 3.8+?
(BTW, I've tested this on Python 3.9, 3.10, and 3.11, all of which share the same behavior).