6

I am creating a python program using the argparse module and I want to allow the program to take either one argument or 2 arguments.

What do I mean? Well, I am creating a program to download/decode MMS messages and I want the user to either be able to provide a phone number and MMS-Transaction-ID to download the data or provide a file from their system of already downloaded MMS data.

What I want is something like this, where you can either enter in 2 arguments, or 1 argument:

./mms.py (phone mmsid | file)

NOTE: phone would be a phone number (like 15555555555), mmsid a string (MMS-Transaction-ID) and file a file on the user's computer

Is this possible with argparse? I was hoping I could use add_mutually_exclusive_group, but that didn't seem to do what I want.

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('phone', help='Phone number')
group.add_argument('mmsid', help='MMS-Transaction-ID to download')
group.add_argument('file', help='MMS binary file to read')

This gives the error (removing required=True gives the same error):

ValueError: mutually exclusive arguments must be optional

It looks like it wants me to use --phone instead of phone:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--phone', help='Phone number')
group.add_argument('--mmsid', help='MMS-Transaction-ID to download')
group.add_argument('--file', help='MMS binary file to read')

When running my program with no arguments, I see:

error: one of the arguments --phone --mmsid --file is required

This is closer to what I want, but can I make argparse do (--phone --msid) or (--file)?

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • 1
    Could you use [subcommands](https://docs.python.org/2/library/argparse.html#sub-commands)? – mgilson Sep 28 '16 at 15:42
  • @mgilson: I didn't look into that. Is there a way to make a "default" command? Like `./mms.py file.bin` instead of `./mms.py decode file.bin` or `./mms.py 15555555555 abcd` instead of `./mms.py download 15555555555 abcd`. – gen_Eric Sep 28 '16 at 15:45
  • That's a good question. I don't see anything documented :-/... – mgilson Sep 28 '16 at 15:52
  • @mgilson: Using subcommands *does* seem like a decent idea here, that way each one can have its own list of arguments it needs. – gen_Eric Sep 28 '16 at 15:56
  • 2
    You mean something like http://stackoverflow.com/q/17909294/3001761? – jonrsharpe Sep 28 '16 at 16:01
  • @jonrsharpe: Yeah, that's basically the same thing I am trying to do. Subcommands seems like a great idea for this. – gen_Eric Sep 28 '16 at 16:05
  • 1
    The suggested duplicate http://stackoverflow.com/questions/17909294/python-argparse-mutual-exclusive-group, is about nesting groups of optionals. This question is about handling 3 positionals. That question may help, but it is not a duplicate. – hpaulj Sep 28 '16 at 19:18

1 Answers1

4

This is a little beyond the scope of what argparse can do, as the "type" of the first argument isn't known ahead of time. I would do something like

import argparse

p = argparse.ArgumentParser()
p.add_argument("file_or_phone", help="MMS File or phone number")
p.add_argument ("mmsid", nargs="?", help="MMS-Transaction-ID")

args = p.parse_args()

To determine whether args.file_or_phone is intended as a file name or a phone number, you need to check whether or not args.mmsid is None.

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
chepner
  • 497,756
  • 71
  • 530
  • 681
  • This seems, more or less, like exactly what I was trying to do. In my `file` argument, I had `type=argparse.FileType('rb')`. I guess I'll have to manually call `open(args.file_or_phone, 'rb')` if needed. – gen_Eric Sep 28 '16 at 16:00
  • Also, it looks like I can override the text displayed to show `./mms.py (phone mmsid | file)` (and maybe add an "only with phone number" to mmsid) and then process the arguments as needed. – gen_Eric Sep 28 '16 at 16:02
  • 1
    You could define a custom action that, based on the value given, decide if it is a phone number or a file name and act accordingly. However, the argument itself would still be something like `p.add_argument("file_or_phone", action=MyCustomAction, help="...")`. – chepner Sep 28 '16 at 16:03
  • A variant would be to accept one argument with `nargs='+'`, and then parse the list based on length and content. Though this is basically the same as looking at `sys.argv[1:]` directly. – hpaulj Sep 28 '16 at 20:08