I was wondering if there was a better way to implement my cli parser.
For context: I need to pass to my script either a file in one of two formats or two files in a specific format.
It may be easier to understand like this:
file.ext1 OR file.ext2 OR (file_1.ext2 AND file_2.ext2)
I've used python argparse add_mutually_exclusive_group
successfully and it looks like this:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"--ext1",
type = __existant_file,
metavar = "FILE",
help = "input file (format ext1)"
)
group.add_argument(
"--ext2",
type = __existant_file,
metavar = "FILE",
help = "input file (format ext2)"
)
group.add_argument(
"--paired",
nargs = 2,
type = __existant_file,
metavar = ("MATE_1", "MATE_2"),
help = "input file (two files format ext2)"
)
args = parser.parse_args()
print(args)
if args.ext1 is not None:
file = args.ext1
elif args.ext2 is not None:
file = args2.ext2
else:
file = args.paired[0]
file2 = args.paired[1]
Which is used as:
python script.py --ext1 file
OR
python script.py --ext2 file
OR
python script.py --paired file_1 file_2
Which is working but not really smooth. Do you have any lead of how I can improve the CLI parser ?
1 Answer 1
Okay, I found a better way
import argparse
parser = argparse.ArgumentParser()
class RequiredLen(argparse.Action):
def __call__(self, parser, args, values, option_string=None):
if not 1 <= len(values) <= 2:
msg = f"argument {self.dest} requires 1 or 2 arguments"
raise argparse.ArgumentTypeError(msg)
setattr(args, self.dest, values)
# Just give me 1 or 2 files
parser.add_argument(
"--paired",
nargs = '+',
action = RequiredLen,
required = True,
help = "input file(s)"
)
# Specify either ext1 or ext2 format
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"--ext1",
action = "store_const",
dest = "format",
const = "ext1",
help = "input is ext1 format"
)
group.add_argument(
"--ext2",
action = "store_const",
dest = "format",
const = "ext2",
help = "input is ext2 format"
)
args = parser.parse_args()
print(args)
This way I can later just use something like:
def process_ext1(l: list):
print("ext1 file(s)")
for i in l:
print(i)
def process_ext2(l: list):
print("ext2 file(s)")
for i in l:
print(i)
process_format = { 'ext1': process_ext1, 'ext2': process_ext2 }
process_format[args.format](args.paired)
docopt
, you would specify the alternatives on two/three lines. Here's a variant with the two line usage:command <file.ext1>
$command <file1.ext2> [<file2.ext2>]
\$\endgroup\$