18

Intro

I'm trouble for a school project. I'm making a testsuit and i'm needing bot a configuration generation interface and a test runner. For that i used the library argparse and two subparsers cgi and run

The issue itself

So here is the failing code section:

def CGI(args):
    print("CGI: Work In Progress")
    exit(0)


def runTest(args):
    print("Run: Work in Progress")
    exit(0)

parser = argparse.ArgumentParser()
subparser = parser.add_subparsers()
cgi = subparser.add_parser("cgi", help="CSV Generator Interface")
run = subparser.add_parser("run", help="Test running")
verbosity = parser.add_argument_group("Verbosity").add_mutually_exclusive_group()
check = run.add_argument_group("Checks")

# Arguments
#Run parser
run.set_defaults(func=runTest)

# Run argument declaration ...

# Verbosity argument declaration ...

# Check argument declaration ...

#CGI
cgi.set_defaults(func=CGI)

args = parser.parse_args()
args.func(args) # Error is here

Whenever i run this code i have the following error:

  File "/home/thor/Projects/EPITA/TC/test/test.py", line 44, in main
    args.func(args)
AttributeError: 'Namespace' object has no attribute 'func'

Python version

$ python -V
Python 3.6.4

Argparse version

$ pip show argparse
Name: argparse
Version: 1.4.0
Summary: Python command-line parsing library
Home-page: https://github.com/ThomasWaldmann/argparse/
Author: Thomas Waldmann
Author-email: tw@waldmann-edv.de
License: Python Software Foundation License
Location: /usr/lib/python3.6/site-packages
Requires: 

EDIT

If i install argparse manually it work sudo pip install argparse. But is there any native solution. I'm not sure it will work on school's computers (we can' install packages)

EDIT 2

OK my bad i've been a total idiot i didn't rewrited my running script so i forgot to input run or cgi

Thanks for reading my message and for your future help :)

Biboozz
  • 1,096
  • 2
  • 9
  • 18
  • 1
    Yep, exactly. You learned the hard way that subparsers (including their defaults) are only run when their respective command is present in the arguments. Note that you can answer your own questions. – dhke Feb 06 '18 at 17:05
  • The recommended solution is `p1 = parser.add_subparsers(required=True, dest='cmd')` See https://bugs.python.org/issue16308#msg350415 – guettli Sep 03 '21 at 12:24
  • Just to add to above comment, as mentioned by "makeiteasy" below Python version 3.6 onwards 'required' has to be used explicitly: subparser.required = True – kwick Jun 19 '23 at 04:33

6 Answers6

17

This is a known bug in the Python 3 version of argparse (https://bugs.python.org/issue16308). In Python 2, if the script is called with no arguments whatsoever (i.e., no subcommand), it exits cleanly with "error: too few arguments". In Python3, however, you get an unhandled AttributeError. Luckily, the workaround is pretty straightforward:

    try:
        func = args.func
    except AttributeError:
        parser.error("too few arguments")
    func(args)
simleo
  • 2,775
  • 22
  • 23
17
parser = ArgumentParser()
parser.set_defaults(func=lambda args: parser.print_help())

imho better than try..except

  • This is expressive (it tells you what it does) and does exactly what you want. Only thing I'd say is that you can replace `func=lambda args:` with `func=lambda _:` because `args` isn't used. – LondonRob Oct 13 '22 at 12:47
3

Another solution could be:

if len(args.__dict__) <= 1:
    # No arguments or subcommands were given.
    parser.print_help()
    parser.exit()
Nehal J Wani
  • 16,071
  • 3
  • 64
  • 89
3

You would have to make subparsers required to call the script without arguments. To do that, you have to specify the dest and required arguments for the parser.add_subparsers:

parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(dest='cmd', required=True)

Note that for Python 3.6 and earlier there's no required argument and you have to set it explicitly for the subparser object:

subparser.required = True

Details are available in this SO answer: Argparse with required subparser

makeiteasy
  • 766
  • 7
  • 11
  • For anyone reading this answer (which is the official answer according to https://bugs.python.org/issue16308#msg350415 and should therefore be upvoted), I suggest adding also `metavar='cmd'` to `add_subparsers`. Without metavar: `usage: jobs.py [-h] {run,list,show}`. With metavar: `usage: jobs.py [-h] cmd`. As the error message when no cmd is provided is "error: the following arguments are required: cmd", I prefer to have `cmd` explicitly displayed in usage. The available choices for `cmd` are displayed in the full help anyway. – Samuel Verschelde May 11 '22 at 10:08
1

Or, a combination of @simleo and @nehaljwani's answers:

    # Parse the arguments and call the sub command
    args = parser.parse_args()
    try:
        args.func(args)
    except AttributeError:
        parser.print_help()
        parser.exit()
CpILL
  • 6,169
  • 5
  • 38
  • 37
  • exactly, was going to post the same, but without passing args to func, just args.func inside the try – Matrix Aug 26 '21 at 12:38
1

This error only happened when you run 'python script.py' directly. 'python script.py --help' works fine.

Add

args = parser.parse_args()
try:
    args.func(args)
except AttributeError:
    parser.print_help()
    parser.exit()

will help you handle this case of running 'python script.py' directly. It resolved my issue, thanks very much!

Rae Sun
  • 11
  • 1