3

I have been writing a command line programs with Argparse for some time now, and I am trying to write it in such a way that when the user supplies the following to the command line:

$python my_script.py -h

A help section (usage) will be printed out that prints out help section of the main parser, as well as brief overviews of the subparsers.

But right now, anytime I type in the previous line into my terminal, I receive no usage and instead get a massive traceback and the following error:

TypeError: expected string or buffer

This error has never occurred to me before with argparse-based command line programs. Furthermore, if I supply the name of one of the subparsers,

$python my_script.py subparserA -h

I get a print-out of the subparser's usage. The same holds true for other subparsers.

So why is it not possible for me to get the usage for the main parser? This worked for me before so I don't know why it's not working now. I really would like for the user to be able to look at an overview of the different subparsers available.

My basic code is currently set up in the following way:

import argparse
import sys

if __name__ == "__main__":
    Parser = argparse.ArgumentParser(prog= "My_program")

    Parser.description= "This program does A and B things."
    subparsers= Parser.add_subparsers(help= "SubparserA does A things and SubparserB does B things", dest='mode')

    subparserA= subparsers.add_parser("subparserA", help= "Additional explanation of what A things entail")

    subparserA.add_arguments("-foo", required=True, help= "foo is needed for SubparserA to work")

    subparserB= subparsers.add_parser("subparserB", help="Additional explanation of what B things entail")

    subparserB.add_argument("-bar", required=True, help= "bar is needed for SubparserB to work")

    args= Parser.parse_args()

    if args.mode == "subparserA":
        ###do things pertinent to subparserA
    elif args.mode== "subparserB":
        ###do things pertinent to subparserB
    else:
        argparse.print_help()
        argparse.ArgumentError("too few arguments")

UPDATE

Here is the full traceback of the error:

Traceback (most recent call last):
  File "my_program.py", line 164, in <module>
    args= Parser.parse_args()
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 1701, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 1733, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 1939, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 1879, in consume_optional
    take_action(action, args, option_string)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 1807, in take_action
    action(self, namespace, argument_values, option_string)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 996, in __call__
    parser.print_help()
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 2340, in print_help
    self._print_message(self.format_help(), file)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 2314, in format_help
    return formatter.format_help()
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 281, in format_help
    help = self._root_section.format_help()
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 211, in format_help
    func(*args)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 485, in _format_text
    return self._fill_text(text, text_width, indent) + '\n\n'
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/argparse.py", line 621, in _fill_text
    text = self._whitespace_matcher.sub(' ', text).strip()
TypeError: expected string or buffer
Bob McBobson
  • 743
  • 1
  • 9
  • 29
  • 1
    The problem occurs while formatting the help. Make sure all text lines, e.g. description, help, are proper strings. Also that the `argparse.py` source file has been corrupted. – hpaulj Oct 27 '17 at 16:36
  • @hpaulj yep, you hit the nail on the head! In my attempts to make my code to adhere to the PEP8, I separated a long string from the `Parser.description` by putting in a comma and then placing the rest of the string on the next line. Clearly it through off my code somehow. Thanks so much! – Bob McBobson Oct 27 '17 at 16:43

2 Answers2

1

You should be using

Parser.print_help()
Parser.error('too few arguments')

That is use methods of the existing Parser object.


When I run your script

1019:~/mypy$ python stack46754855.py 
Traceback (most recent call last):
  File "stack46754855.py", line 10, in <module>
    subparserA= subparsers.add_parser("subparserA", help= "Additional explanation of what A things entail", dest= 'mode')
  File "/usr/lib/python2.7/argparse.py", line 1066, in add_parser
    parser = self._parser_class(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'dest'

dest is a not a valid parameter for the add_parser method. It is a valid, and useful, parameter for add_subparsers.

subparsers= Parser.add_subparsers(dest='mode')

It also objects to the add_arguments method.

After correction those I get:

1022:~/mypy$ python stack46754855.py 
usage: My_program [-h] {subparserA,subparserB} ...
My_program: error: too few arguments

In Py2, subparsers is a required argument. It is optional in Py3 (a bug), allowing the script to run to the invalid argparse.print_help call:

1022:~/mypy$ python3 stack46754855.py 
Traceback (most recent call last):
  File "stack46754855.py", line 27, in <module>
    argparse.print_help()
AttributeError: module 'argparse' has no attribute 'print_help'

With the change I suggested above:

1025:~/mypy$ python3 stack46754855.py 
usage: My_program [-h] {subparserA,subparserB} ...

This program does A and B things.

positional arguments:
  {subparserA,subparserB}
                        SubparserA does A things and SubparserB does B things
    subparserA          Additional explanation of what A things entail
    subparserB          Additional explanation of what B things entail

optional arguments:
  -h, --help            show this help message and exit
usage: My_program [-h] {subparserA,subparserB} ...
My_program: error: too few arguments

The second usage comes from the Parser.error call.


I can't reproduce your

massive traceback and the following error: TypeError: expected string or buffer

I need to see that traceback (or part of it) to see what exactly is raising the error. That's not a normal argparse error; certainly it isn't one that argparse traps and reroutes.


More on the required/not required subparser behavior at How to Set a Default Subparser using Argparse Module with Python 2.7

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • I have also run into the problem of trying to figure out what could possibly be causing this behavior. In any case, would you recommend that I make another subparser that defaults when no subparser is indicated, and whose only job is merely to print out the usage/help sections of the other subparsers? – Bob McBobson Oct 18 '17 at 13:42
  • @BobMcBobson, if you want the optional subparser behavior in Py2, then the 2 stage parser that I suggest in the other question probably is the best. But does that make life any easier for your users? I'm exploring `conda`. It takes 12+ commands, without any hint of them being `optional`. That's normal behavior in large program interfaces. – hpaulj Oct 18 '17 at 16:31
  • @BobMcBobson. Think of the `usage`. How do you tell your user that the subparser command is optional? Current usage is something like `prog [-h] {cmd1,cmd2} ...`. Should it be `prog [-h] [{cmd1,cmd2}] ...`, adding the [] to indicate that providing a `cmd` is optional? Does that add clarity? – hpaulj Oct 18 '17 at 16:39
  • Sorry for late response, work has been topsy turvy lately and I have been busy on other projects. Anyways, rather than telling my user that the subparser command is optional, I want to the user that one of the two subparsers is mandatory for the program to function. Also, I don't understand why adding `[...]` square brackets to the command line indicates that a certain `cmd` is optional. – Bob McBobson Oct 23 '17 at 01:05
  • `argparse` (and possibly other parsers) uses the convention of marking optional arguments with brackets. I just realized that while Py3 `argparse` has left `subparsers` optional for some time, it does not mark it as such. Is that a flaw or not? – hpaulj Oct 23 '17 at 16:33
  • I am using Python-2.7 at the moment, and additionally I have set the subparsers to being positional (albeit mutually exclusive). So do the square brackets mean that a certain command is optional within the command line? How would that affect the program running? – Bob McBobson Oct 25 '17 at 12:28
  • To be clear you should show the relevant code in a new question. – hpaulj Oct 25 '17 at 15:25
  • For various reasons, I can only post as much code as I have written above in my description. But I can post the full traceback of the error I have. Also, I should note that there was an error in the code I posted above, the `dest='mode'` was a parameter set in the `Parser.add_subparsers()` line, not in the `.add_parser()` line. In my original code, the `dest='mode'` was, as you correctly pointed out, always part of the `Parser.add_subparsers()` line. – Bob McBobson Oct 27 '17 at 14:11
1

Use + instead of , for multi line help string in parser.add_argument. If you have split you argument help in multiple lines using ',' then, you will see this issue

parser.add_argument("xml",help=("long help here",
                                " long help second line"))

This will result in above exception

instead

parser.add_argument("xml",help=("long help here" +
                                " long help second line"))
Srikan
  • 2,161
  • 5
  • 21
  • 36