# Python CLI-Parser improvement for "one of those options"

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()
"--ext1",
type    = __existant_file,
metavar = "FILE",
help    = "input file (format ext1)"
)
"--ext2",
type    = __existant_file,
metavar = "FILE",
help    = "input file (format ext2)"
)
"--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 ?

• @Graipher Well, I do believe this is exactly what I asked, my code is working but I'd like improvement. Maybe it's not clear tho, I'll rewrite a bit Sep 4, 2018 at 14:03
• @Graipher Oh I didn't though I needed a working example as I wasn't expecting any code back but more a general idea, I've edited it :) Sep 4, 2018 at 14:20
• We got a very good meta thread about how to improve question quality if you're interested :-)
– Mast
Sep 4, 2018 at 14:31
• depending on the specific case, a simpler cli interface might be preferable: just pass one or two files as positional parameters (raise ArgumentError if given files in a wrong format). Using 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>]
– jfs
Sep 4, 2018 at 19:05

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
"--paired",
nargs    = '+',
action   = RequiredLen,
required = True,
help     = "input file(s)"
)
# Specify either ext1 or ext2 format
"--ext1",
action = "store_const",
dest   = "format",
const  = "ext1",
help   = "input is ext1 format"
)
"--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)