Argparse: str object has no attribute - python

Not sure what is going on here. Passing 2 arguments to an argparser, getting an error. Am I passing my arguments improperly?
Here is the code I am running.
parser = argparse.ArgumentParser()
parser.add_argument('-dspath', '--datasheet-path', required=True, dest='datasheet_path', type=str, help='path to data')
parser.add_argument('-pname', '--project-name', required=True, dest='project_name', type=str, help='name of project')
args = parser.parse_args("./path_to_project", "name_of_project")
I get the following error:
Traceback (most recent call last):
File "/Users/ayoung/PycharmProjects/pdf_scraper_atul/datasheet_rms_comparator 3.py", line 1320, in <module>
args = parse_args()
File "/Users/ayoung/PycharmProjects/pdf_scraper_atul/datasheet_rms_comparator 3.py", line 26, in parse_args
args = parser.parse_args("./path_to_project", "name_of_project")
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/argparse.py", line 1749, in parse_args
args, argv = self.parse_known_args(args, namespace)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/argparse.py", line 1772, in parse_known_args
setattr(namespace, action.dest, action.default)
AttributeError: 'str' object has no attribute 'datasheet_path'
Thanks!

Since argparse is not taking any positional arguments, you have to include the --flags to tell argparse where to look for the arguments.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-dspath', '--datasheet-path', required=True, dest='datasheet_path', type=str, help='path to data')
parser.add_argument('-pname', '--project-name', required=True, dest='project_name', type=str, help='name of project')
args = parser.parse_args(["--datasheet-path", "./path_to_project", "--project-name", "name_of_project"])
print(args)
Running this script gives us...
$ python parseArgs.py
Namespace(datasheet_path='./path_to_project', project_name='name_of_project')

First point: the second argument to parser.parse_args is :
An object to take the attributes. The default is a new empty Namespace object.
Which explains your first error (strings are immutable, you can't set attributes on them).
Second point: by prefixing your arguments names with "-", you make them named arguments, so you have to pass them as such, not as positional arguments.

Yes you're passing them improperly. They should be in a sequence, like a list:
args = parser.parse_args(["./path_to_project", "name_of_project"])
Thanks to Anthony Sottile for pointing this out in a comment
The reason for that error is parse_args is expecting its second argument to be a namespace. From the documentation:
ArgumentParser.parse_args(args=None, namespace=None)
Convert argument strings to objects and assign them as attributes of the namespace.
However, then you get another error:
usage: test.py [-h] -dspath DATASHEET_PATH -pname PROJECT_NAME
test.py: error: the following arguments are required: -dspath/--datasheet-path, -pname/--project-name
Which you can fix by passing the required options:
args = parser.parse_args(["-dspath", "./path_to_project", "-pname", "name_of_project"])
Or by turning your options into positional arguments:
parser.add_argument('datasheet_path', help='path to data')
parser.add_argument('project_name', help='name of project')
args = parser.parse_args(["./path_to_project", "name_of_project"])
print(args)
# Namespace(datasheet_path='./path_to_project', project_name='name_of_project')
BTW type=str is implied and I switched the argument names to have underscores instead of dashes.

replace datasheet_path with datasheet-path

Related

Arguments get mixed up with argparse

def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('p', 'projectType', type=str, help = 'c or c++ project type', choices=['c', 'c++'])
parser.add_argument('i', 'inputfile', type=pathlib.Path, help = 'the input file path')
parser.add_argument('o',"outputfile",type=pathlib.Path, help= 'the output file path')
parser.add_argument
args = parser.parse_args()
when i run this code on command prompt, and enter the inputfile first for example.. it gives me an error. does Argparse have an option like getopt (where i call something with a letter before inputting it so no mix up ex, '-i ...input...'). Here this option is just for optional arguments.
Is there a way for positional arguments to do that?
As per the argparse documentation you would define it as an "optional" argument but with the required flag set to true, e.g.:
parser.add_argument('-i', '--inputfile', required=True, type=pathlib.Path, help='the input file path')

Python argparse save argument into a variable

Im trying to save the argparse argument into a variable.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-n', dest='noww', action='store', help="shows noww")
args = parser.parse_args()
print(noww)
but its giving this error.
python c:/Users/rbhuv/Desktop/code/testing.py -n sdad
Traceback (most recent call last):
File "c:/Users/rbhuv/Desktop/code/testing.py", line 8, in <module>
print(noww)
NameError: name 'noww' is not defined
It should be args.noww instead of noww.
When you make the parse_args call, you get back a Namespace object:
>>> args = parser.parse_args(['-n', 'sdad'])
>>> args
Namespace(noww='sdad')
From here you can access the value as an attribute:
>>> args.noww
'sdad'
You need to reformat your argument a bit.
parser.add_argument('-n', '--now', action='store_true', help="shows noww")
args = parser.parse_args()
print(args.now)
When using optional arguments, use -- to define the variable name you will use throughout and then you can call it like such.
Convert the args to a dictionary using vars
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-n', dest='noww', action='store', help="shows noww")
args = parser.parse_args()
args = vars(args)
print(args["noww"])

python - mutually exclusive arguments complains about action index

I'm trying to group arguments such that the user can do either:
python sample.py scan -a 1 -b 2
or
python sample.pt save -d /tmp -n something
here is my code:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='this is the description'
)
parser.add_argument('op', choices=['scan','save'], help='operation', default='scan')
root_group = parser.add_mutually_exclusive_group()
group1 = root_group.add_argument_group('g1', 'scan')
group1.add_argument('-a', help='dir1')
group1.add_argument('-b', help='dir2')
group2 = root_group.add_argument_group('g2', 'save')
group2.add_argument('-d', help='dir')
group2.add_argument('-n', help='name')
args = parser.parse_args()
print args
as I run python sample.py --help
I'm getting an error. Can someone please tell me how to fix it?
Traceback (most recent call last):
File "sample.py", line 18, in <module>
args = parser.parse_args()
File "C:\Python27\lib\argparse.py", line 1688, in parse_args
args, argv = self.parse_known_args(args, namespace)
File "C:\Python27\lib\argparse.py", line 1720, in parse_known_args
namespace, args = self._parse_known_args(args, namespace)
File "C:\Python27\lib\argparse.py", line 1926, in _parse_known_args
start_index = consume_optional(start_index)
File "C:\Python27\lib\argparse.py", line 1866, in consume_optional
take_action(action, args, option_string)
File "C:\Python27\lib\argparse.py", line 1794, in take_action
action(self, namespace, argument_values, option_string)
File "C:\Python27\lib\argparse.py", line 994, in __call__
parser.print_help()
File "C:\Python27\lib\argparse.py", line 2313, in print_help
self._print_message(self.format_help(), file)
File "C:\Python27\lib\argparse.py", line 2287, in format_help
return formatter.format_help()
File "C:\Python27\lib\argparse.py", line 279, in format_help
help = self._root_section.format_help()
File "C:\Python27\lib\argparse.py", line 209, in format_help
func(*args)
File "C:\Python27\lib\argparse.py", line 317, in _format_usage
action_usage = format(optionals + positionals, groups)
File "C:\Python27\lib\argparse.py", line 388, in _format_actions_usage
start = actions.index(group._group_actions[0])
IndexError: list index out of range
and if I add action='store_const', the error goes away and a new error occurrs asking for 4 inputs.
Argparse does not seem to fully support adding a group into another group. This error happens because Argparse requires root_group to have some kind of action. A workaround would be adding an argument to the group:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='this is the description'
)
# This is now redundant. We can remove it
# parser.add_argument('op', choices=['scan','save'], help='operation', default='scan')
root_group = parser.add_mutually_exclusive_group()
# Workaround
root_group.add_argument('--scan', help='scan', action='store_true')
root_group.add_argument('--save', help='save', action='store_true')
group1 = root_group.add_argument_group('g1', 'scan')
group2 = root_group.add_argument_group('g2', 'save')
group1.add_argument('-a', help='dir1')
group1.add_argument('-b', help='dir2')
group2.add_argument('-d', help='dir', default='')
group2.add_argument('-n', help='name')
args = parser.parse_args()
print args
Notice that we are using --scan and --save. To avoid using the -- prefix, you may need the help of Sub-commands. Details can be found here.
Thanks to #skyline's link above, I got it working with subparsers:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='this is the description'
)
scan_parser = argparse.ArgumentParser(add_help=False)
scan_parser.add_argument('-a', '--a', help='first num', required=True)
scan_parser.add_argument('-b', '--b', help='second num', required=True)
save_parser = argparse.ArgumentParser(add_help=False)
save_parser.add_argument('-d', '--d', help='directory path', required=True)
save_parser.add_argument('-n', '--n', help='name of the file', required=True)
sp = parser.add_subparsers()
sp_scan = sp.add_parser('scan', parents=[scan_parser], help='scans directories')
sp_save = sp.add_parser('save', parents=[save_parser], help='saves something')
args = parser.parse_args()
print args
The error occurs while formatting the usage line, and is the result of the root_group not having any _group_actions. I know from other bug issues that the usage formatter is fragile.
argument_groups and mutually_exclusive_groups are not designed to nest. And despite similar names (and class heritage) they have very different purposes. argument_groups control the display of the help lines. mutually_exclusive_groups control the usage display, and raise errors.
In your case, the arguments added to group1 and group2 where added to the master parser list, but not added to the list that root_group uses for exclusivity checking (or usage formatting).
If I add an argument directly to root_group, help works, and produces:
In [19]: parser.print_help()
usage: ipython3 [-h] [-a A] [-b B] [-d D] [-n N] [--foo FOO] {scan,save}
this is the description
positional arguments:
{scan,save} operation
optional arguments:
-h, --help show this help message and exit
--foo FOO
You can see from the 'related' sidebar that a number of people have asked about adding groups of arguments to mutually_exclusive_groups. A patch that would allow nested groups with all sorts of logical conditions is a long ways in the future.
But, as you discovered, the subparser mechanism handles your particular case nicely.
You don't need to use the parents mechanism:
sp = parser.add_subparsers()
sp_scan = sp.add_parser('scan', help='scans directories')
sp_scan.add_argument('-a', '--a', help='first num', required=True)
sp_scan.add_argument('-b', '--b', help='second num', required=True)
sp_save = sp.add_parser('save', parents=[save_parser], help='saves something')
sp_save.add_argument('-d', '--d', help='directory path', required=True)
sp_save.add_argument('-n', '--n', help='name of the file', required=True)
The parents mechanism works here, but is intended more for cases where the parsers are defined elsewhere (and imported), or get reused in several subparsers. For example if you have many subparsers which share a core group of arguments (as well as their own unique ones).

Make argparse with mandatory and optional arguments

I'm trying to write a argparse system where it takes in 3 arguments, with 1 of them being optional. Depending on the presence of the 3rd argument, the program will decide how to proceed.
I current have this:
parser = argparse.ArgumentParser(description='Some description')
parser.add_argument('-f', '--first', help='Filename for f', required=True)
parser.add_argument('-s', '--second', help='Filename for s', required=True)
parser.add_argument('-t', '--third', help='Filename for t')
args = parser.parse_args()
if args.third:
parse.classify(args['first'], args['second'], args['third'])
else:
parse.classify(args['first'], args['second'], None)
I'm getting the following error: TypeError: 'Namespace' object is not subscriptable
Namespace objects aren't subscriptable; they have attributes.
parse.classify(args.first, args.second, args.third)
You can use vars to create a dict, though.
args = vars(args)
Then subscripting will work.

Argparse with required subparser

I'm using Python 3.4, I'm trying to use argparse with subparsers, and I want to have a similar behavior to the one in Python 2.x where if I don't supply a positional argument (to indicate the subparser/subprogram) I'll get a helpful error message. I.e., with python2 I'll get the following error message:
$ python2 subparser_test.py
usage: subparser_test.py [-h] {foo} ...
subparser_test.py: error: too few arguments
I'm setting the required attribute as suggested in https://stackoverflow.com/a/22994500/3061818, however that gives me an error with Python 3.4.0: TypeError: sequence item 0: expected str instance, NoneType found - full traceback:
$ python3 subparser_test.py
Traceback (most recent call last):
File "subparser_test.py", line 17, in <module>
args = parser.parse_args()
File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1717, in parse_args
args, argv = self.parse_known_args(args, namespace)
File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1749, in parse_known_args
namespace, args = self._parse_known_args(args, namespace)
File "/usr/local/Cellar/python3/3.4.0/Frameworks/Python.framework/Versions/3.4/lib/python3.4/argparse.py", line 1984, in _parse_known_args
', '.join(required_actions))
TypeError: sequence item 0: expected str instance, NoneType found
This is my program subparser_test.py - adapted from https://docs.python.org/3.2/library/argparse.html#sub-commands:
import argparse
# sub-command functions
def foo(args):
print('"foo()" called')
# create the top-level parser
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
subparsers.required = True
# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo')
parser_foo.set_defaults(func=foo)
args = parser.parse_args()
args.func(args)
Related question: Why does this argparse code behave differently between Python 2 and 3?
You need to give subparsers a dest.
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
subparsers.required = True
Now:
1909:~/mypy$ argdev/python3 stack23349349.py
usage: stack23349349.py [-h] {foo} ...
stack23349349.py: error: the following arguments are required: cmd
In order to issue this 'missing arguments' error message, the code needs to give that argument a name. For a positional argument (like subparses), that name is (by default) the 'dest'. There's a (minor) note about this in the SO answer you linked.
One of the few 'patches' to argparse in the last Python release changed how it tests for 'required' arguments. Unfortunately it introduced this bug regarding subparsers. This needs to be fixed in the next release (if not sooner).
update
If you want this optional subparsers behavior in Py2, it looks like the best option is to use a two stage parser as described in
How to Set a Default Subparser using Argparse Module with Python 2.7
There has been some recent activity in the related bug/issue
https://bugs.python.org/issue9253
update
A fix to this is in the works: https://github.com/python/cpython/pull/3027
The previous mentioned workaround is no longer required as the add_mutually_exclusive_group() method also accepts a required argument.
This will indicate that least one of the mutually exclusive arguments is required.
parser = argparse.ArgumentParser(prog='PROG')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--foo', action='store_true')
group.add_argument('--bar', action='store_false')
> parser.parse_args([])
> usage: PROG [-h] (--foo | --bar)
> PROG: error: one of the arguments --foo --bar is required
Taken from the argsparse docs: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_mutually_exclusive_group

Categories

Resources