argparse to validated unknown args in python - python

test1.py:
_argParserObj = argparse.ArgumentParser()
_argParserObj.add_argument('--tool', type=str, dest='tool')
parsedArgs, passthroughs = _argParserObj.parse_known_args()
When test1.py is executed as below
test1.py --tool abc arg1 arg2 --invalid_switch
I would like to show the below error.
But still accept arg1 and arg2 as extra arguments
test1.py: error: unrecognized arguments: -invalid_switch
Currently it just parses invalid_switch also a passthrough and throw any error.
Can anyone please suggest how to achieve this?

You can try parse_args() instead of parse_known_args()
_argParserObj = argparse.ArgumentParser()
_argParserObj.add_argument('--tool', type=str, dest='tool')
parsedArgs, passthroughs = _argParserObj.parse_args()
parse_args() will show this error in your case
test.py: error: unrecognized arguments: --invalid_switch

Related

Argparse: exception for option required=True

I use the following code to parse argument to my script (simplified version):
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-l", "--library", required=True)
ap.add_argument("--csv2fasta", required=False)
args = vars(ap.parse_args())
For every way the script can be run, the -l/--library flag should be required (required=True), but is there a way that it can use the setting required=False when you only use the --csv2fasta flag?
You have to write your test after parsing arguments, here's what I do for such cases:
def parse_args():
ap = argparse.ArgumentParser()
ap.add_argument("-l", "--library")
ap.add_argument("--csv2fasta")
args = ap.parse_args()
if not args.library and not args.csv2fasta:
ap.error("--library is required unless you provide --csv2fasta argument")
return args
$ python3 test-args.py
usage: test-args.py [-h] [-l LIBRARY] [--csv2fasta CSV2FASTA]
test-args.py: error: --library is required unless you provide --csv2fasta argument
$ python3 test-args.py --csv2fasta value

How to set optional arguments to positional arguments in Python's Argparse?

I have the following code:
# Get parsed arguments
args = argparse.ArgumentParser(description=Messages().Get(112))
# Get the arguments for sinit
args.add_argument('init', help=Messages().Get(100), action="store_true")
args.add_argument('--url', default=None, help=Messages().Get(101))
# Get the arguments for schema import
args.add_argument('schema-import', help=Messages().Get(104), action="store_true")
args.add_argument('--file', default=None, help=Messages().Get(104))
The --url argument should only be used with init. For example: script.py schema-import --url should not be accepted but script.py schema-import --file should.
How to set arguments as child arguments?
As mentioned there might be a way to do this with argparse, I'm not sure, but in any event I find it more transparent to explicitly handle argument dependencies in application logic. This should achieve what I think you want:
import argparse
import sys
args = argparse.ArgumentParser(description="please only use the '--url' argument if you also use the 'init' argument")
# Going to use aliases here it's more conventional. So user can use, eg,
# -i or --init for the first argument.
args.add_argument('-i', '--init', help='init help', action="store_true")
args.add_argument('-u', '--url', default=None, help='init help')
args.add_argument('-s', '--schema-import', help='schema-import help', action="store_true")
args.add_argument('-f', '--file', help='file help')
def main():
arguments = args.parse_args()
if arguments.url and not arguments.init:
# You can log an output or raise an exception if you want
# But most likely a print statment is most appropriate
# Since you are interacting with the CLI.
print("You can only use the URL option with init. Exiting")
sys.exit(0)
print("gaurd clauses passed. Here is my code...")
...
if __name__ == "__main__":
main()
Test results (my file called temp.py):
$python temp.py -u https://www.google.com
You can only use the URL option with init. Exiting
$
$python temp.py -i -u https://www.google.com
Gaurd clauses passed. Here is my code...
Why bother with doing all the logic when you can let argparse do all the work for you?
Simply use Sub-commands to define different "branches" of execution:
args = argparse.ArgumentParser(description=Messages().Get(112))
subparsers = args.add_subparsers()
parser_init = subparsers.add_parser('init', help=Messages().Get(100))
parser_init.add_argument('--url', default=None, help=Messages().Get(101))
parser_schema = subparsers.add_parser('schema-import', help=Messages().Get(104))
parser_schema.add_argument('--file', default=None, help=Messages().Get(104))
And this will give you what you want without any logic added:
>>> print(args.parse_args(['schema-import', '--url', "some.url"]))
usage: args.py [-h] {init,schema-import} ...
args.py: error: unrecognized arguments: --url some.url
>>> print(args.parse_args(['schema-import', '--file', "some.file"]))
Namespace(file='some.file')

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

Usage message when using argparse.REMAINDER

I have a Python program that takes as its (only) positional command-line argument one or more file path expressions. I'm using argparse for the CL parsing, and argparse.REMAINDER for the variable that contains the file path(s). See code below:
import argparse
import sys
# Create parser
parser = argparse.ArgumentParser(
description="My test program")
def parseCommandLine():
# Add arguments
parser.add_argument('filesIn',
action="store",
type=str,
nargs=argparse.REMAINDER,
help="input file(s)")
# Parse arguments
args = parser.parse_args()
return(args)
def main():
# Get input from command line
args = parseCommandLine()
# Input files
filesIn = args.filesIn
# Print help message and exit if filesIn is empty
if len(filesIn) == 0:
parser.print_help()
sys.exit()
# Do something
print(filesIn)
if __name__ == "__main__":
main()
Now, when a user runs the script without any arguments, this results in the following help message:
usage: test.py [-h] ...
Where ... represents the positional input. From a user's perspective it would be more informative if the name of the variable (filesIn) was displayed here instead. Especially because typing test.py -h results in this:
usage: test.py [-h] ...
My test program
positional arguments:
filesIn input file(s)
I.e. the usage line displays ... but then in the list of positional arguments filesIn is used.
So my question is whether there's some easy way to change this (i.e. always display filesIn)?
Don't use argparse.REMAINDER here. You are not gathering all remaining arguments, you are trying to take filenames.
Use '+' instead to capture all remaining arguments as filenames and you need at least one:
parser.add_argument('filesIn',
action="store",
type=str,
nargs='+',
help="input file(s)")
This produces better help output:
$ bin/python test.py
usage: test.py [-h] filesIn [filesIn ...]
test.py: error: too few arguments
$ bin/python test.py -h
usage: test.py [-h] filesIn [filesIn ...]
My test program
positional arguments:
filesIn input file(s)
optional arguments:
-h, --help show this help message and exit

argparse with required subcommands

With python's argparse, how do I make a subcommand a required argument? I want to do this because I want argparse to error out if a subcommand is not specified. I override the error method to print help instead. I have 3-deep nested subcommands, so it's not a matter of simply handling zero arguments at the top level.
In the following example, if this is called like so, I get:
$./simple.py
$
What I want it to do instead is for argparse to complain that the required subcommand was not specified:
import argparse
class MyArgumentParser(argparse.ArgumentParser):
def error(self, message):
self.print_help(sys.stderr)
self.exit(0, '%s: error: %s\n' % (self.prog, message))
def main():
parser = MyArgumentParser(description='Simple example')
subs = parser.add_subparsers()
sub_one = subs.add_parser('one', help='does something')
sub_two = subs.add_parser('two', help='does something else')
parser.parse_args()
if __name__ == '__main__':
main()
There was a change in 3.3 in the error message for required arguments, and subcommands got lost in the dust.
http://bugs.python.org/issue9253#msg186387
There I suggest this work around, setting the required attribute after the subparsers is defined.
parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
update
A related pull-request: https://github.com/python/cpython/pull/3027
In addition to hpaulj's answer: you can also use the required keyword argument with ArgumentParser.add_subparsers() since Python 3.7. You also need to pass dest as argument. Otherwise you will get an error: TypeError: sequence item 0: expected str instance, NoneType found.
Example file example.py:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', required=True)
foo_parser = subparsers.add_parser("foo", help="command foo")
args = parser.parse_args()
Output of the call without an argument:
$ python example.py
usage: example.py [-h] {foo} ...
example.py: error: the following arguments are required: command
How about using required=True? More info here.
You can use the dest argument, which is documented in the last example in the documentation for add_subparsers():
# required_subparser.py
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
one = subparsers.add_parser('one')
two = subparsers.add_parser('two')
args = parser.parse_args()
Running with Python 2.7:
$python required_subparser.py
usage: required_subparser.py [-h] {one,two} ...
required_subparser.py: error: too few arguments
$python required_subparser.py one
$# no error

Categories