argpase requiring additional options if a specific option is selected - python

I have read many help links on this issue but none were EXACTLY like my situation so I decided to post here. I am using argparse to grab some command line options. The issue I am having is 1 flag is always required (-m) so I defined it as so
parser.add_argument('-m','--mode', type=str, required=True , metavar='<add|del|list|delID>', choices=['add', 'del' , 'list', 'delID'])
As you can see the only possible acceptable parameters are 'add', 'del', 'list' and delID'
What I need it to do is force 2 additional options to be required if a certain option is entered from the args.mode flag. Here's what I have tried currently but the error is always being triggered
parser = argparse.ArgumentParser(description='Help Desk Calendar Tool')
parser.add_argument('-m','--mode', type=str, required=True , metavar='<add|del|list|delID>', choices=['add', 'del' , 'list', 'delID'])
parser.add_argument('-s', '--start', type=str, required=False, metavar='<Start date in the following format - YYYY-MM-DD>')
parser.add_argument('-e','--end', type=str, required=False, metavar='<End date in the following format - YYYY-MM-DD>')
parser.add_argument('-d','--delete', type=str, required=False, metavar='<Event ID Here>')
args = parser.parse_args()
if args.mode in ('add','del','list'):
print args.mode
if args.start is None or args.end is None:
parser.error('Options add, del and list all require the start (-s) and end (-e) date to be set')
if args.mode in ('delID'):
if args.start is not None or args.end is not None:
parser.error('The option delID can ONLY except the event ID, no other options can be entered')
if args.mode in ('delID'):
if args.delete is None:
parser.error('The delete (-d) option is required when delID mode is selected')
So if I run command.py -s 2016-02-11 -e 2016-02-16 -m add the first error condition is still triggered.
Now it does work for the delID conditional checks. Any suggestions?
Thanks
Update
Looks like the above does in fact work. Turns out I had an additional error check in my definition that was throwing the error
if mode in ('add','del','list'):
parser.error("Options add,del and list all require a start (-s) and end (-e) date!")
Thanks for bringing it to my attention

Completed working code
def get_args():
parser = argparse.ArgumentParser(description='Help Desk Calendar Tool')
parser.add_argument('-m','--mode', type=str, required=True , metavar='<add|del|list|delID>', choices=['add', 'del' , 'list', 'delID'])
parser.add_argument('-s', '--start', type=str, required=False, metavar='<Start date in the following format - YYYY-MM-DD>')
parser.add_argument('-e','--end', type=str, required=False, metavar='<End date in the following format - YYYY-MM-DD>')
parser.add_argument('-d','--delete', type=str, required=False, metavar='<Event ID Here>')
args = parser.parse_args()
if args.mode in ['add','del','list']:
if args.start is None or args.end is None:
parser.error('Options add, del and list all require the start (-s) and end (-e) date to be set')
if args.mode == 'delID':
if args.start is not None or args.end is not None:
parser.error('The option delID can ONLY except the event ID, no other options can be entered')
if args.mode == 'delID':
if args.delete is None:
parser.error('The delete (-d) option is required when delID mode is selected')
start = args.start
end = args.end
mode = args.mode
event = args.delete
return start,end,mode,event

Related

Argparse, nargs with 1 required, 1 optional?

I'm looking to provide flexible input into my parser. My use case is I have a python script to run some queries that are date range based. How I want the arg parser to operate is if only one date is supplied, use that date as both the start and end dates, but if two dates are used, use those.
So at least one date needs to be supplied, but no more than two dates. Is that possible?
def main(args):
parser = argparse.ArgumentParser(description="Run ETL for script")
parser.add_argument('-j', '--job_name', action='store', default=os.path.basename(__file__), type=str, help="Job name that gets used as part of the metadata for the job run.")
parser.add_argument("-d", "--date-range", nargs=2, metavar=('start_date', 'end_date'), default=['2021-01-01', '2021-01-01'], action='store', help="Operating range for script and data sources (as applicable)")
parser.add_argument('-e', '--env', action='store', choices=['dev', 'stage', 'prod'], default='dev', help="Environment to execute.")
parser.add_argument('-c', '--config', action='store', choices=['small', 'medium', 'large'], default='small', help="Resource request. See configs for each.")
args = parser.parse_args()
if args.date_range[0] <= args.date_range[1]:
job_meta = {
"script" : args.job_name,
"start_date" : args.date_range[0],
"end_date" : args.date_range[1],
"environment" : args.env,
"resource" : args.config
}
run_job(job_meta)
else:
print('Invalid date range. Start date as inputted may be following the end date.')
sys.exit(0)
if __name__ == "__main__":
main(sys.argv[1:])
In order to achieve that you can create a new action that check the number of argument given to the nargs.
import argparse
class LimitedAppend(argparse._AppendAction):
def __call__(self, parser, namespace, values, option_string=None):
if not (1 <= len(values) <= 2):
raise argparse.ArgumentError(self, "%s takes 1 or 2 values, %d given" % (option_string, len(values)))
super().__call__(parser, namespace, values, option_string)
parser.add_argument("-d", "--date-range", ..., nargs='+', action=LimitedAppend)
Then you can check the length of this argument and do what you wanted to do

How can i receive 1 argument with the python library parser and use just the argument?

I'm doing a command line application to verify if a website is active, and i want to receive one argument, but the parser needs receive an argument and a valor for this argument, So my question is: How can i use just 1 argument ?
This is my code:
if __name__=="__main__":
import requests
import datetime
from time import sleep
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('u', help="Unique verification")
parser.add_argument('c', help="Continuos verification")
parser.add_argument('s', help="Check and save to file")
parser.add_argument('d', help="Documentation")
args = parser.parse_args()
main(parser.parse_args)
My function main receives a char, how can i use a char via command line?
You are currently creating mandatory positional arguments, so that a call like
python3 verify.py foo bar baz bye
would result in
args.u == 'foo'
args.c == 'bar'
args.s == 'baz'
args.d == 'bye'
You want to define four options, using the store_true action so that providing the option will set a flag from its default False value to True.
parser = argparse.ArgumentParser()
parser.add_argument('-u', action='store_true', help="Unique verification")
parser.add_argument('-c', action='store_true', help="Continuos verification")
parser.add_argument('-s', action='store_true', help="Check and save to file")
parser.add_argument('-d', action='store_true', help="Documentation")
args = parser.parse_args()
Now a call like
python3 verify.py -u -s
would result in
args.u == True
args.c == False
args.s == True
args.d == False
If you want to further restrict the user to exactly one of the four options, use a mutual-exclusion group.
# Same as above, calling group.add_argument, not parser.add_argument
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-u', action='store_true', help="Unique verification")
group.add_argument('-c', action='store_true', help="Continuos verification")
group.add_argument('-s', action='store_true', help="Check and save to file")
group.add_argument('-d', action='store_true', help="Documentation")
args = parser.parse_args()

Python: How to get all default values from argparse

When module optparse is used, then I can get all default values for all command line arguments like this:
import optparse
if __name__ == '__main__':
parser = optparse.OptionParser(usage='pokus --help')
parser.add_option("-d", "--debug", action='store_true', dest="debug",
default=False, help='Enabling debugging.')
options, args = parser.parse_args()
print(parser.defaults)
Since optparse is deprecated it is wise to rewrite your code to use argparse module. However I can't find any way how to get all default values of all command line arguments added to parser object:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(usage='pokus --help')
parser.add_argument("-d", "--debug", action='store_true', dest='debug',
default=False, help='Enabling debugging.')
args = parser.parse_args()
# <---- How to get default values for all arguments here?
# Not: vars(args)
I want to get all default values when I run program with (./app.py -d) or without any command line argument (./app.py).
I found solution:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(usage='pokus --help')
parser.add_argument("-d", "--debug", action='store_true', dest='debug',
default=False, help='Enabling debugging.')
parser.add_argument("-e", "--example", action='store', dest='example',
default="", help='Example of argument.')
# Arguments from command line and default values
args = vars(parser.parse_args())
# Only default values
defaults = vars(parser.parse_args([]))
Then you can compare args and defaults values and distinguish between default values and values from command line.
If you do not want to parse an empty input string, you can use the method get_default in the parser object:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(usage='pokus --help')
parser.add_argument("-d", "--debug", action='store_true', dest='debug',
default=False, help='Enabling debugging.')
args = parser.parse_args()
# To get a single default:
d_default = parser.get_default('d')
# To get all defaults:
all_defaults = {}
for key in vars(args):
all_defaults[key] = parser.get_default(key)
# Edit: Adding an alternative one-liner (using dict comprehension):
all_defaults = {key: parser.get_default(key) for key in vars(args)}
Somewhat late to the party, but this is a function (with bonus unittest) that I've used in a couple of cases to get hold of the default arguments without having to parse first (parsing first can be annoying if you have required arguments that aren't available yet)
def get_argparse_defaults(parser):
defaults = {}
for action in parser._actions:
if not action.required and action.dest != "help":
defaults[action.dest] = action.default
return defaults
def get_argparse_required(parser):
required = []
for action in parser._actions:
if action.required:
required.append(action.dest)
return required
parser = argparse.ArgumentParser()
optional_defaults_dict = get_argparse_defaults(parser)
required_list = get_argparse_required(parser)
class TestDefaultArgs(unittest.TestCase):
def test_get_args(self):
parser = argparse.ArgumentParser()
parser.add_argument('positional_arg')
parser.add_argument('--required_option', required=True)
parser.add_argument('--optional_with_default', required=False, default="default_value")
parser.add_argument('--optional_without_default', required=False)
required_args = get_argparse_required(parser)
self.assertEqual(['positional_arg', 'required_option'], required_args)
default_args = get_argparse_defaults(parser)
self.assertEqual({'optional_with_default': 'default_value',
'optional_without_default': None},
default_args)
For your information, here's the code, at the start of parsing that initializes the defaults:
def parse_known_args(...):
....
# add any action defaults that aren't present
for action in self._actions:
if action.dest is not SUPPRESS:
if not hasattr(namespace, action.dest):
if action.default is not SUPPRESS:
setattr(namespace, action.dest, action.default)
# add any parser defaults that aren't present
for dest in self._defaults:
if not hasattr(namespace, dest):
setattr(namespace, dest, self._defaults[dest])
...
So it loops through the parser._actions list, collecting the action.default attribute. (An action is a Action class object that was created by the parser.add_argument method.). It also checks self._defaults. This is the dictionary modified by a parse.set_defaults method. That can be used to set defaults that aren't linked directly to an action.
After parsing the command line, default strings in the namespace may be evaluated (with the action.type), turning, for example a default='1' into an integer 1.
Handling of defaults in argparse isn't trivial. Your parse_args([]) probably is simplest, provided the parser is ok with that (i.e. doesn't have any required arguments).
I don't know now optparse sets the defaults attribute. There is a non-trival method, optparse.OptionParser.get_default_values.
For the above example:
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(usage='pokus --help')
parser.add_argument("-d", "--debug", action='store_true', dest='debug',
default=False, help='Enabling debugging.')
A. To get all the values with their defaults in a tuple format:
In[1]: args = parser.parse_known_args()[0]
In[2]: args._get_kwargs()
Out[1]: [('debug', False)]
to access to each item:
In[3]: args.debug
Out[2]: False
B. To get the values and their default as dictionary format
In[4]: dict_args = parser.parse_known_args()[0].__dict__
In[5]: dict_args
Out[3]: {'debug': False}
And to access each key:
In[6]: dict_args['debug']
Out[4]: False
Or print them iteratively:
In[7]: for key in dict_args:
... print('value for %s is: %s'% (key, dict_args[key]))
Out[5]: value for debug is: False

Can't pass values via command line

I have to write a program that reads from a file and writes some analysis to a text file. The program has to take some information via the command line but I can't see, to figure it out even given the template. I wrote a test program to see if I could succesfully pass command line input to the class.
#!/usr/bin/env python3
########################################################################
# CommandLine
########################################################################
class CommandLine() :
'''
Handle the command line, usage and help requests.
CommandLine uses argparse, now standard in 2.7 and beyond.
it implements a standard command line argument parser with various argument options,
a standard usage and help, and an error termination mechanism do-usage_and_die.
attributes:
all arguments received from the commandline using .add_argument will be
avalable within the .args attribute of object instantiated from CommandLine.
For example, if myCommandLine is an object of the class, and requiredbool was
set as an option using add_argument, then myCommandLine.args.requiredbool will
name that option.
'''
def __init__(self, inOpts=None) :
'''
CommandLine constructor.
Implements a parser to interpret the command line argv string using argparse.
'''
import argparse
self.parser = argparse.ArgumentParser(description = 'Program prolog - a brief description of what this thing does',
epilog = 'Program epilog - some other stuff you feel compelled to say',
add_help = True, #default is True
prefix_chars = '-',
usage = '%(prog)s [options] -option1[default] <input >output'
)
self.parser.add_argument('inFile', action = 'store', help='input file name')
self.parser.add_argument('outFile', action = 'store', help='output file name')
self.parser.add_argument('-lG', '--longestGene', action = 'store', nargs='?', const=True, default=True, help='longest Gene in an ORF')
self.parser.add_argument('-mG', '--minGene', type=int, choices= range(0, 2000), action = 'store', help='minimum Gene length')
self.parser.add_argument('-s', '--start', action = 'append', nargs='?', help='start Codon') #allows multiple list options
self.parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
if inOpts is None :
self.args = self.parser.parse_args()
else :
self.args = self.parser.parse_args(inOpts)
########################################################################
#MAIN GOES HERE
########################################################################
def main(myCommandLine=None):
'''
Implements the Usage exception handler that can be raised from anywhere in process.
'''
myCommandLine = CommandLine(myCommandLine)
#myCommandLine.args.inFile #has the input file name
#myCommandLine.args.outFile #has the output file name
#myCommandLine.args.longestGene #is True if only the longest Gene is desired
#myCommandLine.args.start #is a list of start codons
#myCommandLine.args.minGene #is the minimum Gene length to include
print (myCommandLine.args) # print the parsed argument string .. as there is nothing better to do
if myCommandLine.args.longestGene:
print ('longestGene is', str(myCommandLine.args.longestGene) )
else :
pass
class Test:
def __init__(self):
print(myCommandLine.args.minGene)
if __name__ == "__main__":
main()
class Test:
def __init__(self):
self.test()
def test(self, infile = myCommandLine.args.inFile, outfile = myCommandLine.args.outFile, longest = myCommandLine.args.longestGene, start = myCommandLine.args.start, min = myCommandLine.args.minGene):
print(infile)
print(outfile)
print(longest)
print(start)
print(min)
new_obj = Test()
The command line input should look like: python testcommand.py -minG 100 -longestG -starts ATG tass2ORFdata-ATG-100.txt
Supposedly the main program goes where it says "MAIN GOES HERE" but when I tried that I got an error that "myCommandline is not defined". So I moved the program to the end. But I get the error 'the '>' operator is reserved for future use"
I'm using Powershell if that matters. How do I get this data into my class?
You don't need CommandLine class. As suggested by James Mills, here is an example:
import argparse
class Test:
def __init__(self, infile, outfile, longest, start, min):
self.infile = infile
self.test()
def test(self):
print(self.infile)
def main():
parser = argparse.ArgumentParser(description = 'Program prolog',
epilog = 'Program epilog',
add_help = True, #default is True
prefix_chars = '-',
usage = 'xxx')
parser.add_argument('-i', '--inFile', action = 'store', help='input file name')
parser.add_argument('-o', '--outFile', action = 'store', help='output file name')
parser.add_argument('-lG', '--longestGene', action = 'store', nargs='?', const=True, default=True, help='longest Gene in an ORF')
parser.add_argument('-mG', '--minGene', type=int, choices= range(0, 20), action = 'store', help='minimum Gene length')
parser.add_argument('-s', '--start', action = 'append', nargs='?', help='start Codon') #allows multiple list options
parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
args = parser.parse_args()
test = Test(args.inFile, args.outFile, args.longestGene, args.minGene, args.start)
if __name__ == '__main__':
main()

Python Argparse add_mutually_exclusive_group()- Need ether 2 args or just 1 args

I am using Python's 2.7 argparse. I need it to where the user can enter arguements (-a and -b) OR (-c). But but not (-a and -b) and (-c) together. If (-a and -b) are chosen by the user instead of -c, both of them are required. How could I do this?
group_key = member_add.add_mutually_exclusive_group(required=True)
group_key.add_argument('-a',
required=True)
group_key.add_argument('-b',
required=True)
group_key.add_argument('-c',
required=True)
The current implementation of add_mutually_exclusive_group() doesn't actually
create mutually exclusive groups. There is a open bug to address this behavior.
Having said that, you could achieve this using:
(a) subcommands
Example code :
# create the top-level parser
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='help for subcommand')
# create the parser for the "cmd_1" command
parser_a = subparsers.add_parser('cmd_1', help='help for cmd_1')
parser_a.add_argument('-a', type=str, help='help for a')
parser_a.add_argument('-b', type=str, help='help for b')
# create the parser for the "cmd_2" command
parser_b = subparsers.add_parser('cmd_2', help='help for cmd_2')
parser_b.add_argument('-c', type=str, help='help for c')
parser.parse_args()
(b) Small hack for the simple case like yours :
ap=argparse.ArgumentParser()
# 1st group
ap.add_argument("-a", dest="value_a", help="help for a", required=False)
ap.add_argument("-b", dest="value_b", help="help for b", required=False)
# 2nd group
ap.add_argument("-c", dest="value_c", help="help for b", required=False)
args = ap.parse_args()
if (args.value_a or args.value_b):
if (args.value_a or args.value_b) and args.value_c:
print "-a and -b|-c are mutually exclusive ..."
elif not (args.value_a and args.value_b):
print "both -a and -b are required."

Categories

Resources