Python argparse, run one or more sub-commands - python

I'm trying to write a program that is able to execute multiple sub-commands. The argparse module is very helpful, but I think it is lacking the ability to specify more than one sub-command. For example, if I have the following code:
parser = argparse.ArgumentParser(prog='My Prog')
sub_parsers = parser.add_subparsers()
subcommand_a = sub_parsers.add_parser('subcommand_a', help='a help')
subcommand_a.add_argument('req1', help='required argument 1 help')
subcommand_a.add_argument('--opt1', help='option 1 help')
subcommand_a.add_argument('--opt2', nargs='+', help='option 2 help')
subcommand_b = sub_parsers.add_parser('subcommand_b', help='b help')
subcommand_b.add_argument('req1', help='required argument 1 help')
subcommand_b.add_argument('--opt1', help='option 1 help')
subcommand_b.add_argument('--opt2', help='option 2 help')
subcommand_b.add_argument('--opt3', nargs='+', help='option 3 help')
parser.parse_args()
I cannot specify both subcommand_a and subcommand_b on the command line. I can only do one of them at a time. I'd imagine that this would require a custom action or possibly even subclassing argparse, but I'm not sure where to start. I'd like to be able to call this program like the following:
./prog.py subcommand_a FOO --opt1=bar --opt2 1 2 3 -- subcommand_b BAR --opt1='foo' --opt3 a b c --
Any ideas?

Your problem is essentially the same as the one asked a few months back
Multiple invocation of the same subcommand in a single command line
That one wanted to call the same subcommand several times, but the issue the same - how to handle more than one subparsers argument.
The solutions there are either to split the command line before passing it in pieces that are parsed separately, or collect the 'unused' pieces from one parsing for using in a second or third.
Here's tweak to your code that returns 2 commands:
import argparse
parser = argparse.ArgumentParser(prog='My Prog')
sub_parsers = parser.add_subparsers(dest='cmd')
subcommand_a = sub_parsers.add_parser('subcommand_a', help='a help')
subcommand_a.add_argument('req1', help='required argument 1 help')
subcommand_a.add_argument('--opt1', help='option 1 help')
subcommand_a.add_argument('--opt2', nargs=3, help='option 2 help')
subcommand_b = sub_parsers.add_parser('subcommand_b', help='b help')
subcommand_b.add_argument('req1', help='required argument 1 help')
subcommand_b.add_argument('--opt1', help='option 1 help')
subcommand_b.add_argument('--opt2', help='option 2 help')
subcommand_b.add_argument('--opt3', nargs=3, help='option 3 help')
argv = "subcommand_a FOO --opt1=bar --opt2 1 2 3 subcommand_b BAR --opt1=foo --opt3 a b c"
rest = argv.split()
while rest:
[args, rest] = parser.parse_known_args(rest)
print args
print rest
which prints:
Namespace(cmd='subcommand_a', opt1='foo', opt2=['1', '2', '3'], req1='FOO')
['subcommand_b', 'BAR', '--opt3', 'a', 'b', 'c']
Namespace(cmd='subcommand_b', opt1=None, opt2=None, opt3=['a', 'b', 'c'], req1='BAR')
[]
I removed the '--' (see the end of my other answer)
I changed opt2 and opt3 to take 3 arguments, not the variable +. With + it can't tell where the list for opt2 ends and the next command begins.
It is also important that the different commands take different optionals (flags). Note that the first opt1 ends up with the second value, 'foo', leaving none for the 2nd command. See the other thread for a discussion of the issues and ways around that.

I've done some testing and changing the subcommand to other string worked.
if you use:
parser = argparse.ArgumentParser(prog='My Prog')
sub_parsers = parser.add_subparsers()
subcommand_a = sub_parsers.add_parser('subcommand_a', help='a help')
subcommand_a.add_argument('req1', help='required argument 1 help')
subcommand_a.add_argument('--opt1', help='option 1 help')
subcommand_a.add_argument('--opt2', nargs='+' help='option 2 help')
subcommand_b = sub_parsers.add_parser('subcommand_b', help='b help')
subcommand_b.add_argument('req1', help='required argument 1 help')
subcommand_b.add_argument('--opt3', help='option 1 help')
subcommand_b.add_argument('--opt4', help='option 2 help')
subcommand_b.add_argument('--opt5', nargs='+', help='option 3 help')
parser.parse_args()
it works.
I've done some research and not found a subcommand with a equal string. A overview of the documentation don't say nothing either. I presume it's a limitation ( by choice ) or a bug.
Maybe you can check it in the Source Code Hosting

Related

How to parse arguments in the style of git or conda in python

At least a few current command line tools follow the general pattern of:
$> program-name action [zero or more action specific options & arguments
For example
$> git checkout -b new-branch
$> git diff HEAD HEAD~2
... and so on ...
The idea is that we have different actions/commands (checkcout, diff, commit etc.) and the arguments/options for each command can be different.
How do you implement this pattern in python (ideally using argparse)?
(FTFM!) This is directly supported by argparse
>>> # create the top-level parser
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--foo', action='store_true', help='foo help')
>>> subparsers = parser.add_subparsers(help='sub-command help')
>>>
>>> # create the parser for the "a" command
>>> parser_a = subparsers.add_parser('a', help='a help')
>>> parser_a.add_argument('bar', type=int, help='bar help')
>>>
>>> # create the parser for the "b" command
>>> parser_b = subparsers.add_parser('b', help='b help')
>>> parser_b.add_argument('--baz', choices='XYZ', help='baz help')
>>>
>>> # parse some argument lists
>>> parser.parse_args(['a', '12'])
Namespace(bar=12, foo=False)
>>> parser.parse_args(['--foo', 'b', '--baz', 'Z'])
Namespace(baz='Z', foo=True)

argparse - how to pass argument from args into function?

import argparse
from queries import most_common_cities
parser = argparse.ArgumentParser(description='A script that does operations with database data and returns values')
parser.add_argument('-c', '--most_common_cities',
nargs=1,
type=positive_int,
help='Specify how many common cities.')
args = parser.parse_args()
if args.most_common_cities:
result = most_common_cities(n) # "n" should be an arg passed by user
print(result)
How could I pass arguments from CLI to my function arg?
When someone use command:
python argp.py --most_common_cities 5
It should return 5 most common cities.
Remove nargs=1, then args.most_common_cities will be the actual value passed in.
nargs=1 wraps it in a list.
parser.add_argument('-c', '--most_common_cities',
type=int,
help='Specify how many common cities.')
args = parser.parse_args(['-c', '5'])
n = args.most_common_cities
print(n)
print(type(n))
# 5
# <class 'int'>
I started your script with following command:
python3 test.py --most_common_cities 5
You can access the arguments with:
import argparse
parser = argparse.ArgumentParser(description='A script that does operations with database data and returns values')
parser.add_argument('-c', '--most_common_cities',
nargs=1,
type=int,
help='Specify how many common cities.')
args = parser.parse_args()
arguments = vars(parser.parse_args())
print(arguments) #{'most_common_cities': [5]}
#then you can access the value with:
arguments['most_common_cities']

Python argparse; if argument 1 not present, how to continue and set required next arguments

I have 3 arguments in my python script. Two are required for running the script and argument 1 shows only a list of options.
I want that if argument 1 is set, that argument 2 and 3 are not required anymore, and if argument 1 is not used, that argument 2 and 3 are required.
The arguments are as follows:
parser = argparse.ArgumentParser()
parser.add_argument("--1", help="list of options", action="store_true", default=False)
parser.add_argument("--2", help="level", required=True)
parser.add_argument("--3", help="folder", required=True)
args = parser.parse_args()
I tried it with the following command:
parser = argparse.ArgumentParser()
parser.add_argument("--1", help="list of options", action="store_true", default=False)
parser.add_argument("--2", help="level)
parser.add_argument("--3", help="folder")
args = parser.parse_args()
if args.1:
print('list of options')
sys.exit()
if not args.1:
parser = argparse.ArgumentParser()
parser.add_argument("--1", help="list of options", action="store_true", default=False)
parser.add_argument("--2", help="level", required=True)
parser.add_argument("--3", help="folder", required=True)
But the problem than is that if the user only set option 2 (or only option 3), that the script continues, instead of having both options 2 and 3 required.
Is it possible in Python to first test for option 1 and, if this option is not set, continue to test for option 2 and 3, and set these two as required?

Argparse arguments from file and string CLI arguments both

I want to launch my script like this:
python3 main.py #params.conf 1 2
where params.conf is a file and 1, 2 are string arguments.
I know how to parse file alone:
argparser = ArgumentParser()
argparser.add_argument('arg1', help='heeelp')
...
args = argparser.parse_args()
But how to parse following arguments?
An argument prefixed with # is treated as if its contents were in the command line directly, one argument per line. So if the contents of params.confis
2
3
And you define a parser like
import argparse
p = argparse.ArgumentParser(fromfile_prefix_chars='#')
p.add_argument("a")
p.add_argument("b")
p.add_argument("c")
p.add_argument("d")
args = p.parse_args()
and you call your script as
script.py 1 #params.conf 4
then your arguments a through d will be set to 1 through 4, respectively.
You just add more argparser.add_argument calls.
Like this:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('arg1', type=str)
parser.add_argument('arg2', type=str)
parser.add_argument('arg3', type=str)
args = parser.parse_args()
print(args) # arguments are parsed

python argparse different parameters with different number of arguments

How do I use a different number of parameters for each option?
ex) a.py
parser.add_argument('--opt', type=str,choices=['a', 'b', 'c'],help='blah~~~')
choice : a / parameter : 1
ex)
$ python a.py --opt a param
choice : c / parameter :2
ex)
$ python a.py --opt b param1 param2
You need to add sub-commands, ArgumentParser.add_subparsers() method will help you
Check this example
>>> # create the top-level parser
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--foo', action='store_true', help='foo help')
>>> subparsers = parser.add_subparsers(help='sub-command help')
>>>
>>> # create the parser for the "a" command
>>> parser_a = subparsers.add_parser('a', help='a help')
>>> parser_a.add_argument('bar', type=int, help='bar help')
>>>
>>> # create the parser for the "b" command
>>> parser_b = subparsers.add_parser('b', help='b help')
>>> parser_b.add_argument('--baz', choices='XYZ', help='baz help')
>>>
>>> # parse some argument lists
>>> parser.parse_args(['a', '12'])
Namespace(bar=12, foo=False)
>>> parser.parse_args(['--foo', 'b', '--baz', 'Z'])
Namespace(baz='Z', foo=True)
You may add more parameters, one for each a, b, and c and also optional arguments for your params. By using the named parameter nargs='?' you can specify that they are optional and with the default="some value" you ensure it rises no errors. Finally, based on the selected option, a,b or c you will be able to capture the ones you need.
Here's a short usage example:
parser.add_argument('x1', type=float, nargs='?', default=0, help='Circ. 1 X-coord')
parser.add_argument('y1', type=float, nargs='?', default=0, help='Circ. 1 Y-coord')
parser.add_argument('r1', type=float, nargs='?', default=70, help='Circ. 1 radius')
parser.add_argument('x2', type=float, nargs='?', default=-6.57, help='Circ. 2 X-coord')
parser.add_argument('y2', type=float, nargs='?', default=7, help='Circ. 2 Y-coord')
parser.add_argument('r2', type=float, nargs='?', default=70, help='Circ. 2 radius')
args = parser.parse_args()
circCoverage(args.x1, args.y1, args.r1, args.x2, args.y2, args.r2)
here, if no values are selected, the default ones are used. You can play with this to get what you want.
Cheers

Categories