Argparse: parse multiple subcommands - python

Did some research, but couldn't find any working solution. I'm trying to parse the following command line, where 'test' and 'train' are two independent subcommands each having distinct arguments:
./foo.py train -a 1 -b 2
./foo.py test -a 3 -c 4
./foo.py train -a 1 -b 2 test -a 3 -c 4
I've been trying using two subparsers ('test','train') but it seems like only one can be parsed at the time. Also it would be great to have those subparsers parents of the main parser such that, e.g. command '-a' doesn't have to be added both to the subparsers 'train' and 'test'
Any solution?

This has been asked before, though I'm not sure the best way of finding those questions.
The whole subparser mechanism is designed for one such command. There are several things to note:
add_subparsers creates a positional argument; unlike optionals a `positional acts only once.
'add_subparsers' raises an error if you invoke it several times
the parsing is built around only one such call
One work around that we've proposed in the past is 'nested' or 'recursive' subparers. In other words train is setup so it too takes a subparser. But there's the complication as to whether subparsers are required or not.
Or you can detect and call multiple parsers, bypassing the subparser mechanism.
From the sidebar
Multiple invocation of the same subcommand in a single command line
and
Parse multiple subcommands in python simultaneously or other way to group parsed arguments

Related

Python - argparse - (Singleton) Argument w/ optional parameter [duplicate]

I'm trying to implement the following argument dependency using the argparse module:
./prog [-h | [-v schema] file]
meaning the user must pass either -h or a file, if a file is passed the user can optionally pass -v schema.
That's what I have now but that doesn't seem to be working:
import argparse
parser = argparse.ArgumentParser()
mtx = parser.add_mutually_exclusive_group()
mtx.add_argument('-h', ...)
grp = mtx.add_argument_group()
grp.add_argument('-v', ...)
grp.add_argument('file', ...)
args = parser.parse_args()
It looks like you can't add an arg group to a mutex group or am I missing something?
If -h means the default help, then this is all you need (this help is already exclusive)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file')
parser.add_argument('-s','--schema')
parser.parse_args('-h'.split()) # parser.print_help()
producing
usage: stack23951543.py [-h] [-s SCHEMA] file
...
If by -h you mean some other action, lets rename it -x. This would come close to what you describe
parser = argparse.ArgumentParser()
parser.add_argument('-s','--schema', default='meaningful default value')
mxg = parser.add_mutually_exclusive_group(required=True)
mxg.add_argument('-x','--xxx', action='store_true')
mxg.add_argument('file', nargs='?')
parser.parse_args('-h'.split())
usage is:
usage: stack23951543.py [-h] [-s SCHEMA] (-x | file)
Now -x or file is required (but not both). -s is optional in either case, but with a meaningful default, it doesn't matter if it is omitted. And if -x is given, you can just ignore the -s value.
If necessary you could test args after parsing, to confirm that if args.file is not None, then args.schema can't be either.
Earlier I wrote (maybe over thinking the question):
An argument_group cannot be added to a mutually_exclusive_group. The two kinds of groups have different purposes and functions. There are previous SO discussions of this (see 'related'), as well as a couple of relevant Python bug issues. If you want tests that go beyond a simple mutually exclusive group, you probably should do your own testing after parse_args. That may also require your own usage line.
An argument_group is just a means of grouping and labeling arguments in the help section.
A mutually_exclusive_group affects the usage formatting (if it can), and also runs tests during parse_args. The use of 'group' for both implies that they are more connected than they really are.
http://bugs.python.org/issue11588 asks for nested groups, and the ability to test for 'inclusivity' as well. I tried to make the case that 'groups' aren't general enough to express all the kinds of testing that users want. But it's one thing to generalize the testing mechanism, and quite another to come up with an intuitive API. Questions like this suggest that argparse does need some sort of 'nested group' syntax.

How to create Argument Parser

I am new to argparser in python . I am trying to create argparser for file which contains two functions download and upload file on/from box. It will do only one functionality at once according according to that i am trying to create parser for that file as follows but it's not working for me:
parser = argparse.ArgumentParser(description='Download or Upload file on box.')
parser.add_argument('-df', '--download', required=True,
help='download file box')
parser.add_argument('-uf', '--upload', nargs='+', required=True,
help='upload file of box')
parser.add_argument('-fp', '--filepath', required=True,
help='file path to upload(which file to upload) or download(where to download file)')
parser.add_argument('-fn', '--filename', required=True,
help='what should be the name of file on box')
parser.add_argument('-fi', '--fileid', required=True,
help='file id of file to download from box')
args = vars(parser.parse_args())
NOTE :- every time only -df or -uf options will be there, -fp is mandatory for both and if it is -df then -fi is only option and it is mandatory and if -uf then -fn is only option and it's mandatory.
How do I achieve this, following are example how will i pass argument to file
pyhton abc.py -df -fp 'Download/boxfile/xyz.txt' -fi 123
python abc.py -uf -fp 'Download/boxfile/xyz.txt' -fn 'qwe.txt'
As written all 5 of the arguments are required - you made that explicit. If that's what you really want, all the rest of the question is irrelevant. You'll have to provide all 5 regardless.
But the comments indicate that you want to use either -df or -uf, but probably not both (though that bit's unclear).
While there is a mutually_exclusive_mechanism in argparse, there isn't a inclusive equivalent - something that says if -f is present, then -g must also be given.
But subparsers mechanism can be used that way. You could define an download subparser, with a required -fi argument. And an upload with its own argument.
Another option is to set -df to take 2 arguments (nargs=2), both its box and its file.
If -df and -uf are mutually exclusive, why not use the same argument for the name of the file? That is, replace -fn and -fi with one file argument.
Another option is to make all (or most) of the arguments not-required, and check for the correct combinations after parsing. It's easier to implement complicated logic in your own code than to force argparse to do it for you.
e.g.
if args.download is not None: # not default
<check for `args.filename`> etc
It can also be a good idea to provide defaults for optional arguments. That way the code can run even if the user doesn't provide all items.
On a matter of style. Short option flags, with on - are usually a single character. -d, -u, etc. The ability to combine several into one string only works in that case, e.g. -df foo. In this case it probably doesn't matter since none of your arguments are store_true.
I'm not incredibly familiar with argparse, however from reading the documentation I don't believe there's a way to use mutually_exclusive_group and required to force this. It seems conditional statements or equivalent are necessary to confirm that valid parameter combinations were passed.
See: Python Argparse conditionally required arguments
I'd suggest that you get rid of most of these arguments, maybe all, and think about standard Unix ways of doing things.
Consider scp, which has a syntax of: scp source destination
For instance: scp Download/boxfile/xyz.txt qwe.txt
If you don't supply a destination, it infers that you want the file to be called the same thing, and land right here, so these two things are equivalent:
scp Download/boxfile/xyz.txt xyz.txt
scp Download/boxfile/xyz.txt
Of course, scp can talk to machines across the internet, so there is a format for that:
scp hostname:Download/boxfile/xyz.txt xyz.txt
And if you are uploading, you simple switch the order:
scp xyz.txt hostname:Download/boxfile/xyz.txt

How to have two independent groups [duplicate]

This question already has answers here:
Python argparse mutual exclusive group
(4 answers)
Closed 6 years ago.
My program has two functionalities. One is run without any arguments, and the other can have optional arguments. The groups can't interfere with each other.
import argparse
parser = argparse.ArgumentParser()
root_group = parser.add_mutually_exclusive_group()
group_export = root_group.add_argument_group()
group_export.add_argument('--export', action='store_true', help='Exports data from database')
group_export.add_argument('-l', action='append', help='Reduce output with league name')
group_export.add_argument('-d', action='append', help='Reduce output with date range')
group_run = root_group.add_argument_group()
group_run.add_argument('--run', action='store_true', help='Start gathering of data')
I want this to be allowed:
python file.py --export -l name1 -l name2 -d 1/1/2015
python file.py --export
python file.py --run
And this to be not allowed:
python file.py --run --export # Namespace(d=None, export=True, l=None, run=True)
python file.py --run -l name1 # Namespace(d=None, export=False, l=['name1'], run=True)
However, as on now neither of the disallowed operations rises an error, as indicated by the comments.
Argument groups don't nest inside a mutually exclusive group. Despite the names, the two kinds of groups have different purposes.
Argument groups group arguments in the help display. They do nothing during parsing.
Mutually exclusive groups test the occurrence of arguments, and try to display that in the usage line.
You could make --export and --run mutually exclusive. But it won't block the use of l or d with run. But you could just ignore those values. Or you could do your own tests after parsing, and complain that the point.
What would be a meaningful way of representing this constrain in the usage line? You may need to customize that.
Another possibility is to use subparsers. That might fit your case better. The 'export' parser would define the arguments that work with that. The 'run' would not accept any further arguments.
In one way or other this has been discussed in other argparse questions. The sidebar seems to have found some possible matches.

python - argparse multiple command-line how to know

I want to use the argparser module with multiple command-line and some of them should have no arguments.
Example:
parser.add_argument('-website', type=str, nargs='*')
parser.add_argument('-auth', type=str, nargs='*')
parser.add_argument('-dothis', action='store_true')
So I want to call in a command line following commands:
- python script.py -website www.website.com www.website2.com -dothis
In this case the -dothis command should only be use for the second website not for the first one, but it is used for both websites.
Another example:
-python script.py -website www.website1.com www.website2.com -auth username/password
In this case the second website has a authentication not the first one.
So what I want is:
- python script.py www.webstie1.com -dothis www.website2.com -auth u:p -dothis www.website3.com -auth u:p www.website4.com
or:
- python script.py -site www.website1.com -auth u:p -site www.website2.com -site www.website3.com -auth u2:p2
so how my script knows which auth is for which website?
How can I fix it?
Your examples are inconsistent. One has '-website' with 2 values, an other has '-site' repeated (append action?), and the third repeated positionals.
Your goal runs into several fundamental issues with argparse:
optionals are parsed in an order independent manner
arguments normally don't interact
usage does not have provision for displaying interactions
I can write a set of custom Action classes that perform the kind of interaction that you want. But are you really interested?
The basic parsing strategy of argparse is to handle the 'positionals' in order, but 'optionals' (flagged) arguments can occur in any order. So the resulting args namespace does not have information about the order of the commands strings. Old parsers like getopt and optparse aren't much better, since they focus on collecting the optionals, and returning the rest as a undifferentiate list.
An alterantive to custom Action classes is to parse sys.argv yourself. Just iterate through the strings. A string without '--' is a site; any '--' flags the follow apply to that site, etc.
A twist on your desired API is to define '--website' as an append that takes multiple arguments.
parser.add_argument('--website','-w','--site',dest='site',nargs='+',action='append',
help='website plus flags; may repeat')
possible commandlines and Namespaces:
--site www.webwsite.com dothig --site www.website2.com
NS(site=[['www.website.con','dothis'],['website2']])
--site site1 username/pass --site site2
NS(site=[['site1','username/pass'],['site2']])
So for each element in args.site, you can interpret the 1st item as the site name, a parse the rest to find the dothis flag and the auth name.

How to require one command line action argument among several possible but exclusive?

Using argparse, is there a simple way to specify arguments which are mutually exclusive so that the application asks for one of these arguments have to be provided but only one of them?
Example of fictive use-case:
> myapp.py foo --bar
"Foo(bar) - DONE"
> myapp.py read truc.txt
"Read: truc.txt - DONE"
>myapp.py foo read
Error: use "myapp.py foo [options]" or "myapp.py read [options]" (or something similar).
> myapp.py foo truc.txt
Error: "foo" action don't need additional info.
> myapp.py read --bar
Error: "read" action don't have a "--bar" option.
My goal is to have a "driver" application(1) that would internally apply one action depending on the first command line argument and have arguments depending on the action.
So far I see no obvious ways to do this with argparse without manually processing the arguments myself, but maybe I missed something Pythonic? (I'm not a Python3 expert...yet)
I call it "driver" because it might be implemented by calling another application, like gcc does with different compilers.
What you're trying to do is actually supported quite well in Python.
See Mutual Exclusion
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('foo', dest='foo', nargs=1)
group.add_argument('read', dest='read', nargs=1)
args = parser.parse_args()
return args

Categories

Resources