1

My script is accepting --full, --last and --check using ArgParse. If no option is provided, it just show the help message. But in that message, the parameters appear as optional.

usage: script.py [-h] [--full] [--last] [--check log_file]

If I use the keyword required, then the script will always expect the parameter, which is not correct.

usage: script.py [-h] --full --last --check log_file

So, how can I show something like:

usage: script.py [-h] (--full |--last |--check log_file)

Indicating that the help is optional but that at least one of those parameters is required.

  • For a Python bug/issues patch I've wondered what would be a good usage message for this kind of 'required any' condition. `|` is already used for the mutually exclusive `xor` condition. Testing for conditions like this is easier designing a good API. – hpaulj Jul 02 '15 at 17:07

3 Answers3

1

As @doublep explained in his answer, if you want to use more than one option at a time:

Change the usage message manually to the one you want.

Add the following code from Python argparse: Make at least one argument required:

if not (args.full or args.last or args.check):
    parse.error('[-] Error: DISPLAY_ERROR_MESSAGE')
Community
  • 1
  • 1
Rakholiya Jenish
  • 3,165
  • 18
  • 28
  • I wanted to edit the usage message showed on help, but it seems impossible so I'm going to write a custom message indicating the user to use - - help –  Jul 02 '15 at 12:28
  • `old_usage=parser.format_usage()` gives the formatted usage line. You could edit that, and assign it back with `parser.usage = new_usage`. – hpaulj Jul 02 '15 at 18:04
  • 1
    @hpaulj I found that yesterday as well. At the moment of creating the ArgumentParser object, one optional parameter is `usage`, which is precisely what I was looking for. Came here to publish that as an answer and just saw your comment, so if you want, answer the question and I choose it. –  Jul 04 '15 at 07:53
  • @hpaulj even I would appreciate your answer, since I didn't understand what you said. Also this would be a better chance for me to learn something new in `argparse`. – Rakholiya Jenish Jul 04 '15 at 07:55
1

On the question of customizing the usage:

The parser constructor takes a usage parameter. The immediate effect is to set an attribute:

parser = argparse.ArgumentParser( ... usage=custom_usage...)
print(parser.usage)
# should show None or the custom_usage string

Being a normal Python object attribute, you can change it after the parser was created.

usage_str = parser.format_usage()

The format_usage method ask the parser for create the usage that will be shown in the help (and error messages). If the parser.usage value is None, it formats it from the arguments. If a string, it is used as is (I think it fills in values like %(prog)s).

So you could write a usage string from scratch. Or you could set up the parser, get the current usage string, and edit that to suit your needs. Editing is most likely something you'd do during development, while testing the parser in an IDE. But it could be done on the fly.

A crude example:

In [441]: parser=argparse.ArgumentParser()
In [442]: g=parser.add_mutually_exclusive_group()
In [443]: g.add_argument('--foo')    
In [444]: g.add_argument('--bar')

In [445]: ustr = parser.format_usage()
# 'usage: ipython3 [-h] [--foo FOO | --bar BAR]\n'

In [450]: parser.usage = ustr.replace('[','(').replace(']',')')
In [451]: parser.format_usage()
# 'usage: usage: ipython3 (-h) (--foo FOO | --bar BAR)\n'

I've replaced the [] with () (even on the -h :( ).

For now testing logical combinations of the args attributes is the best choice. Inside the parse_args functions the parser maintains a list (set actually) of arguments that it has seen. That is used to test for required arguments, and for mutually_exclusive_arguments, but it is not available outside that code.

For store_true (or false) arguments, just check their truth value. For others I like to test for the default None. If you use other default values, test accordingly. A nice thing about None is that the user cannot give you that value.

Perhaps the most general way to test for arguments is to count the number of attributes which are not None:

In [461]: args=argparse.Namespace(one=None, tow=2, three=None)
In [462]: ll = ['one','tow','three']
In [463]: sum([getattr(args,l,None) is not None for l in ll])
Out[463]: 1

0 means none are found; >0 at least one present; ==len(ll) all found; >1 violates mutually exclusivity; '==1' for required mutually exclusive.

hpaulj
  • 221,503
  • 14
  • 230
  • 353
0

You can use add_mutually_exclusive_group():

parser = argparse.ArgumentParser ()
group  = parser.add_mutually_exclusive_group (required = True)
group.add_argument ('--foo')
group.add_argument ('--bar')

However, the main effect is that you won't be able to use more than one option at a time.

  • I had that same effect with if-else. I forgot to mention that I'm interested in allowing full or last and check at the same time –  Jul 02 '15 at 12:06