Not recognizing command line argument in Python - python

I am trying to add a command line argument depending on some values returned by the functions. When I am not giving that argument it says:
main.py: error: argument -opp/--operator is required
When I am giving the argument it says:
main.py: error: unrecognized arguments: -opp +
Following is the piece of EDITED code (as told in one of the answers):
parser.add_argument('-z', help='Help msg', required=True)
args, unknown = parser.parse_known_args()
value = some_functions(args.z)
if value == some_particular_value:
parser.add_argument('-opp','--operator',help='Some help msg',required=True)
args = parser.parse_args()
Please help me in adding this argument. Thanks!

There are, though, a couple of mistakes in your code. Here's the corrected version:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-z', help = 'Help msg', required = True)
args, _ = parser.parse_known_args()
# value = some_functions(args.z)
if value == some_particular_value:
parser.add_argument('-opp', '--operator', help = 'Some help msg', required = True)
# args2, _ = parser.parse_known_args()
# some_function2(args2.operator)
So, let's analyse your mistakes:
Assigning instead of comparing
That's typical newbie mistake. Within a conditional operator (if, case...) you set the value instead of checking it. The difference is in amount of the = sign.
If you assign value, the condition in the operator will be always True and test will always succeed (in most of programming languages and cases).
Check this out:
a = 1
if a = 2:
print a
This may print 2 in some languages (like C or Java; using the correct syntax). Why? You've just set it! Yet, Python is smart enough to tell you about your mistake:
File "<stdin>", line 1
if a = 2:
^
SyntaxError: invalid syntax
And compare it to this:
a = 1
if a == 2:
print a
This will not print anything. Because the if test did not pass.
Assigning value instead of calling method
You want to be using the method add_argument instead of re-defining the parser variable, right?
parser = add_argument(...)
That's something like I've described above. You should be calling a method of a parser variable, not defining its new value:
parser.add_argument(...)
Re-parsing arguments is missing?
You did not show the part of the code where you check for the operator argument. Note: you should parse your arguments again, when defined a new argument:
parser.add_argument(...)
args, _ = parser.parse_known_arguments()
Then you will get a new argument in the args variable.
Using the wrong name of argument?
Again, you are missing part of code, where you check for the operator argument' value. If you are trying to access it with
args.opp # whoops...
Then you'd just get an error saying There's no argument 'opp'!, because it has its full name and should be accessed with it:
args.operator # aaah, here it is!

Related

pass JSON dictionary and a char with argparse

I'm trying to pass two arguments to python, one read-only JSON file and one char together. In fact I want the arguments both positional, but I couldn't do it either, so let the char one optional. At the end I will read the dictionary and the char will be my key.
When I try like below, the second argument is passed as None. I can't understand why it's empty. Do you have any idea?
Thanks a lot!
python3 myProg.py dict.json -a
import json
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(description = 'program')
parser.add_argument('json', nargs = 1, type = argparse.FileType('r'))
parser.add_argument('-args', nargs ="?")
arguments = parser.parse_args()
dictionary = json.load(arguments.json[0])
arg_start = arguments.args
Recieving this error:
Namespace(args=None, json=[<_io.TextIOWrapper name='dict.json' mode='r' encoding='UTF-8'>])
It looks like you got the json file ok. If you'd omitted the nargs=1 you could have used:
dictionary = json.load(arguments.json)
-args is an nargs='?'. That means it it is ok to omit an argument. If not used at all its value will be the Actions default (currently None).
If provided as just -a, the value is the Action's const (also None).
If provided as -a foo, it will set that string.
If you'd define that argument as positional, plain 'args' (without the -) then it would have accepted the next string after the json file name.

How do I make an argparse argument optional when using subparsers?

I'm working on a simple Git/Redmine glue script but I'm having some difficulty using optional arguments with the Python argparse module.
With the following code:
import argparse
class MyClass:
def StartWork(self, issueNumber, **kwargs):
if issueNumber is None:
issueNumber = input("please enter an issue number: ")
else:
print("issue number detected")
print(issueNumber)
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='MyClass-command', help='Command to perform')
subparsers.required = True
startWorkParser = subparsers.add_parser('startwork', help='Command to begin work on a new branch')
startWorkParser.add_argument("issuenumber", type=int, help="The issue number used to create a local branch based on the specified issue number", nargs='?', default=None)
startWorkParser.set_defaults(func=MyClass.StartWork)
# Parse the arguments to make sure we have all the information requried to actually do something.
args = parser.parse_args()
mc = MyClass()
try:
args.func(mc, **vars(args))
except AssertionError as e:
print("Error: "+str(e))
# Parse the arguments to make sure we have all the information required to actually do something.
args = parser.parse_args()
I'd expect a call like this:
python MyClass.py startwork
...to result in the user being prompted for an issue number. Instead I get:
Traceback (most recent call last):
File "C:\Projects\RedmnieGlue\MyClass.py", line 23, in <module>
args.func(mc, **vars(args))
TypeError: StartWork() missing 1 required positional argument: 'issueNumber'
So why is the nargs='?' not prevailing here?
Edit
If I call it like this:
python MyClass.py startwork -h
I get this:
usage: class1.py startwork [-h] [issuenumber]
positional arguments:
issuenumber The issue number used to create a local branch based on the
specified issue number
optional arguments:
-h, --help show this help message and exit
...which (based on the [] around issuenumber) suggests to me it is understanding that is an optional argument but something is preventing it from working as I'd expect it to. Something to do with my use of subparsers and calling methods with the arg parser perhaps?
If you print the contents of vars(args) before your function call like this:
print(vars(args))
args.func(mc, **vars(args))
Then you can easily verify whether there is something wrong with the argument parser or not. With a call of the script without arguments (e.g. python myscript.py), you get the following output:
{'MyClass-command': 'startwork', 'issuenumber': None, 'func': <function MyClass.StartWork at 0x000000493898C510>}
As you can see issuenumber actually is in that dictionary, and it did get the default value. So the error you are seeing is not because of the argument parser (it’s also not an argparse error, so the validation on the arguments—with issuenumber being optional—is absolutely correct).
Instead, what’s going wrong is that the argument issuenumber is not passed to the positional argument when using **vars(args). The reason that does not happen is actually quite simply:
The dictionary key is issuenumber; the function expects a issueNumber (note the upper case N). So either change the function to use a lowercase issuenumber, or change the argument parser to store the value in issueNumber instead.

How to Check for and raise an error for no command line argument

I have an argparsing program:
import argparse
def main():
p = argparse.ArgumentParser()
help_str = {'rule': 'Blah blah blah and stuff.'}
p.add_argument('-r', '--rule', action="store", type=str, dest='rule', help=help_str['rule'])
p.set_defaults(rule='string')
args = p.parse_args()
Therefore, my command line input:
username#compname:~$python progname.py -r/--rule 'something'
I am trying to figure out how I could just input:
username#compname:~$python progname.py -r/--rule
and have my own error message come up:
print '\n Error: no input value for rule:'
print ' resetting rule to default value.'
args.rule = 'string'
after this, the rule value should print out as 'string'
I am only slightly fluent in Python, sorry. I have tried using try/except blocks and even sys.argv[2] (though I may have done something wrong.) This is the error that keeps popping up with all of my solutions (not very familiar with errors outside Type and Value):
progname.py: error: argument -r/--rule: expected one argument
Any help appreciated.
p = argparse.ArgumentParser()
p.add_argument('-r', '--rule', nargs='?', default='string')
args = p.parse_args()
print args
with nargs='?' produces Namespace(rule='string') if called without argument. With -r, args is Namespace(rule=None). And Namespace(rule='something') with -r something.
Lets add a few lines of code
if args.rule is None:
print '\n Error: no input value for rule:'
print ' resetting rule to default value.'
args.rule = 'string'
print args
Now the output with '-r' (or --rule) is:
Namespace(rule=None)
Error: no input value for rule:
resetting rule to default value.
Namespace(rule='string')
the other cases are the same.
If I drop the default; p.add_argument('-r', '--rule', nargs='?'), the no argument case also produces this custom 'error message', since the default (in argparse) is None.
It's possible to add custom error checking with a custom type or action, but I think testing for None after using argparse is simpler, and easier to understand.
I'd suggest changing this error message to a warning. An Error usually terminates the program; a warning prints the message and continues.
Here's a solution when nargs=2 (or something else that's fixed). It's not a trivial change, since it involves redefining the error method (documented near the end of the argparse docs), and then capturing the error that it produces. The error method cannot access the Namespace (args), nor can it continue parse_args. So it cannot handle any other arguments if there is problem with the rule argument.
class MyParser(argparse.ArgumentParser):
def error(self, message):
if 'rule' in message:
message = 'wrong number of input values for rule'
raise argparse.ArgumentError(None,message)
# cannot access namespace or continue parsing
else:
super(MyParser, self).error(message)
p = MyParser()
p.add_argument('-r', '--rule', nargs=2)
try:
args = p.parse_args()
except argparse.ArgumentError as e:
print e
args = argparse.Namespace(rule='default')
# create a namespace with this default value
print args
Keep in mind that nargs has 2 purposes:
- raise an error if a wrong number of argument strings is given
- allocate multiple argument strings among multiple Actions (the thing created by add_argument).
The 2nd is especially evident if you have, for example, several positionals, taking 1,2 and * arguments respectively. It is a key novel feature of argparse, at least relative to earlier Python parsers. While it possible to tweak it, it is hard to completely redefine it.
If the arguments allow it, you could use nargs='*' instead of the '?', and issue the warning if the number of strings is not '2' (or whatever).

python using argparse.ArgumentParser method

I've tried to learn how argparse.ArgumentParser works and I've write a couple lines for that :
global firstProduct
global secondProduct
myparser=argparse.ArgumentParser(description='parser test')
myparser.add_argument("product1",help="enter product1",dest='product_1')
myparser.add_argument("product2",help="enter product2",dest='product_2')
args=myparser.parse_args()
firstProduct=args.product_1
secondProduct=args.product_2
I just want to that when User run this script with 2 parameters my code assign them to firstProduct and secondProduct respectively. However it doesn’t work. Is there anyone to tell me why? thanks in advance
Omit the dest parameter when using a positional argument. The name supplied for the positional argument will be the name of the argument:
import argparse
myparser = argparse.ArgumentParser(description='parser test')
myparser.add_argument("product_1", help="enter product1")
myparser.add_argument("product_2", help="enter product2")
args = myparser.parse_args()
firstProduct = args.product_1
secondProduct = args.product_2
print(firstProduct, secondProduct)
Running % test.py foo bar prints
('foo', 'bar')
In addition to unutbu's answer, you may also use the metavar attribute in order to make the destination variable and the variable name that appears in the help menus different, as shown in this link.
For example if you do:
myparser.add_argument("firstProduct", metavar="product_1", help="enter product1")
You will have the argument available for you in args.firstProduct but have it listed as product_1 in the help.

Argparse: ignore multiple positional arguments when optional argument is specified

I'm trying to make argparse ignore the fact that two normally required positional arguments shouldn't be evaluated when an optional argument (-l) is specified.
Basically I'm trying to replicate the behavior of --help: when you specify the -h, all missing required arguments are ignored.
Example code:
parser = argparse.ArgumentParser(description="Foo bar baz")
parser.add_argument('arg1', help='arg1 is a positional argument that does this')
parser.add_argument('arg2', help='arg2 is a positional argument that does this')
parser.add_argument('-l', '--list', dest='list', help='this is an optional argument that prints stuff')
options, args = parser.parse_args()
if options.list:
print "I list stuff"
And of course, if I run it now, I get :
error: too few arguments
I tried different things like nargs='?', but couldn't get anything working.
This question is quite similar but wasn't answered.
Unfortunately, argparse isn't quite flexible enough for this. The best you can do is to make arg1 and arg2 optional using nargs="?" and check yourself whether they are given if needed.
The internal help action is implemented by printing the help message and exiting the program as soon as -h or --help are encountered on the command line. You could write a similar action yourself, something like
class MyAction(argparse.Action):
def __call__(self, parser, values, namespace, option_string):
print "Whatever"
parser.exit()
(Warning: untested code!)
There are definite downsides to the latter approac, though. The help message will unconditionally show arg1 and arg2 as compulsory arguments. And parsing the command line simply stops when encountering -l or --list, ignoring any further arguments. This behaviour is quite acceptable for --help, but is less than desirable for other options.
I ran into this issue and decided to use subcommands. Subcommands might be overkill, but if you find your program not using some of the positional arguments in many instances (as I did), then subcommands might be a good solution.
For your given example, I'd use something like the following:
parser = argparse.ArgumentParser(description="Foo bar baz")
subparsers = parser.add_subparsers(description='available subcommands')
parser_main = subparsers.add_parser('<main_command_name>')
parser_main.add_argument('arg1', help='arg1 is a positional argument that does this')
parser_main.add_argument('arg2', help='arg2 is a positional argument that does this')
parser_list = subparsers.add_parser('list', help='this is a subcommand that prints stuff')
options, args = parser.parse_args()
I left out some details that you might want to include (like set_defaults(func=list)), which are mentioned in the argparse documentation.
I think the nargs='*' is helpful.
Positional arguments is ignorable, then you can use if to check the positional arguments is true or false.
http://docs.python.org/library/argparse.html#nargs
The cleanest approach I've been able to find so far is to split the parsing into two stages. First check for -l/--list:
parser = argparse.ArgumentParser(description="Foo bar baz")
parser.add_argument('-l', '--list', dest='list', action='store_true',
help='this is an optional argument that prints stuff')
options, remainder = parser.parse_known_args()
Now, since you used parse_known_args, you won't get an error up to here, and you can decide what to do with the remainder of the arguments:
if options.list:
print "I list stuff"
else:
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('arg1', help='arg1 is a positional argument that does this')
parser.add_argument('arg2', help='arg2 is a positional argument that does this')
options = parser.parse_args(remainder)
You may want to set the usage option in the first parser to make the help string a bit nicer.
I may have found a solution here. True, it is a dirty hack, but it works.
Note: all the following applies to Python 3.3.2.
As per the answer here, parse_args checks which actions are required and throws an error if any of them are missing. I propose to override this behavior.
By subclassing ArgumentParser we can define a new ArgumentParser.error method (original here) that will check whether the error was thrown because some arguments are missing and take necessary action. Code follows:
import argparse
import sys
from gettext import gettext as _
class ArgumentParser(argparse.ArgumentParser):
skip_list = []
def error(self, message):
# Let's see if we are missing arguments
if message.startswith('the following arguments are required:'):
missingArgs = message.split('required: ')[1].split(', ')
newArgs = [] # Creating a list of whatever we should not skip but is missing
for arg in missingArgs:
if arg not in self.skip_list:
newArgs.append(arg)
else:
self.skip_list.remove(arg) # No need for it anymore
if len(newArgs) == 0:
return # WARNING! UNTESTED! MAY LEAD TO SPACETIME MELTDOWN!
else: # Some required stuff is still missing, so we show a corrected error message
message = _('the following arguments are required: %s') % ', '.join(newArgs)
self.print_usage(sys.stderr) # Original method behavior
args = {'prog': self.prog, 'message': message}
self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
The new method first checks whether the error is because arguments are missing from the command line (see here for the code that generates the error). If so, the method gets the names of the arguments from the error message and puts them into a list (missingArgs).
Then, we iterate over this list and check which arguments should be skipped, and which are still required. To determine which arguments to skip, we compare them against skip_list. It is a field in our ArgumentParser subclass that should contain the names of the arguments to skip even when they are required by the parser. Please note that arguments that happen to be in skip_list are removed from it when they are found.
If there are still required arguments that are missing from the command line, the method throws a corrected error message. If all the missing arguments should be skipped, however, the method returns.
WARNING! The original definition of ArgumentParser.error states that if it is overridden in a subclass it should not return, but rather exit or raise an exception. Therefore, what is shown here is potentially unsafe and may cause your program to crash, your computer to catch fire or worse - IT MAY EVAPORATE ALL YOUR TEA. However, it seems like in this particular case (missing required arguments) it is safe to return from the method. But it might not be. You have been warned.
In order to fill skip_list we could use code like this:
class SpecialHelp(argparse._HelpAction):
def __call__(self, parser, namespace, values, option_string=None):
parser.print_help()
print()
for action in parser._actions:
if action != self and action.required:
parser.skip_list.append(argparse._get_action_name(action))
This particular class imitates the built-in help action, but instead of exiting it inserts all the remaining required arguments into skip_list.
Hope my answer helps and best of luck.
It may be ugly, but that is what I normally do.
def print_list():
the_list = ["name1", "name2"]
return "{0}".format(the_list)
...
parser.add_argument("-l", "--list", action='version',
version=print_list(), help="print the list")

Categories