4

(Run on python 3.6.0)

TL;DR

Usage: prog.py {caesar | vigenere} [key]

parser = argparse.ArgumentParser()
subp = parser.add_subparsers()
caesar = subp.add_parser("caesar", aliases=["c"], allow_abbrev=True)
args = parser.parse_args()
$ python prog.py caes 123
prog.py: error: invalid choice: 'caes' (choose from 'caesar', 'c')

Why is the subparser abbreviation invalid even with allow_abbrev=True?


LONG VER

Basically, having an issue getting argparse to accept abbreviated subparsers names/aliases.

Here's the code:

Usage: prog.py [caesar] [key]

import sys, argparse

def main(argv):
parser = argparse.ArgumentParser
         (description="runs text through X cipher")
subp = parser.add_subparsers\
         (help="sub-command help")

#<ArgumentParser object>
caesar = subp.add_parser\
         ("caesar", aliases=["c"], allow_abbrev=True)
caesar.add_argument\
         ("key", metavar = "key (any integer)",\
          type = int, default = 0)


args = parser.parse_args()
print(caesar)

if __name__ == "__main__":
sys.argv = list(str(c).lower() for c in sys.argv[0:])
main(sys.argv)

So from the code above, it should be expected that any of the following should be accepted:

- "Caesar" or "caesar"
- "C" or "c" 
- Any abbreviation in between "c" and "caesar" 

So here's the problem:

This works: $ python prog.py c 123 O

This gives an error: $ python prog.py caes 123 X

prog.py: error: invalid choice: 'cae' (choose from 'caesar', 'c')

Now here's the confusing part.

According to the argparse doc:

ArgumentParser supports the creation of such sub-commands with the add_subparsers() method. The add_subparsers() method is normally called with no arguments and returns a special action object. This object has a single method, add_parser(), which takes a command name and any ArgumentParser constructor arguments, and returns an ArgumentParser object that can be modified as usual.

  1. okay, so any object created with add_subparser() can create its own ArgumentParser objectwith object.add_parser() right?

  2. ...which means this newly created ArgumentParser object should be able to accept any ArgumentParser arguments yeah?

ArgumentParser definition:

class 
argparse.ArgumentParser(
prog=None, usage=None, 
description=None, epilog=None, 
parents=[],formatter_class=argparse.HelpFormatter, 
prefix_chars='-',fromfile_prefix_chars=None, 
argument_default=None,conflict_handler='error', 
add_help=True, allow_abbrev=True)

Create a new ArgumentParser object. All parameters should be passed as keyword arguments. Each parameter has its own more detailed description below, but in short they are:

allow_abbrev - Allows long options to be abbreviated if the abbreviation is unambiguous.

(default: True)

Changed in version 3.5: allow_abbrev parameter was added.

(this was on python 3.6.0)


Thanks in advance, guys

cdpp
  • 152
  • 2
  • 9
  • The abbreviation parameter applies to the flagged arguments of the parser, not to the names or aliases that invoke the subparser. – hpaulj Nov 10 '17 at 16:45
  • 1
    Note that allow_abbrev default is true. What has really been added with 3.6 is the ability to turn it off. Earlier versions always accepted abbrev. But it never applied to argument `choices`, just to the `--foo` like flags. – hpaulj Nov 10 '17 at 16:50

2 Answers2

3

A patch to allow abbreviations of the subparser names was implemented, but then withdrawn when it proved to be buggy:

Issue 12713: allow abbreviation of sub commands by users

Allowing users to turn off abbreviations for long options is a different issue, handled in

Issue 14910: argparse: disable abbreviation

Two different parts of the code.

allow_abbrev - Allows long options to be abbreviated if the abbreviation is unambiguous.

A long option is created with:

caesar.add_argument('-f','--foobar')

With the default allow_abbrev value, this would work with '-f', '--foo', and '--foobar'. The long_option in this case is '--foobar'. With it False, '--foo' would not work.

It's the main parser that decides whether c or caesar or cae are valid subparser commands (via subp, the special action object created by parser.add_subparsers). This behaves more like a positional with choices.

parser.add_argument('foo', choices = ['c', 'caesar'])
cdpp
  • 152
  • 2
  • 9
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • Thanks for the thorough response! This definitely beats trying to figure out the doc. – cdpp Nov 18 '17 at 09:33
0

The error I'm getting is this:

usage: [-h] {caesar,c} ...
: error: unrecognized arguments: a e s

Hinting that abbreviations are supposed to be composable in the sense that two different abbreviations "c" and "a" can be referenced by passing ca.

What is it that should really happen there? ca is both a combination between the c and the (non-existing) a short form, as well as an abbreviation. Which should the parser prefer? Therefore this question had to be resolved explicitly when the library was designed: For predictability, you just cannot have both.

That being said, maybe you can tweak the result by passing conflict_handler='resolve'? https://docs.python.org/3/library/argparse.html#allow-abbrev

mar77i
  • 90
  • 6
  • What are you giving the parser? `parser.parse_args(list('caes'])`? Looks like the `argv` list is `['c','a','e','s']`. – hpaulj Nov 11 '17 at 07:52
  • actually, I was talking about `parser.parse_args(["caes"])`. The thing here is that it should be possible to combine short command line flags. Exactly the same as with POSIX `getopt.h`, think `tr -cd`, `grep -vERF`, or the famous `tar xvzf`. – mar77i Nov 11 '17 at 11:38
  • 1
    Short options can be combined, as in `python prog.py -caes`. But this does not apply to subparser commands. Only one 'word' works. – hpaulj Nov 11 '17 at 13:08