Add command line arguments with existing one - python

Getting an error
"error: unrecognized arguments: "
For built in arguments when trying to build my own command line arguments. I am defining my arguments as:
import argparse
def fn_a(parsed_args):
print("hello i am in function fn_a")
def fn_b(parsed_args):
print("hello i am in function fn_b")
parser=argparse.ArgumentParser(description="my first arg scripting")
parser.add_argument('--a', dest='action', action='store_const',const=fn_a)
parser.add_argument('--b', dest='action', action='store_const', const=fn_b)
parsed_args = parser.parse_args()
if parsed_args.action is None:
parser.parse_args(['-h'])
parsed_args.action(parsed_args)
What may be the problems?

You could change your code to use a try-except block and only parse known arguments, for example:
try:
parsed_args, unknown_args = parser.parse_known_args()
except:
parsed_args = None
if parsed_args and parsed_args.action:
parsed_args.action(parsed_args)
else:
try:
parser.parse_known_args(['-h'])
except:
pass

Changing the last bit to:
if parsed_args.action is None:
parser.parse_args(['-h'])
parsed_args.action(parsed_args)
lets me do:
1218:~/mypy$ python stack42067791.py
usage: stack42067791.py [-h] [--a] [--b]
my first arg scripting
optional arguments:
-h, --help show this help message and exit
--a
--b
1218:~/mypy$ python stack42067791.py --a
hello i am in function fn_a
1218:~/mypy$ python stack42067791.py --b
hello i am in function fn_b
1218:~/mypy$ python stack42067791.py -h
usage: stack42067791.py [-h] [--a] [--b]
my first arg scripting
optional arguments:
-h, --help show this help message and exit
--a
--b
That is it responds to --a and --b, and replicates -h if neither is provided.
Anything else produces a standard argparse error message with usage
1218:~/mypy$ python stack42067791.py foo
usage: stack42067791.py [-h] [--a] [--b]
stack42067791.py: error: unrecognized arguments: foo
1220:~/mypy$ python stack42067791.py --b foo
usage: stack42067791.py [-h] [--a] [--b]
stack42067791.py: error: unrecognized arguments: foo
Using parse_known_args lets you throw away the foo in that last case. But why would you want to do that?
parsed_args,extras = parser.parse_known_args()
print(parsed_args, extras)
prints things like
(Namespace(action=None), [])
(Namespace(action=<function fn_b at 0xb71f1b54>), ['foo'])
(Namespace(action=<function fn_a at 0xb744d25c>), [])

Related

Better help for argparse subcommands

Given the following code snippet:
import argparse
import sys
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help="subcommand help")
command1 = subparsers.add_parser("foo", description="Run foo subcommand")
command2 = subparsers.add_parser("bar", description="Run bar subcommand")
opts = parser.parse_args(sys.argv[1:])
When I print help for this I get this:
usage: test.py [-h] {foo,bar} ...
positional arguments:
{foo,bar} subcommand help
optional arguments:
-h, --help show this help message and exit
Is there a way to make it print something like this instead:
usage: test.py [-h] {foo,bar} ...
subcommands:
foo Run foo subcommand
bar Run bar subcommand
optional arguments:
-h, --help show this help message and exit
without supplying a custom formatter? If I change the formatter then it also changes everything else about how the help is printed, but in my case I just want to change the way that subcommand help is printed from the parent (sub)command.
You need to set the help parameter, not the description parameter, to get the output you desire:
import argparse
import sys
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help="subcommand help")
command1 = subparsers.add_parser("foo", help="Run foo subcommand")
command2 = subparsers.add_parser("bar", help="Run bar subcommand")
opts = parser.parse_args(sys.argv[1:])
Output:
usage: test.py [-h] {foo,bar} ...
positional arguments:
{foo,bar} subcommand help
foo Run foo subcommand
bar Run bar subcommand
optional arguments:
-h, --help show this help message and exit
The argparse docs have this to say about the help value:
The help value is a string containing a brief description of the argument. When a user requests help (usually by using -h or --help at the command line), these help descriptions will be displayed with each argument.
And this to say to about the description value:
This argument gives a brief description of what the program does and how it works. In help messages, the description is displayed between the command-line usage string and the help messages for the various arguments.

How to display custom error message for optional and conditional argument

I’m trying to implement a command line argument in a Python script that is both conditional and optional. So the valid usage would be something like that shown below, which says “-a” is optional, and “-b” is optional but only valid if “-a” is specified.
[-a argumentA [-b argumentB]]
I have a solution for doing this using argparse, but I am not able to get the help and error text to do what I want. This is the solution I have:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--AAA", dest="aaa_arg", help="this is the 'a' argument")
namespace, extra = parser.parse_known_args()
if namespace.aaa_arg != None:
parser.add_argument("-b", "--BBB", dest="bbb_arg", help="only use 'b' when 'a' is used")
myArgs = parser.parse_args()
if myArgs.aaa_arg != None:
print("AAA: " + myArgs.aaa_arg)
if myArgs.bbb_arg != None:
print("BBB: " + myArgs.bbb_arg)
The output for various parameters on the command line is:
>py test.py
>py test.py -a something
AAA: something
>py test.py -a something -b somethingElse
AAA: something
BBB: somethingElse
>py test.py -b somethingElse
usage: test.py [-h] [-a AAA_ARG]
test.py: error: unrecognized arguments: -b somethingElse
>py test.py -h
usage: test.py [-h] [-a AAA_ARG]
optional arguments:
-h, --help show this help message and exit
-a AAA_ARG, --AAA AAA_ARG this is the 'a' argument
>
The issue is that a user who has been told that “-b” is a valid argument will be confused by the error message that says “-b somethingElse” is unrecognized. If that user then displays the help, they will not see “-b” listed.
Fixing the help is fairly simple. The second version below uses conflict_handler in the ArgumentParser to allow a custom help message.
import argparse
parser = argparse.ArgumentParser(conflict_handler="resolve")
parser.add_argument("-h", "--help", action="store_true", dest="display_help")
parser.add_argument("-a", "--AAA", dest="aaa_arg", help="this is the 'a' argument")
namespace, extra = parser.parse_known_args()
if namespace.aaa_arg != None:
parser.add_argument("-b", "--BBB", dest="bbb_arg", help="only use 'b' when 'a' is used")
myArgs = parser.parse_args()
if myArgs.display_help:
print('''usage: test2.py [-h] [-a AAA_ARG [-b BBB_ARG]]
optional arguments:
-h, --help show this help message and exit
-a AAA_ARG, --AAA AAA_ARG this is the 'a' argument
-b BBB_ARG, --BBB BBB_ARG only use 'b' when 'a' is used''')
else:
if myArgs.aaa_arg != None:
print("AAA: " + myArgs.aaa_arg)
if myArgs.bbb_arg != None:
print("BBB: " + myArgs.bbb_arg)
As you can see in the output below, the help message lists “-b” as a valid argument; however, the error message still states that “-b somethingElse” is unrecognized.
>py test2.py
>py test2.py -a something
AAA: something
>py test2.py -a something -b somethingElse
AAA: something
BBB: somethingElse
>py test2.py -b somethingElse
usage: test2.py [-h] [-a AAA_ARG]
test2.py: error: unrecognized arguments: -b somethingElse
>py test2.py -h
usage: test2.py [-h] [-a AAA_ARG [-b BBB_ARG]]
optional arguments:
-h, --help show this help message and exit
-a AAA_ARG, --AAA AAA_ARG this is the 'a' argument
-b BBB_ARG, --BBB BBB_ARG only use 'b' when 'a' is used
>
What I would really like to have is something like the output shown below (this output was created manually as an example, not by Python):
>py test3.py
>py test3.py -a something
AAA: something
>py test3.py -a something -b somethingElse
AAA: something
BBB: somethingElse
>py test3.py -b somethingElse
usage: test2.py [-h] [-a AAA_ARG [-b BBB_ARG]]
test2.py: only use 'b' when 'a' is used
>py test3.py -h
usage: test2.py [-h] [-a AAA_ARG [-b BBB_ARG]]
optional arguments:
-h, --help show this help message and exit
-a AAA_ARG, --AAA AAA_ARG this is the 'a' argument
-b BBB_ARG, --BBB BBB_ARG only use 'b' when 'a' is used
>
Is there a way to customize the error message for the "-b" option?
UPDATE:
Based on the suggestions from #hpaulj, I separated the parsing and the conditional checking. The solution adds both the “-a” and “-b” arguments to the parser, then checks the conditional and sets an error with a cutom message.
Adding a custom usage message to the ArgumentParser and tweaking the argument names displayed by help via the metavar attibute on the arguments matches up the uasge statement and the argument descriptions in the help message.
The end result is simpler than what I started out with: no need to parse twice, and no need for a custom help message.
import argparse
parser = argparse.ArgumentParser(usage="%(prog)s [-h] [-a argA [-b argB]]")
parser.add_argument("-a", "--AAA", metavar="argA", dest="aaa_arg", help="this is the 'a' argument")
parser.add_argument("-b", "--BBB", metavar="argB", dest="bbb_arg", help="only use 'b' when 'a' is used")
myArgs = parser.parse_args()
if myArgs.aaa_arg == None and myArgs.bbb_arg != None:
parser.error("only use 'b' when 'a' is used")
if myArgs.aaa_arg != None:
print("AAA: " + myArgs.aaa_arg)
if myArgs.bbb_arg != None:
print("BBB: " + myArgs.bbb_arg)
>py test2.py
>py test2.py -a something
AAA: something
>py test2.py -a something -b somethingElse
AAA: something
BBB: somethingElse
>py test2.py -b somethingElse
usage: test2.py [-h] [-a argA [-b argB]]
test2.py: error: only use 'b' when 'a' is used
>py test2.py -h
usage: test2.py [-h] [-a argA [-b argB]]
optional arguments:
-h, --help show this help message and exit
-a argA, --AAA argA this is the 'a' argument
-b argB, --BBB argB only use 'b' when 'a' is used
>
In the first try
The issue is that a user who has been told that “-b” is a valid argument will be confused by the error message that says “-b somethingElse” is unrecognized. If that user then displays the help, they will not see “-b” listed.
The first parse_known_args() handles, or rather ignores, the -b. But it acts on the -h. The second parse_args() is the one that's complaining about the unrecognized -b.
ArgumentParser(add_help=False) creates a parser without the automatic help; that's an alternative to your resolve. You could add the custom or standard help after first parse_know_args.
Another thing to consider is a custom usage string.
If you don't want it to complain about -b, skip the parse_args or use parse_known_args again.
parser.error('custom message') can be used to display a custom error with usage.
argparse does not provide a convenient way making one argument conditional on another. Often we recommend doing that kind of testing after parsing, rather than trying to do it all while parsing (or with 2 parsing calls). Keep in mind that argparse tries to handle optionals in any order: -a foo -b bar the same as -b bar -a foo.

collecting input files from command line in unix using argparse library

I'm trying to write a script that would take some flags and files as arguments and then execute other scripts, depend on the flag that the user chooses. For example, the command line should look like that:
main_script.py -flag1 -file_for_flag_1 another_file_for_flag_1
and
main_script.py -flag2 -file_for_flag_2
I tried to use the argparse library, but I don't know how to take the input files as arguments for the next steps and manipulate them as I want. I started with:
parser = argparse.ArgumentParser(description="Processing inputs")
parser.add_argument(
"-flat_map",
type=str,
nargs="+",
help="generates a flat addressmap from the given files",
)
parser.add_argument(
"-json_convert",
type=str,
nargs="+",
help="generates a flat addressmap from the given files",
)
args = parser.parse_args(args=["-flat_map"])
print(args)
I printed args in the end to see what I get from it but I got nothing I can work with.
Would like to have some guidance. Thanks.
You can convert the args to a dict (where the key is the arg option and the value is the arg value) if it's more convenient for you:
args_dict = {key: value for key, value in vars(parser.parse_args()).items() if value}
Using argparse you can use sub-commands to select sub-modules:
import argparse
def run_command(parser, args):
if args.command == 'command1':
# add code for command1 here
print(args)
elif args.command == 'command2':
# add code for command2 here
print(args)
parser = argparse.ArgumentParser(
prog='PROG',
epilog="See '<command> --help' to read about a specific sub-command."
)
subparsers = parser.add_subparsers(dest='command', help='Sub-commands')
A_parser = subparsers.add_parser('command1', help='Command 1')
A_parser.add_argument("--foo")
A_parser.add_argument('--bar')
A_parser.set_defaults(func=run_command)
B_parser = subparsers.add_parser('command2', help='Command 2')
B_parser.add_argument('--bar')
B_parser.add_argument('--baz')
B_parser.set_defaults(func=run_command)
args = parser.parse_args()
if args.command is not None:
args.func(parser, args)
else:
parser.print_help()
This generates a help page like so:
~ python args.py -h
usage: PROG [-h] {command1,command2} ...
positional arguments:
{command1,command2} Sub-commands
command1 Command 1
command2 Command 2
optional arguments:
-h, --help show this help message and exit
See '<command> --help' to read about a specific sub-command.
and help text for each sub-command:
~ python args.py B -h
arg.py command2 -h
usage: PROG command2 [-h] [--bar BAR] [--baz BAZ]
optional arguments:
-h, --help show this help message and exit
--bar BAR
--baz BAZ

argparser.print_help() not printing the full message

I have a program where I tried to put help in my code using argparse:
import argparse,sys
parser = argparse.ArgumentParser(description='prog desc')
parser.add_argument('path', help='name of directory')
args = parser.parse_args()
parser.print_help()
this prints:
>python testArgs.py
usage: testArgs.py [-h] path
testArgs.py: error: too few arguments
but I'm expecting same as if I entered -h:
>python testArgs.py -h
usage: testArgs.py [-h] path
prog desc
positional arguments:
path name of directory
optional arguments:
-h, --help show this help message and exit
But if I switch the position of the print_help() before parse_args(), then it works right:
import argparse,sys
parser = argparse.ArgumentParser(description='prog desc')
parser.add_argument('path', help='name of directory')
parser.print_help()
args = parser.parse_args()
output:
>python testArgs.py
usage: testArgs.py [-h] path
prog desc
positional arguments:
path name of directory
optional arguments:
-h, --help show this help message and exit
usage: testArgs.py [-h] path
testArgs.py: error: too few arguments
What am I doing wrong?
In your first example your program doesn't reach the parser.print_help() method, it fails on parser.parse_args(), prints the default error message (which is testArgs.py: error: too few arguments) and exits the program.
In your second example, when you switch between the functions, it still behaves the same but you see the help details because you called the print_help() function before the program fails (you can see it fails because it still prints the error message at the end).
If you want to print the help message when an argparse error occurred, read this post:
Display help message with python argparse when script is called without any arguments

Print help when positional arguments not provided

I'm trying to execute my Python program. It uses one positional argument. When the positional argument is not provided I want to print help. But all I get is
error : too few arguments
Here is the Python code :
parser = argparse.ArgumentParser(
description = '''My Script ''')
parser.add_argument('I', type=str, help='Provide the release log file')
args = parser.parse_args()
I'm expecting the following output when no positional arguments are specified:
usage: script.py [-h] I
My Script
positional arguments:
I Provide the release log file
optional arguments:
-h, --help show this help message and exit
Any thoughts how to achieve this would be appreciated.
argparse doesn't work that way. You need to ask for help with the -h argment. Otherwise it just gives the usage along with the error message.
0015:~/mypy$ python3 stack41671660.py
usage: stack41671660.py [-h] I
stack41671660.py: error: the following arguments are required: I
0015:~/mypy$ python stack41671660.py
usage: stack41671660.py [-h] I
stack41671660.py: error: too few arguments
0015:~/mypy$ python stack41671660.py -h
usage: stack41671660.py [-h] I
My Script
positional arguments:
I Provide the release log file
optional arguments:
-h, --help show this help message and exit
You could make the positional argument 'optional' with nargs='?', and add a test for the default value:
print(args)
if args.I is None:
parser.print_help()
sample runs:
0016:~/mypy$ python stack41671660.py
Namespace(I=None)
usage: stack41671660.py [-h] [I]
My Script
positional arguments:
I Provide the release log file
optional arguments:
-h, --help show this help message and exit
0019:~/mypy$ python stack41671660.py 2323
Namespace(I='2323')
Another option is to customize the parser.error method, so that it does print_help instead of print_usage. That will affect all parsing errors, not just this missing positional.
def error(self, message):
"""error(message: string)
Prints a usage message incorporating the message to stderr and
exits.
If you override this in a subclass, it should not return -- it
should either exit or raise an exception.
"""
# self.print_usage(_sys.stderr)
self.print_help(_sys.stderr)
args = {'prog': self.prog, 'message': message}
self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
`

Categories

Resources