Having options in argparse with a dash - python

I want to have some options in argparse module such as --pm-export however when I try to use it like args.pm-export I get the error that there is not attribute pm. How can I get around this issue? Is it possible to have - in command line options?

As indicated in the argparse docs:
For optional argument actions, the value of dest is normally inferred from the option strings. ArgumentParser generates the value of dest by taking the first long option string and stripping away the initial -- string. Any internal - characters will be converted to _ characters to make sure the string is a valid attribute name
So you should be using args.pm_export.

Unfortunately, dash-to-underscore replacement doesn't work for positional arguments (not prefixed by --).
E.g:
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('logs-dir',
help='Directory with .log and .log.gz files')
parser.add_argument('results-csv', type=argparse.FileType('w'),
default=sys.stdout,
help='Output .csv filename')
args = parser.parse_args()
print args
# gives
# Namespace(logs-dir='./', results-csv=<open file 'lool.csv', mode 'w' at 0x9020650>)
So, you should use 1'st argument to add_argument() as attribute name and metavar kwarg to set how it should look in help:
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('logs_dir', metavar='logs-dir',
nargs=1,
help='Directory with .log and .log.gz files')
parser.add_argument('results_csv', metavar='results-csv',
nargs=1,
type=argparse.FileType('w'),
default=sys.stdout,
help='Output .csv filename')
args = parser.parse_args()
print args
# gives
# Namespace(logs_dir=['./'], results_csv=[<open file 'lool.csv', mode 'w' at 0xb71385f8>])

Dashes are converted to underscores:
import argparse
pa = argparse.ArgumentParser()
pa.add_argument('--foo-bar')
args = pa.parse_args(['--foo-bar', '24'])
print args # Namespace(foo_bar='24')

Concise and explicit but probably not always acceptable way would be to use vars():
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('a-b')
args = vars(parser.parse_args())
print(args['a-b'])

getattr(args, 'positional-arg')
This is another OK workaround for positional arguments:
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('a-b')
args = parser.parse_args(['123'])
assert getattr(args, 'a-b') == '123'
Tested on Python 3.8.2.

I guess the last option is to change shorten option -a to --a
import argparse
parser = argparse.ArgumentParser(description="Help")
parser.add_argument("--a", "--argument-option", metavar="", help="") # change here
args = parser.parse_args()
option = args.a # And here
print(option)

Related

python argparse - How to prevent by using a combination of arguments

In argparse, I want to prevent a particular combination of arguments. Lets see the sample code.
Sample:
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--firstname', dest='fn', action='store')
parser.add_argument('--lastname', dest='ln', action='store')
parser.add_argument('--fullname', dest='full', action='store')
args = parser.parse_args()
For eg: --firstname --lastname --fullname
The user can run the code in 2 days.
Way 1:
code.py --firstname myfirst --lastname mylast
Way 2:
code.py --fullname myfullname
Prevent
We should not use the combination fistname, fullname or lastname, fullname. If we use both, then it should not execute.
Can someone help me to fix this?
Not sure that is an argparse specific behavior that is possible. But as long as those items are going to their own variables in the argparse resposes its a simple set of programming to check which ones are set and issue a message and exit.
example (assuming the result of parsing is in argsvalue):
if argsvalue.fullname and (argsvalue.firstname or argsvalue.lastname):
print ("missuse of name options.....")
This assumes the argparse default for the vars is None (then if anything is set in them they will test to true with the logic above...
Like this answer proposes (on a similar question) you can do something like the following by using subparsers for both cases:
# create the top-level parser
parser = argparse.ArgumentParser(add_help=false)
subparsers = parser.add_subparsers(help='help for subcommands')
# create the parser for the "full_name" command
full_name = subparsers.add_parser('full_name', help='full_name help')
full_name.add_argument('--fullname', dest='full', action='store')
# create the parser for the "separate_names" command
separate_names = subparsers.add_parser('separate_names', help='separate_names help')
separate_names.add_argument('--firstname', dest='fn', action='store')
separate_names.add_argument('--lastname', dest='ln', action='store')
args = parser.parse_args()
You can improve it even further by requiring both the first and last name of the user as it generally makes sense.
You can split your arguments into separate commands and use subparsers, for example:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_clean = subparsers.add_parser('clean', description='clean build folder')
parser_clean.add_argument('--all', help='clean build folder and logs', action='store_true')
parser_deploy = subparsers.add_parser('deploy')
parser_deploy.add_argument('object')
parser_deploy.add_argument('--nc', help='no cleanup', action='store_true')
args = parser.parse_args()

unittest for command line arguments

How do I unit test my script for incorrect command line arguments?
For example,
my_script.py -t
should give an error since -t flag is not present, as shown in the code below:
parser = OptionParser()
parser.add_option("-d",
action="callback",
callback=get_bios_difference,
help="Check difference between two files"
)
(options, args) = parser.parse_args()
if len(sys.argv) == 1: # if only 1 argument, it's the script name
parser.print_help()
exit()
First, you need a unit to test:
def parse_options(args=None):
# Duplicating the default behavior of OptionParser.parse_args
if args is None:
args = sys.argv[1:]
parser = OptionParser()
parser.add_option("-d",
action="callback",
callback=get_bios_difference,
help="Check difference between two files"
)
(options, args) = parser.parse_args(args)
if not args:
parser.print_help()
exit()
Note that we explicitly pass args to parse_args, rather than let it assume sys.argv is to be parsed.
In production, you can call parse_options() and it will work as before. For testing, you can call parse_options([]) or parse_options(["-t"]) or whatever other combination of arguments you want to test.
All that said, you should be using argparse instead of optparse (which has been deprecated for years) in new code.
OptionParser
Deprecated since version 2.7: The optparse module is deprecated and will not be developed further; development will continue with the argparse module.
your questions has nothing to do with unittest? there is no need to assert, the nargs=2 defined 2 items needed for --d.
argparse will check the number of args for you, here is parameter.py
import argparse
from pprint import pprint
def get_parser():
parser = argparse.ArgumentParser(description='example of argparse')
parser.add_argument('--d', type=str, nargs=2, help='2 files')
return parser
if __name__ == '__main__':
parser = get_parser()
args = parser.parse_args(sys.argv[1:])
pprint(args)
run with 2 files:
parameter.py --d a b
Namespace(d=['a', 'b'])
When run with 1 parameter
parameter.py --d a
parameter.py: error: argument --d: expected 2 argument(s)

Python argparse: Too few arguments

Here's my code:
def parse_args():
parser = argparse.ArgumentParser(description='Simple training script for object detection from a CSV file.')
parser.add_argument('csv_path', help='Path to CSV file')
parser.add_argument('--weights', help='Weights to use for initialization (defaults to ImageNet).', default='imagenet')
parser.add_argument('--batch-size', help='Size of the batches.', default=1, type=int)
return parser.parse_args()
when I run my code, I get an error:
usage: Train.py [-h] [--weights WEIGHTS] [--batch-size BATCH_SIZE] csv_path
Train.py: error: too few arguments
Any idea where I'm going wrong?
This is because you did not specify the number of arguments expected after each flag with nargs as such:
import argparse
def parse_args():
parser = argparse.ArgumentParser(description='Simple training script for object detection from a CSV file.')
parser.add_argument('csv_path', nargs="?", type=str, help='Path to CSV file')
parser.add_argument('--weights', nargs="?", help='Weights to use for initialization (defaults to ImageNet).', default='imagenet')
parser.add_argument('--batch-size', nargs="?", help='Size of the batches.', default=1, type=int)
return parser.parse_args()
parse_args()
According to the doc:
If the nargs keyword argument is not provided, the number of arguments consumed is determined by the action. Generally this means a single command-line argument will be consumed and a single item (not a list) will be produced.
'?'. One argument will be consumed from the command line if possible, and produced as a single item. If no command-line argument is present, the value from default will be produced. Note that for optional arguments, there is an additional case - the option string is present but not followed by a command-line argument. In this case the value from const will be produced. Some examples to illustrate this:
Details here
The first arg csv_path is required (you did not provide some default value), so you need to pass that to your command line like below:
python Train.py some_file.csv # or the path to your file if it's not in the same directory
Try this:
import argparse
import sys
import csv
parser = argparse.ArgumentParser()
parser.add_argument('--file', default='fileName.csv')
args = parser.parse_args()
csvdata = open(args.file, 'rb')

Arguments that are dependent on other arguments with Argparse

I want to accomplish something like this:
-LoadFiles
-SourceFile "" -DestPath ""
-SourceFolder "" -DestPath ""
-GenericOperation
-SpecificOperation -Arga "" -Argb ""
-OtherOperation -Argc "" -Argb "" -Argc ""
A user should be able to run things like:
-LoadFiles -SourceFile "somePath" -DestPath "somePath"
or
-LoadFiles -SourceFolder "somePath" -DestPath "somePath"
Basically, if you have -LoadFiles, you are required to have either -SourceFile or -SourceFolder after. If you have -SourceFile, you are required to have -DestPath, etc.
Is this chain of required arguments for other arguments possible? If not, can I at least do something like, if you have -SourceFile, you MUST have -DestPath?
You can use subparsers in argparse
import argparse
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', required=True, help='foo help')
subparsers = parser.add_subparsers(help='sub-command help')
# create the parser for the "bar" command
parser_a = subparsers.add_parser('bar', help='a help')
parser_a.add_argument('bar', type=int, help='bar help')
print(parser.parse_args())
After you call parse_args on the ArgumentParser instance you've created, it'll give you a Namespace object. Simply check that if one of the arguments is present then the other one has to be there too. Like:
args = parser.parse_args()
if ('LoadFiles' in vars(args) and
'SourceFolder' not in vars(args) and
'SourceFile' not in vars(args)):
parser.error('The -LoadFiles argument requires the -SourceFolder or -SourceFile')
There are some argparse alternatives which you can easily manage cases like what you mentioned.
packages like:
click or
docopt.
If we want to get around the manual implementation of chain arguments in argparse, check out the Commands and Groups in click for instance.
Here is a sample that in case you specify --makeDependency, forces you to specify --dependency with a value as well.
This is not done by argparse alone, but also by by the program that later on validates what the user specified.
#!/usr/bin/env python
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument('--makeDependency', help='create dependency on --dependency', action='store_true')
parser.add_argument('--dependency', help='dependency example')
args = parser.parse_args()
if args.makeDependency and not args.dependency:
print "error on dependency"
sys.exit(1)
print "ok!"

Display help message with Python argparse when script is called without any arguments

Assume I have a program that uses argparse to process command line arguments/options. The following will print the 'help' message:
./myprogram -h
or:
./myprogram --help
But, if I run the script without any arguments whatsoever, it doesn't do anything. What I want it to do is to display the usage message when it is called with no arguments. How is that done?
This answer comes from Steven Bethard on Google groups. I'm reposting it here to make it easier for people without a Google account to access.
You can override the default behavior of the error method:
import argparse
import sys
class MyParser(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(2)
parser = MyParser()
parser.add_argument('foo', nargs='+')
args = parser.parse_args()
Note that the above solution will print the help message whenever the error
method is triggered. For example, test.py --blah will print the help message
too if --blah isn't a valid option.
If you want to print the help message only if no arguments are supplied on the
command line, then perhaps this is still the easiest way:
import argparse
import sys
parser=argparse.ArgumentParser()
parser.add_argument('foo', nargs='+')
if len(sys.argv)==1:
parser.print_help(sys.stderr)
sys.exit(1)
args=parser.parse_args()
Note that parser.print_help() prints to stdout by default. As init_js suggests, use parser.print_help(sys.stderr) to print to stderr.
Instead of writing a class, a try/except can be used instead
try:
options = parser.parse_args()
except:
parser.print_help()
sys.exit(0)
The upside is that the workflow is clearer and you don't need a stub class. The downside is that the first 'usage' line is printed twice.
This will need at least one mandatory argument. With no mandatory arguments, providing zero args on the commandline is valid.
With argparse you could use ArgumentParser.print_usage():
parser.argparse.ArgumentParser()
# parser.add_args here
# sys.argv includes a list of elements starting with the program
if len(sys.argv) < 2:
parser.print_usage()
sys.exit(1)
Printing help
ArgumentParser.print_usage(file=None)
  Print a brief description of how the ArgumentParser should be invoked on the command line. If file is None, sys.stdout is assumed.
The cleanest solution will be to manually pass default argument if none were given on the command line:
parser.parse_args(args=None if sys.argv[1:] else ['--help'])
Complete example:
import argparse, sys
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost', help='Host to connect to')
# parse arguments
args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])
# use your args
print("connecting to {}".format(args.host))
This will print complete help (not short usage) if called w/o arguments.
If you associate default functions for (sub)parsers, as is mentioned under add_subparsers, you can simply add it as the default action:
parser = argparse.ArgumentParser()
parser.set_defaults(func=lambda x: parser.print_usage())
args = parser.parse_args()
args.func(args)
Add the try-except if you raise exceptions due to missing positional arguments.
If you have arguments that must be specified for the script to run - use the required parameter for ArgumentParser as shown below:-
parser.add_argument('--foo', required=True)
parse_args() will report an error if the script is run without any arguments.
Throwing my version into the pile here:
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()
if not vars(args):
parser.print_help()
parser.exit(1)
You may notice the parser.exit - I mainly do it like that because it saves an import line if that was the only reason for sys in the file...
There are a pair of one-liners with sys.argv[1:] (a very common Python's idiom to refer the command line arguments, being sys.argv[0] the script's name) that can do the job.
The first one is self-explanatory, clean and pythonic:
args = parser.parse_args(None if sys.argv[1:] else ['-h'])
The second one is a little hackier. Combining the previously evaluated fact that an empty list is False with the True == 1 and False == 0 equivalences you get this:
args = parser.parse_args([None, ['-h']][not sys.argv[1:]])
Maybe too many brackets, but pretty clear if a previous argument selection was made.
_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])
parser.print_help()
parser.exit()
The parser.exit method also accept a status (returncode), and a message value (include a trailing newline yourself!).
an opinionated example,
:)
#!/usr/bin/env python3
""" Example argparser based python file
"""
import argparse
ARGP = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawTextHelpFormatter,
)
ARGP.add_argument('--example', action='store_true', help='Example Argument')
def main(argp=None):
if argp is None:
argp = ARGP.parse_args() # pragma: no cover
if 'soemthing_went_wrong' and not argp.example:
ARGP.print_help()
ARGP.exit(status=64, message="\nSomething went wrong, --example condition was not set\n")
if __name__ == '__main__':
main() # pragma: no cover
Example calls:
$ python3 ~/helloworld.py; echo $?
usage: helloworld.py [-h] [--example]
Example argparser based python file
optional arguments:
-h, --help show this help message and exit
--example Example Argument
Something went wrong, --example condition was not set
64
$ python3 ~/helloworld.py --example; echo $?
0
Most of the answers here required another module, such as sys, to be imported or were using optional arguments. I wanted to discover an answer that used only argparse, worked with required arguments, and if possible worked without catching exceptions. I ended up with the following:
import argparse
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser(add_help=False)
arg_parser.add_argument('input_file', type=str, help='The path to the input file.')
arg_parser.add_argument('output_file', type=str, help='The path to the output file.')
arg_parser.add_argument('-h','--help', action='store_true', help='show this help message and exit')
arg_parser.usage = arg_parser.format_help()
args = arg_parser.parse_args()
The main idea was to use the format_help function in order to provide the help string to the usage statement. Setting add_help to False in the call to ArgumentParser() prevents the help statement from printing twice in certain circumstances. However, I had to create an argument for the optional help argument that mimicked the typical help message once it was set to False in order to display the optional help argument in the help message. The action is set to store_true in the help argument to prevent the help message from filling in a value like HELP for the parameter when it prints the help message.
So for a really simple answer. Most of the time with argparse you are checking to see if parameters are set anyway, to call a function that does something.
If no parameters, just else out at the end and print the help. Simple and works.
import argparse
import sys
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("--holidays", action='store_true')
group.add_argument("--people", action='store_true')
args=parser.parse_args()
if args.holidays:
get_holidays()
elif args.people:
get_people()
else:
parser.print_help(sys.stderr)
Here is another way to do it, if you need something flexible where you want to display help if specific params are passed, none at all or more than 1 conflicting arg:
import argparse
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--days', required=False, help="Check mapped inventory that is x days old", default=None)
parser.add_argument('-e', '--event', required=False, action="store", dest="event_id",
help="Check mapped inventory for a specific event", default=None)
parser.add_argument('-b', '--broker', required=False, action="store", dest="broker_id",
help="Check mapped inventory for a broker", default=None)
parser.add_argument('-k', '--keyword', required=False, action="store", dest="event_keyword",
help="Check mapped inventory for a specific event keyword", default=None)
parser.add_argument('-p', '--product', required=False, action="store", dest="product_id",
help="Check mapped inventory for a specific product", default=None)
parser.add_argument('-m', '--metadata', required=False, action="store", dest="metadata",
help="Check mapped inventory for specific metadata, good for debugging past tix", default=None)
parser.add_argument('-u', '--update', required=False, action="store_true", dest="make_updates",
help="Update the event for a product if there is a difference, default No", default=False)
args = parser.parse_args()
days = args.days
event_id = args.event_id
broker_id = args.broker_id
event_keyword = args.event_keyword
product_id = args.product_id
metadata = args.metadata
make_updates = args.make_updates
no_change_counter = 0
change_counter = 0
req_arg = bool(days) + bool(event_id) + bool(broker_id) + bool(product_id) + bool(event_keyword) + bool(metadata)
if not req_arg:
print("Need to specify days, broker id, event id, event keyword or past tickets full metadata")
parser.print_help()
sys.exit()
elif req_arg != 1:
print("More than one option specified. Need to specify only one required option")
parser.print_help()
sys.exit()
# Processing logic here ...
Cheers!
I like to keep things as simple as possible, this works great:
#!/usr/bin/env python3
Description = """Tool description"""
Epilog = """toolname.py -a aflag -b bflag with these combined it does blah"""
arg_parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=Description,
epilog=Epilog,
)
try:
if len(sys.argv) == 1:
arg_parser.print_help()
except Exception as e:
print(e)
This is how I start all my tools as its always good to include some examples
When call add_subparsers method save the first positional argument to dest= and check value after argparse has been initialized, like this:
subparsers = parser.add_subparsers(dest='command')
And just check this this variable:
if not args.command:
parser.print_help()
parser.exit(1) # If exit() - exit code will be zero (no error)
Full example:
#!/usr/bin/env python
""" doc """
import argparse
import sys
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest='command',
help='List of commands')
list_parser = subparsers.add_parser('list',
help='List contents')
list_parser.add_argument('dir', action='store',
help='Directory to list')
create_parser = subparsers.add_parser('create',
help='Create a directory')
create_parser.add_argument('dirname', action='store',
help='New directory to create')
create_parser.add_argument('--read-only', default=False, action='store_true',
help='Set permissions to prevent writing to the directory')
args = parser.parse_args()
if not args.command:
parser.print_help()
parser.exit(1)
print(vars(args)) # For debug
This approach is a lot more elegant than most others. Instead of overriding error(), you can control the behaviour a lot more precisely by wrapping the parse_args() method:
import sys
import argparse
HelpFlags = ('help', '--help', '-h', '/h', '?', '/?', )
class ArgParser (argparse.ArgumentParser):
def __init__(self, *args, **kws):
super().__init__(*args, **kws)
def parse_args(self, args=None, namespace=None):
if args is None:
args = sys.argv[1:]
if len(args) < 1 or (args[0].lower() in HelpFlags):
self.print_help(sys.stderr)
sys.exit()
return super().parse_args(args, namespace)
Set your positional arguments with nargs, and check if positional args are empty.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?')
args = parser.parse_args()
if not args.file:
parser.print_help()
Reference Python nargs
If your command is something where a user needs to choose some action, then use a mutually exclusive group with required=True.
This is kind of an extension to the answer given by pd321.
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--batch", action='store', type=int, metavar='pay_id')
group.add_argument("--list", action='store_true')
group.add_argument("--all", action='store_true', help='check all payments')
args=parser.parse_args()
if args.batch:
print('batch {}'.format(args.batch))
if args.list:
print('list')
if args.all:
print('all')
Output:
$ python3 a_test.py
usage: a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: error: one of the arguments --batch --list --all is required
This only give the basic help. And some of the other answers will give you the full help. But at least your users know they can do -h
This isn't good (also, because intercepts all errors), but:
def _error(parser):
def wrapper(interceptor):
parser.print_help()
sys.exit(-1)
return wrapper
def _args_get(args=sys.argv[1:]):
parser = argparser.ArgumentParser()
parser.error = _error(parser)
parser.add_argument(...)
...
Here is definition of the error function of the ArgumentParser class.
As you see, the following signature takes two arguments. However, functions outside the class know nothing about first argument self, because, roughly speaking, this argument is for the class.
def _error(self, message):
self.print_help()
sys.exit(-1)
def _args_get(args=sys.argv[1:]):
parser = argparser.ArgumentParser()
parser.error = _error
...
will output:
"AttributeError: 'str' object has no attribute 'print_help'"
You can pass parser (self) in _error function, by calling it:
def _error(self, message):
self.print_help()
sys.exit(-1)
def _args_get(args=sys.argv[1:]):
parser = argparser.ArgumentParser()
parser.error = _error(parser)
...
But if you don't want exit the program right now, return it:
def _error(parser):
def wrapper():
parser.print_help()
sys.exit(-1)
return wrapper
Nonetheless, parser doesn't know that it has been modified. Thus, when an error occurs, it will print the cause of it (by the way, it's a localized translation). So intercept it:
def _error(parser):
def wrapper(interceptor):
parser.print_help()
sys.exit(-1)
return wrapper
Now, when an error occurs, parser will print the cause of it, and you'll intercept it, look at it, and... throw out.

Categories