I want to create a program which selects users from a database between 2 dates given on the command line. I have:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("--date1","-d1",help="Show users between dates",type=str)
group.add_argument("--date2","-d2",help="Show users between dates",type=str)
if args.date1 and args.date2:
DataCalculation.show_users_between_date(args.date1,args.date2)
And in my DataCalculation I have query to get users between 2 dates.
Unfortunately this solution doesnt work and I get error: argument --date2/-d2: not allowed with argument --date1/d1
I was running program like: py main.py -d1 1994-01-01 -d2 1995-12-31
I was thinking that I can split these 2 dates to list in function and give only 1 argument like: py main.py -d 1994-01-01 1995-12-31, but this idea doesn't work too. Is there an easy way to use 2 arguments which have to be given together?
You're looking for inclusivity, not exclusivity. You can accomplish that by using nargs=2 with one option, like your second case.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--date",
"-d",
nargs=2,
metavar=('start', 'end'), # Describes each argument
help="Show users between start and end dates",
)
args = parser.parse_args()
print(args)
Usage:
$ ./tmp.py -d 1994-01-01 1995-12-31
Namespace(date=['1994-01-01', '1995-12-31'])
$ ./tmp.py -d 1994-01-01
usage: tmp.py [-h] [--date start end]
tmp.py: error: argument --date/-d: expected 2 arguments
$ ./tmp.py -d 1994-01-01 1995-12-31 1998
usage: tmp.py [-h] [--date start end]
tmp.py: error: unrecognized arguments: 1998
$ ./tmp.py -h
usage: tmp.py [-h] [--date start end]
optional arguments:
-h, --help show this help message and exit
--date start end, -d start end
Show users between start and end dates
You could use
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("--daterange","-dr",help="Show users between dates",type=str)
args = parser.parse_args()
date1, date2 = args.daterange.split()
print(date1)
And then quotes around your arguments as in
python test.py -dr "1994-01-01 1995-12-31"
Which yields with the above snippet:
1994-01-01
Related
How can a program accept/validate a set of parameters, depending on a previous parameter/option?
e.g:
params:
<action1> -p <path> -name <name> -t <type>
<action2> -v <value> -name <name>
<action3> -p <path> -t <type>
<action4> -m <mode1 | mode2>
--verbose
--test
--..
So if one of the actionX parameters is used (only one can be used), additional parameters might be required.
For instance for action2 the -v and -name are required.
valid input:
python myparser.py action2 -v 11 -name something --test --verbose
python myparser.py action4 -m mode1
python myparser.py --test
invalid input:
python myparser.py action2 -v 11
python myparser.py action4 -n name1
Can the argparse validate this or is it better to add all of them as optional and validate them later on?
You can use subparsers
Simple example for your case
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--test", action="store_true")
subparsers = parser.add_subparsers()
parser_action2 = subparsers.add_parser("action2")
parser_action2.add_argument("-v", required=True)
parser_action2.add_argument("-name", type=str, required=True)
parser_action4 = subparsers.add_parser("action4")
parser_action4.add_argument("-m", type=str, required=True)
valid case
parser.parse_args(["action2", "-v", "11", "-name", "something"])
parser.parse_args(["action4", "-m", "mode1"])
parser.parse_args(["--test"])
# Namespace(v='11', name='something')
# Namespace(m='mode1')
# Namespace(test=True)
Invalid case
parser.parse_args(["action2", "-v", "11"])
# action2: error: the following arguments are required: -name
parser.parse_args(["action4", "-n", "name1"])
# action4: error: the following arguments are required: -m
EDIT: And in case you want to use shared argument which is required in one and optional in others, it is better to make them optional and validate them later as you said.
I'd like to do the following, where the type specified dictates what parameters I can use after.
For example, for cmd1, I'd expect a param1 and param2.
For cmd2, I'd expect a param3, param4, param5, and param6.
For cmd3, I'd expect no parameters.
If I don't specify the expected parameters, an error should be shown.
Also, is there someway I could make it so that issuing a -h would show the different combinations allowed?
In all situations, there may be other tags that could be specified, such as --id 5 or --config 1.
Is this possible with python's argparse?
I.e.,
python test.py --type cmd1 param1 param2 --id 5
python test.py --config 2 --type cmd2 param3 param4 param5 param6
python test.py --type cmd3 --config 1
This use of subcommands will get most of what you want. Read the docs for explainations
#python test.py --type cmd1 param1 param2 --id 5
#python test.py --config 2 --type cmd2 param3 param4 param5 param6
#python test.py --type cmd3 --config 1
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--config', type=int)
parser.add_argument('--id', type=int)
sp = parser.add_subparsers(dest='type', required=True)
sp1 = sp.add_parser('cmd1')
sp1.add_argument('param1')
sp1.add_argument('param2')
sp2 = sp.add_parser('cmd2')
sp2.add_argument('parame1')
# etc
sp3 = sp.add_parser('cmd3')
args = parser.parse_args()
print(args)
test cases
1535:~/mypy$ python3 stack70763135.py -h
usage: stack70763135.py [-h] [--config CONFIG] [--id ID] {cmd1,cmd2,cmd3} ...
positional arguments:
{cmd1,cmd2,cmd3}
optional arguments:
-h, --help show this help message and exit
--config CONFIG
--id ID
1535:~/mypy$ python3 stack70763135.py --id 5 cmd1 foo1 foo2
Namespace(config=None, id=5, param1='foo1', param2='foo2', type='cmd1')
1535:~/mypy$ python3 stack70763135.py --config 2 cmd2 foo1
Namespace(config=2, id=None, parame1='foo1', type='cmd2')
1536:~/mypy$ python3 stack70763135.py --config 3 cmd3
Namespace(config=3, id=None, type='cmd3')
The help's might be more fragmented that what you want, but the info is all there.
id and config have to come before the cmd entries.
You should be able to use nargs, I believe. For example:
parser.add_argument('--cmd1', nargs=2) # expects 2 parameters for this argument
nargs will save the parameters into a list (even if you set it to 1!). There are even special characters you can use for nargs, such as doing nargs='?' to get an indeterminate number of parameters.
this should work:
parser.add_argument("--cmd1",nargs=2)
args = parser.parse_args()
print(args.cmd1)
and running it:
>>> py parser.py --cmd1 onlyoneargument
usage: RecFcs.py [-h] [--cmd1 CMD1 CMD1]
RecFcs.py: error: argument --cmd1: expected 2 arguments
if we provide 2 arguments
>>> py parser.py --cmd1 one two
["one","two"]
I wanted to create an interface with argparse for my script with subcommands; so, if my script is script.py, I want to call it like python script.py command --foo bar, where command is one of the N possible custom commands.
The problem is, I already tried looking for a solution here on StackOverflow, but it seems like everything I tried is useless.
What I have currently is this:
parser = argparse.ArgumentParser()
parser.add_argument("-x", required=True)
parser.add_argument("-y", required=True)
parser.add_argument("-f", "--files", nargs="+", required=True)
# subparsers for commands
subparsers = parser.add_subparsers(title="Commands", dest="command")
subparsers.required = True
summary_parser = subparsers.add_parser("summary", help="help of summary command")
If I try to run it with:
args = parser.parse_args("-x 1 -y 2 -f a/path another/path".split())
I got this error, as it should be: script.py: error: the following arguments are required: command.
If, however, I run this command:
args = parser.parse_args("summary -x 1 -y 2 -f a/path another/path".split())
I got this error, that I shouldn't have: script.py: error: the following arguments are required: -x, -y, -f/--files.
If I put the command at the end, changing also the order of arguments because of -f, it works.
args = parser.parse_args("-x 1 -f a/path another/path -y 2 summary".split())
If I add the parents keyword in subparser, so substitute the summary_parser line with summary_parser = subparsers.add_parser("summary", help=HELP_CMD_SUMMARY, parents=[parser], add_help=False), then I got:
script.py summary: error: the following arguments are required: command when summary is in front of every other argument;
script.py summary: error: the following arguments are required: -x, -y, -f/--files, command when summary is at the end of the args.
My question is, how I have to setup the parsers to have the behaviour script.py <command> <args>? Every command shares the same args, because they are needed to create certain objects, but at the same time every command can needs other arguments too.
Creating another parser helped me getting what I wanted.
The root parser should add all the optional arguments - and also have add_help=False, to avoid an help message conflict -, then another parser - parser2, with a lot of fantasy - will be created.
The second parser will have subparsers, and they all needs to specify as parents the root parser.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument() ...
parser2 = argparse.ArgumentParser()
subparsers = parser2.add_subparsers(title="Commands", dest="command")
subparsers.required = True
summary_parser = subparsers.add_parser("summary", parents=[parser])
summary_parser.add_argument("v", "--verbose", action="store_true")
# parse args
args = parser2.parse_args()
Now the output will be this:
usage: script.py [-h] {summary} ...
optional arguments:
-h, --help show this help message and exit
Commands:
{summary}
summary For each report print its summary, then exit
usage: script.py summary [-h] -x X -y Y -f FILES [FILES ...] [-v]
optional arguments:
-h, --help show this help message and exit
-x X
-y Y
-f FILES [FILES ...], --files FILES [FILES ...]
-v, --verbose
I'm trying to have a mutually exclusive group between different groups:
I have the arguments -a,-b,-c, and I want to have a conflict with -a and -b together, or -a and -c together. The help should show something like [-a | ([-b] [-c])].
The following code does not seem to do have mutually exclusive options:
import argparse
parser = argparse.ArgumentParser(description='My desc')
main_group = parser.add_mutually_exclusive_group()
mysub_group = main_group.add_argument_group()
main_group.add_argument("-a", dest='a', action='store_true', default=False, help='a help')
mysub_group.add_argument("-b", dest='b', action='store_true',default=False,help='b help')
mysub_group.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
parser.parse_args()
Parsing different combinations - all pass:
> python myscript.py -h
usage: myscript.py [-h] [-a] [-b] [-c]
My desc
optional arguments:
-h, --help show this help message and exit
-a a help
> python myscript.py -a -c
> python myscript.py -a -b
> python myscript.py -b -c
I tried changing the mysub_group to be add_mutually_exclusive_group turns everything into mutually exclusive:
> python myscript.py -h
usage: myscript.py [-h] [-a | -b | -c]
My desc
optional arguments:
-h, --help show this help message and exit
-a a help
-b b help
-c c help
How can I add arguments for [-a | ([-b] [-c])]?
So, this has been asked a number of times. The simple answer is "with argparse, you can't". However, that's the simple answer. You could make use of subparsers, so:
import argparse
parser = argparse.ArgumentParser(description='My desc')
sub_parsers = parser.add_subparsers()
parser_a = sub_parsers.add_parser("a", help='a help')
parser_b = sub_parsers.add_parser("b", help='b help')
parser_b.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
parser.parse_args()
You then get:
$ python parser -h
usage: parser [-h] {a,b} ...
My desc
positional arguments:
{a,b}
a a help
b b help
optional arguments:
-h, --help show this help message and exit
and:
$ python parser b -h
usage: parser b [-h] [-c]
optional arguments:
-h, --help show this help message and exit
-c c help
If you would prefer your arguments as stated in the question, have a look at docopt, it looks lovely, and should do exactly what you want.
argument_groups don't affect the parsing. They just contribute to the help formatting. So defining a group within an mutually_exclusive_group does not help with this problem.
There is a proposed patch, http://bugs.python.org/issue10984, 'argparse add_mutually_exclusive_group should accept existing arguments to register conflicts', that would allow you to define two mutually_exclusive_groups, one [-a | -b] and the other [-a | -c]. Creating a 2nd group that includes an argument (-a) that is already defined is the trivial part of this patch. Producing a meaningful usage line is more difficult, and required a rewrite of several HelpFormatter methods.
import argparse
parser = argparse.ArgumentParser(description='My desc')
group1 = parser.add_mutually_exclusive_group()
action_a = group1.add_argument("-a", dest='a', action='store_true', default=False, help='a help')
group1.add_argument("-b", dest='b', action='store_true',default=False,help='b help')
group2 = parser.add_mutually_exclusive_group()
group2.add_argument("-c", dest='c', action='store_true',default=False,help='c help')
group2._group_actions.append(action_a) # THE KLUDGE
print parser.format_usage()
# usage: stack16769409.py [-h] [-a | -b] [-c]
args = parser.parse_args()
Usage does not show the 2 groups correctly. But it does accept -b -c, while objecting to -a -b and -a -c. But you can write a custom usage line.
I have a test code as follows, that shall take EITHER the positional argument file OR all the optional arguments time, expression and name:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-t","--time")
parser.add_argument("-x","--expression")
parser.add_argument("-n","--name")
parser.add_argument("file")
print parser.parse_args()
The following combination should work
test.py filename
test.py -t 5 -x foo -n test
but NOT these:
test.py filename -t 5 # should raise error because the positional and the optional -t argument cannot be used together
test.py -t 5 -x foo # should raise an error because all three of the optional arguments are required
Any simple solution to that problem?
The first issue is that you have specified that file is positional which will make it required. You will probably need to convert it to a optional argument.
Here is a simple way to check that the correct arguments have been provided:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-t","--time")
parser.add_argument("-x","--expression")
parser.add_argument("-n","--name")
parser.add_argument("-f", "--file")
args = parser.parse_args()
has_file = args.file is not None
has_txn = None not in frozenset([args.time, args.expression, args.name])
assert (has_file ^ has_txn), "File or time, expression and name must be provided"
You might find the following approach useful.
import argparse, sys
print(sys.argv)
if len(sys.argv) == 2:
sys.argv += ['-t', 'time', '-x', 'expression', '-n', 'name']
else:
sys.argv.append('FILE')
parser = argparse.ArgumentParser()
group = parser.add_argument_group('Required Group', 'All 3 are required else provide "file" argument.')
group.add_argument("-t","--time", required=True)
group.add_argument("-x","--expression", required=True)
group.add_argument("-n","--name", required=True)
parser.add_argument("file", help='file name')
print(parser.parse_args())
Here is some example output..
$ ./t.py -t 2 -x "a + b" -n George
['./t.py', '-t', '2', '-x', 'a + b', '-n', 'George']
Namespace(expression='a + b', file='FILE', name='George', time='2')
$ ./t.py FILE
['./t.py', 'FILE']
Namespace(expression='expression', file='FILE', name='name', time='time')
$ ./t.py -h
usage: t.py [-h] -t TIME -x EXPRESSION -n NAME file
positional arguments:
file file name
optional arguments:
-h, --help show this help message and exit
Required Group:
All 3 are required else provide "file" argument.
-t TIME, --time TIME
-x EXPRESSION, --expression EXPRESSION
-n NAME, --name NAME