Argparse, nargs with 1 required, 1 optional? - python

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

Related

how to get argparser arguments name?

I am trying to parse arguments passed from command line.I am passing 15 arguments at all. I am trying to group those by giving them same destination () I need to group those. Now when I print input i get lists f.e [mylogo.png, otherlogo.png] and so on. How I could get a result similar to {destination:'value1','value2'} . I know I could do it manually but It's not a solution in my case..
parser = argparse.ArgumentParser(prog='Moodle automation', add_help= False,
description=description(), usage='nana nanan nana')
parser.add_argument('-logo', '--set_logo',
help='',
dest='branding',
type=str,
action='append')
parser.add_argument('-c_logo', '--set_compact_logo',
help='',
dest='branding',
type=str,
action='append'
)
web_status.add_argument('-wn', '--web_new',
help=" ",
dest='web_state',
action="append")
web_status.add_argument('-wo', '--web_old',
help="",
dest="web_state",
action="append")
args = parser.parse_args()
branding_details = args.branding
print(branding_details)
in case input:
program.py -logo mylogo.png -c_logo custom_logo.png
I get output ['mylogo.png', 'custom_logo.png']
Here is a complete minimal example, where we can give several logo and compact_logo with nargs='*'. The result contains lists of arguments.
cli represents an example string you would to pass to the program.
import argparse
parser = argparse.ArgumentParser(prog='Moodle automation', add_help= False,
description='', usage='nana nanan nana')
parser.add_argument('-l', '--set_logo',
help='',
dest='logo',
nargs='*',
type=str)
parser.add_argument('-c', '--set_compact_logo',
help='',
dest='compact_logo',
nargs='*',
type=str)
cli = '-l mylogo.png -c custom_logo_1.png custom_logo_2.png'
args = parser.parse_args(cli.split())
print("List of arguments")
print(args.logo)
print(args.compact_logo)
print("Create a dict of key values for arguments")
dict_key_args = {key: value for key, value in args._get_kwargs()}
# Create the following data structure
# {'compact_logo': ['custom_logo_1.png', 'custom_logo_2.png'],
# 'logo': ['mylogo.png']}
If you just print(vars(args)), it will give output like this. vars() is always a handy function if you are dealing with object names.
{'branding': ['mylogo.png', 'custom_logo.png']}
It is just matter of removing those square brackets [] from output and if you are using multiple destinations, you can iterate over the args dictionary to get desired output.
args_dict = vars(args)
for k, v in args_dict.items():
print("{", k, ":", str(v).strip('[]'), "}")
output:
{ branding : 'mylogo.png', 'custom_logo.png' }
OR even better formatting with
args_dict = vars(args)
for k, v in args_dict.items():
print('{{{0}: {1}}}'.format(k, str(v).strip('[]')))
output:
{branding: 'mylogo.png', 'custom_logo.png'}

How to declare two different type variables through argpars

Argps approach need to accept two different types of variables, in this case only the array of string is accepting values and the int variable do not. how do i solve this? or is this the best approach to the case? I´m very new whit python thanks to all
python code:
def read_cmdline_args():
parser = argparse.ArgumentParser()
parser.add_argument("-l", "--search_by_name", help="Search by name",
type=str, nargs='+')
args = parser.parse_args()
return args
cmdline_args = read_cmdline_args()
uSerach_by_name = cmdline_args.serach_by_name
session.serach_by_name(tags=uSerach_by_name, amount=uSerach_by_name)
Original method:
session.serach_by_name(["peter"], amount=2)
command line:
py quickstart.py --l peter john 2 (the value 2 which is amount is not being accepted )
Do you want to pass list of words and some number? Simply add another argument:
def read_cmdline_args():
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--search_by_name", type=str, nargs='+', help="Search by name")
# added argument
parser.add_argument("-a", "--amount", type=int, default=0, help="Some amount")
return parser.parse_args()
Now you can run the script like this:
py quickstart.py -s peter john -a 2
Parsed arguments are:
Namespace(amount=2, search_by_name=['peter', 'john'])

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

Python argparse - Mutually exclusive group with default if no argument is given

I'm writing a Python script to process a machine-readable file and output a human-readable report on the data contained within.
I would like to give the option of outputting the data to stdout (-s) (by default) or to a txt (-t) or csv (-c) file. I would like to have a switch for the default behaviour, as many commands do.
In terms of Usage:, I'd like to see something like script [-s | -c | -t] input file, and have -s be the default if no arguments are passed.
I currently have (for the relevant args, in brief):
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout', action='store_true')
group.add_argument('-c', '--csv', action='store_true')
group.add_argument('-t', '--txt', action='store_true')
args = parser.parse_args()
if not any((args.stdout, args.csv, args.txt)):
args.stdout = True
So if none of -s, -t, or -c are set, stdout (-s) is forced to True, exactly as if -s had been passed.
Is there a better way to achieve this? Or would another approach entirely be generally considered 'better' for some reason?
Note: I'm using Python 3.5.1/2 and I'm not worried about compatibility with other versions, as there is no plan to share this script with others at this point. It's simply to make my life easier.
You could have each of your actions update the same variable, supplying stdout as the default value for that variable.
Consider this program:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument(
'-s', '--stdout', action='store_const', dest='type', const='s', default='s')
group.add_argument(
'-c', '--csv', action='store_const', dest='type', const='c')
group.add_argument(
'-t', '--txt', action='store_const', dest='type', const='t')
args = parser.parse_args()
print args
Your code could look like:
if args.type == 's':
ofile = sys.stdout
elif args.type == 'c':
ofile = ...
...
First alternative:
Rather than arbitrarily choose one of the .add_argument()s to specify the default type, you can use parser.set_defaults() to specify the default type.
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout', action='store_const', dest='type', const='s')
group.add_argument('-c', '--csv', action='store_const', dest='type', const='c')
group.add_argument('-t', '--txt', action='store_const', dest='type', const='t')
parser.set_defaults(type='s')
args = parser.parse_args()
print args
Second alternative:
Rather than specify the type as an enumerated value, you could store a callable into the type, and then invoke the callable:
import argparse
def do_stdout():
# do everything that is required to support stdout
print("stdout!")
return
def do_csv():
# do everything that is required to support CSV file
print("csv!")
return
def do_text():
# do everything that is required to support TXT file
print("text!")
return
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout', action='store_const', dest='type', const=do_stdout)
group.add_argument('-c', '--csv', action='store_const', dest='type', const=do_csv)
group.add_argument('-t', '--txt', action='store_const', dest='type', const=do_text)
parser.set_defaults(type=do_stdout)
args = parser.parse_args()
print args
args.type()
You can "cheat" with sys.argv :
import sys
def main():
if len(sys.argv) == 2 and sys.argv[1] not in ['-s', '-c', '-t', '-h']:
filename = sys.argv[1]
print "mode : stdout", filename
else:
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--stdout')
group.add_argument('-c', '--csv')
group.add_argument('-t', '--txt')
args = parser.parse_args()
if args.stdout:
print "mode stdout :", args.stdout
if args.csv:
print "mode csv :", args.csv
if args.txt:
print "mode txt :", args.txt
if __name__ == "__main__":
main()

argpase requiring additional options if a specific option is selected

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

Categories

Resources