0

having a code like below

>>> import argparse
>>> parser=argparse.ArgumentParser()
>>> parser.add_argument('must_have', help='this is a required arg')
>>> parser.add_argument('-o', '--optional', help='some optional arg')
>>> parser.add_argument('--others', nargs=argparse.REMAINDER, help='some other options')

and displaying help will display sth like:

usage: [-h] [-o OPTIONAL] [--others ...] must_have

positional arguments:
  must_have             this is a required arg

optional arguments:
  -h, --help            show this help message and exit
  -o OPTIONAL, --optional OPTIONAL
                        some optional arg
  --others ...          some other options

which is clearly misleading, cause passing arguments in the order like in help will cause an error:

>>> parser.parse_args(['-o', 'foo', '--others', 'foo=1', 'bar=2', 'ye_mandatory_arg'])
usage: [-h] [-o OPTIONAL] [--others ...] must_have
: error: the following arguments are required: must_have

which of course sounds right. and to make it work we have to pass the mandatory positional arg before the one using REMAINDER:

>>> parser.parse_args(['-o', 'foo', 'ye-mandatory-arg', '--others', 'foo=1', 'bar=2'])
Namespace(must_have='ye-mandatory-arg', optional='foo', others=['foo=1', 'bar=2'])

Is it even possible to fix the help message?

murison
  • 3,640
  • 2
  • 23
  • 36
  • The accepted answer in this post seems to be what you're looking for: https://stackoverflow.com/questions/26985650/argparse-do-not-catch-positional-arguments-with-nargs/26986546#26986546. Let me know if it is not. – Cameron Riddell Jan 19 '23 at 15:49
  • I was going to suggest a custom `usage` parameter. My answer in the suggested link mentions that. The default usage format places all `positionals` at the end without checking for cases like yours. – hpaulj Jan 19 '23 at 16:07

1 Answers1

0

I have come across this issue before as well. Creating a custom usage string that makes the help message less misleading is certainly an option, however I always try to make the cli as user friendly as possible.

Since adding the positional argument at the end is common practice in CLIs I reconfigure it to make such usage possible by making the positional argument optional and giving it a default of an empty string, then checking for the empty string after calling parse_args and if an empty string is found then I assume that the last element of the list arguments is the positional argument.

For example:

import argparse
parser=argparse.ArgumentParser()
parser.add_argument('must_have', help='this is a required arg', default="", nargs="?")
parser.add_argument('-o', '--optional', help='some optional arg')
parser.add_argument('--others', nargs=argparse.REMAINDER, help='some other options')

args = parser.parse_args()
if not args.must_have:
    positional = args.others.pop(-1)
    args.must_have = positional

print(args)

With the above you can run:

myscript.py -o optional --other 1 2 3 4 positional

output:

Namespace(must_have='positional', optional='optional', others=['1', '2', '3', '4'])

Any additional error checking is always beneficial as well.

Alexander
  • 16,091
  • 5
  • 13
  • 29