Python argparse: args has no attribute func - python

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 :)

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)

parser = ArgumentParser()
parser.set_defaults(func=lambda args: parser.print_help())
imho better than try..except

Another solution could be:
if len(args.__dict__) <= 1:
# No arguments or subcommands were given.
parser.print_help()
parser.exit()

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

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()

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!

Related

argparse error with parsing required arguments

I have a script saved as workspace.py
import argparse
import os
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('title', type=str, help="Will be displayed as the title")
parser.add_argument('-f', '--folder', help='Point to the folder you want to read from (defaults to current folder in command prompt)', type=str, default=os.getcwd())
args = parser.parse_args()
print(args)
someFunction(args.folder, args.title)
Which I call from terminal with:
workspace.py myTitle
Resulting in the error
workspace.py: error: the following arguments are required: title
I have no idea why this is happening because I supply "myTitle" in the terminal. If I specify a default= for the title argument it works perfectly with that value. The part that is throwing me is it doesn't even get to the print(args) so I cannot see what the program thinks is what, but instead fails at args = parser.parse_args()
I tried to even redo the exact example at: https://docs.python.org/2/howto/argparse.html#introducing-positional-arguments (copied below)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
print args.echo
Running
workspace.py hello
Results in (after adding parenthesis to the print for 3.X)
workspace.py: error: the following arguments are required: echo
Is there something I'm missing? Why does it not just print "hello"? Is there some Python 3 specific syntax I'm missing or something?
I've gotten it to work if I run python workspace.py someString instead of workspace.py someString. I do not understand why this version works, since command prompt obviously recognizes it as Python and runs it correctly until args = parser.parse_args(). There were no errors like 'workspace.py' is not recognized as an internal or external command, operable program or batch file. There was no problem importing modules either. Consider the below command prompt session if you are running into a similar error. Maybe you will simply have to include python in your commands like I have to...
C:\Users\rparkhurst\PycharmProjects\Workspace>workspace.py MyTitle
usage: workspace.py [-h] [-f FOLDER] title
workspace.py: error: the following arguments are required: title
C:\Users\rparkhurst\PycharmProjects\Workspace>python workspace.py MyTitle
Namespace(folder='C:\\Users\\rparkhurst\\PycharmProjects\\Workspace', title='MyTitle')

support version without providing required parameters [duplicate]

I have the following code (using Python 2.7):
# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=False)
parser_shared.add_argument('--version', action='store_true')
# the main parser, inherits from `parser_shared`
parser = argparse.ArgumentParser(description='main', parents=[parser_shared])
# several subcommands, which can't inherit from the main parser, since
# it would expect subcommands ad infinitum
subparsers = parser.add_subparsers('db', parents=[parser_shared])
...
args = parser.parse_args()
Now I would like to be able to call this program e.g. with the --version appended to the normal program or some subcommand:
$ prog --version
0.1
$ prog db --version
0.1
Basically, I need to declare optional subparsers. I'm aware that this isn't really supported, but are there any workarounds or alternatives?
Edit: The error message I am getting:
$ prog db --version
# works fine
$ prog --version
usage: ....
prog: error: too few arguments
According to documentation, --version with action='version' (and not with action='store_true') prints automatically the version number:
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
FWIW, I ran into this also, and ended up "solving" it by not using subparsers (I already had my own system for printing help, so didn't lose anything there).
Instead, I do this:
parser.add_argument("command", nargs="?",
help="name of command to execute")
args, subcommand_args = parser.parse_known_args()
...and then the subcommand creates its own parser (similar to a subparser) which operates only on subcommand_args.
This seems to implement the basic idea of an optional subparser. We parse the standard arguments that apply to all subcommands. Then, if anything is left, we invoke the parser on the rest. The primary arguments are a parent of the subcommand so the -h appears correctly. I plan to enter an interactive prompt if no subcommands are present.
import argparse
p1 = argparse.ArgumentParser( add_help = False )
p1.add_argument( ‘–flag1′ )
p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )
( init_ns, remaining ) = p1.parse_known_args( )
if remaining:
p2.parse_args( args = remaining, namespace=init_ns )
else:
print( ‘Enter interactive loop’ )
print( init_ns )
As discussed in http://bugs.python.org/issue9253 (argparse: optional subparsers), as of Python 3.3, subparsers are now optional. This was an unintended result of a change in how parse_args checked for required arguments.
I found a fudge that restores the previous (required subparsers) behavior, explicitly setting the required attribute of the subparsers action.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True # the fudge
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
See that issue for more details. I expect that if and when this issue gets properly patched, subparsers will be required by default, with some sort of option to set its required attribute to False. But there is a big backlog of argparse patches.
Yeah, I just checked svn, which is used as an object example in the add_subparsers() documentation, and it only supports '--version' on the main command:
python zacharyyoung$ svn log --version
Subcommand 'log' doesn't accept option '--version'
Type 'svn help log' for usage.
Still:
# create common parser
parent_parser = argparse.ArgumentParser('parent', add_help=False)
parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0')
# create the top-level parser
parser = argparse.ArgumentParser(parents=[parent_parser])
subparsers = parser.add_subparsers()
# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo', parents=[parent_parser])
Which yields:
python zacharyyoung$ ./arg-test.py --version
arg-test.py 2.0
python zacharyyoung$ ./arg-test.py foo --version
arg-test.py foo 2.0
While we wait for this feature to be delivered, we can use code like this:
# Make sure that main is the default sub-parser
if '-h' not in sys.argv and '--help' not in sys.argv:
if len(sys.argv) < 2:
sys.argv.append('main')
if sys.argv[1] not in ('main', 'test'):
sys.argv = [sys.argv[0], 'main'] + sys.argv[1:]
Although #eumiro's answer address the --version option, it can only do so because that is a special case for optparse. To allow general invocations of:
prog
prog --verbose
prog --verbose main
prog --verbose db
and have prog --version work the same as prog --verbose main (and prog main --verbose) you can add a method to Argumentparser and call that with the name of the default subparser, just before invoking parse_args():
import argparse
import sys
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_main(args):
print 'main verbose', args.verbose
def do_db(args):
print 'db verbose:', args.verbose
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
subparsers = parser.add_subparsers()
sp = subparsers.add_parser('main')
sp.set_defaults(func=do_main)
sp.add_argument('--verbose', action='store_true')
sp = subparsers.add_parser('db')
sp.set_defaults(func=do_db)
parser.set_default_subparser('main')
args = parser.parse_args()
if hasattr(args, 'func'):
args.func(args)
The set_default_subparser() method is part of the ruamel.std.argparse package.

mypy gets "namespace has no attritube XXX" error on argparse

I am trying to use mypy for type annotation. I wrote a simple test.py
import argparse
args = None
parser = argparse.ArgumentParser()
parser.add_argument('--dev', '-d', action='store_true', required=False)
args = parser.parse_args()
args.dev = True
After I ran mypy test.py, I got an error:
test.py:8: error: "Namespace" has no attribute "dev"
My assumption is that attritubes of Namespace class is dynamically created so they cannot be detected by mypy? I am able to get ride of this error message after replacing the last line with setattr(args, 'dev', True) but this does not look right... Can someone help me understand this problem? I am using Python 3.6.1.

Python/argparse: How to make an argument (i.e. --clear) don't warn me "error: too few arguments"?

I have this cmd line parsing stuf and I need to make "--clear" a valid unique parameter. However I'm getting an "Error: Too few arguments" when I only use "--clear" as an unique parameter.
parser = argparse.ArgumentParser(
prog="sl",
formatter_class=argparse.RawDescriptionHelpFormatter,
description="Shoot/Project launcher application",
epilog="")
parser.add_argument("project", metavar="projectname",
help="Name of the project/shot to use")
parser.add_argument("-p", metavar="project_name",
help="Name of the project")
parser.add_argument("-s", metavar="shot_name",
help="Name of the shot")
parser.add_argument("--clear",action='store_true',
help="Clear the information about the current selected project")
parser.add_argument("--test",
help="test parameter")
args=parser.parse_args()
Any ideas? Thanks
Update:
Trying to answer some questions of the comments.
When I launch the app like:
sl project
it works fine.
But if I launch it like:
sl --clear
I got a simple "sl: error: too few arguments"
The --clear argument is not the problem here; project is a required argument.
If you should be able to call your program without naming a project, make project optional by adding nargs='?':
parser.add_argument("project", metavar="projectname",
help="Name of the project/shot to use", nargs='?')
If it is an error to not specify a project name when other command-line switches are used, do so explicitly after parsing:
args = parser.parse_args()
if not args.clear and args.project is None:
parser.error('Please provide a project')
Calling parser.error() prints the error message, the help text and exits with return code 2:
$ python main.py --clear
Namespace(clear=True, p=None, project=None, s=None, test=None)
$ python main.py
usage: sl [-h] [-p project_name] [-s shot_name] [--clear] [--test TEST]
[projectname]
sl: error: Please provide a project
Add default values for each argument (--clear is no exception)

Set a default choice for optionparser when the option is given

I have a python option parsers that parses an optional --list-something option.
I also want the --list-something option to have an optional argument (an option)
Using the argument default="simple" does not work here, otherwise simple will always
be the default, not only when --list-something was given.
from optparse import OptionParser, OptionGroup
parser = OptionParser()
options = OptionGroup(parser, "options")
options.add_option("--list-something",
type="choice",
choices=["simple", "detailed"],
help="show list of available things"
)
parser.add_option_group(options)
opts, args = parser.parse_args()
print opts, args
The above code is producing this:
[jens#ca60c173 ~]$ python main.py --list-something simple
{'list_something': 'simple'} []
[jens#ca60c173 ~]$ python main.py --list-something
Usage: main.py [options]
main.py: error: --list-something option requires an argument
[jens#ca60c173 ~]$ python main.py
{'list_something': None} []
But I want this to hapen:
[jens#ca60c173 ~]$ python main.py --list-something simple
{'list_something': 'simple'} []
[jens#ca60c173 ~]$ python main.py --list-something
{'list_something': 'simple'} []
[jens#ca60c173 ~]$ python main.py
{'list_something': None} []
I would like something that works out of the box in python 2.4 up till 3.0 (3.0 not included)
Since argparse is only introduced in python 2.7 this is not something I could use.
Optparse does not have any options for doing this easily. Instead you'll have to create a custom callback for your option. The callback is triggered when your option is parsed, at which point, you can check the remaining args to see if the user put an argument for the option.
Check out the custom callback section of the docs, in particular Callback example 6: variable arguments.
There is no default for the Optparser in python.
However, you can use the follwing -
# show help as default
if len(sys.argv) == 1:
os.system(sys.argv[0] + " -h")
exit()
This will run the same script with the -h option, and exit.
please notice - you will need to import the sys module.

Categories