Common positional arguments in different subparsers - python

I am trying to make a parser using argparse than can parse the following commands:
python prog.py update <DOMAIN> <ENVIRONMENT>
python prog.py pull <DOMAIN> <ENVIRONMENT>
python prog.py release <DOMAIN> <ENVIRONMENT>
As you can see, both update, pull and release take the same arguments <DOMAIN> and <ENVIRONMENT>.
All three of them are subparsers of the main parser.
I wrote the following:
import argparse
# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG', add_help=False)
parser.add_argument('domain', type=str, help='domain help')
parser.add_argument('environment', type=str, help='environment help')
#subparsers
subparsers = parser.add_subparsers(help='sub-command help', parents=[parser])
parser_pull = subparsers.add_parser('pull', help='pull help')
parser_update = subparsers.add_parser('update', help='update help')
print parser_pull.parse_args(['pull', 'WEBAPPS', 'DEV'])
print parser.parse_args(['update', 'WEBAPPS', 'DEV'])
but it seems that domain and environment are expected BEFORE the subcommands update, pull and release, so it throws an error.
How can I make it required to accept those arguments after the subcommands, without duplicating code inside each subcommand ?
For the record, I use Python 2.7.

Go ahead and duplicate the code. A little cut and paste is not that much work.
Positional arguments have to be given in a certain order. And .add_subparsers creates one of those positionals (one that expects values like 'pull','update'. So the order of the subparse command, positionals defined for the main parser, and positionals for the subparsers matters.
There is a parents mechanism, which can save some typing. But I hesitate to recommend it because it can cause problems (previous SO questions demonstrate this). Simply biting the bullet and entering the positional arguments where they are expected is the surest approach.
Don't forget that you can create subparsers in a loop or with helper functions - saving one kind of typing for another.
For example, after creating the subparsers:
for p in parser_pull, parser_update:
p.add_argument('domain', type=str, help='domain help')
p.add_argument('environment', type=str, help='environment help')

Related

python argparse separate 2 options of comamnds

I've read the documentation but still can't figure out how to achieve the following behavior:
1.likes. Give a specified number of likes to users in a tinder (in the future, this may contain more options, such as "gender", "frequency", "age", etc.)
2. Write a given text to a given number of people on tinder (in the future, there may also be more options).
There is my code:
parser = argparse.ArgumentParser(description='Badoo liker', epilog='Enjoy the program! :)')
# I also tried "add_mutually_exclusive_group" instead of "add_argument_group"
chat_args = parser.add_argument_group(title='chat_args')
chat_args.add_argument('-c', '--chat', help='chat help')
chat_args.add_argument('-t', '--text', help='text help')
chat_args.add_argument('-n', '--number', help='n help')
like_args = parser.add_argument_group(title='like_args')
like_args.add_argument('-l', '--like', help='like help')
like_args.add_argument('-n', '--number', help='n help')
args = parser.parse_args()
Usage:
$script.py chat --text 'Hello world' -n 20 # Var 1
$script.py liking -n 20 # Var 2
Obviously, I'm waiting for arguments either for a chat or either for liking
P.S. I'm getting an error because of -n common argument, but even if comment it it will not working as expecting
It's a little hard to tell what you understand and what has worked or not for you.
This is more of a comment, but long enough that I'll make it answer.
Are those usage lines samples of what you use when calling this script? What error(s) do you get?
Have you tried scipt.py -h to see the help?
I don't see a positional argument that would accept a string like "chat" or "liking". I suspect you want to use the subcommands mechanism, but get the basic argparse working.
I often suggest including a print(args) line to get a clear idea of what the parser has done. Though obviously you won't see that while argparse is raising errors.
Postpone the use of groups until you get the basics down. argument_group just groups arguments in the help display. mutually_exclusive_group is a parsing tool, that complains if you try to use more than one item in the group.
Eventually, I did it, the next code is (apparently) meets my question
# "metavar=''" -just hide redundant double --key KEY word
parser = argparse.ArgumentParser(description='Badoo liker', epilog='Enjoy the program! :)')
subparsers = parser.add_subparsers(help='')
parser_like = subparsers.add_parser('like', help='like help')
parser_like.add_argument('-n', '--number', metavar='', help='Set count of likes', default=49)
parser_like.add_argument('-f', '--frequency', metavar='', help='Set chance to like/dislike', default=70)
parser_chat = subparsers.add_parser('chat', help='chat help')
parser_chat.add_argument('-n', '--number', metavar='', help='number help', required=True)
parser_chat.add_argument('-t', '--text', metavar='', help='text help', required=True)
args = parser.parse_args()

Python argparse module usage

I have a program which is called like this:
program.py add|remove|show
The problem here is that depending on the add/remove/show command it takes a variable number of arguments, just like this:
program.py add "a string" "another string"
program.py remove "a string"
program.py show
So, 'add' command would take 2 string arguments, while 'remove' command would take 1 only argument and 'show' command wouldn't take any argument.
I know how to make a basic argument parser using the module argparse, but I don't have much experience with it, so I started from this:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=["add", "remove", "show"])
But I don't know how to continue and how to implement this functionality depending on the command. Thanks in advance.
You're looking for argparse's subparsers...
parser = argparse.ArgumentParser(prog='PROG')
subparsers = parser.add_subparsers(help='sub-command help')
# create the parser for the "add" command
parser_add = subparsers.add_parser('add', help='add help')
# [example] add an argument to a specific subparser
parser_add.add_argument('bar', type=int, help='bar help')
# create the parser for the "remove" command
parser_remove = subparsers.add_parser('remove', help='remove help')
# create the parser for the "show" command
parser_show = subparsers.add_parser('show', help='show help')
This example code is stolen with very little modification from the language reference documentation.

Is there a way to add a parameter that supress the need of other parameters using argparse on Python?

I am using Argparse on python to do a script on command line. I have this for my script:
parser = argparse.ArgumentParser(prog = 'manageAdam')
parser.add_argument("-s", action='store_true', default=False, help='Shows configuration file')
parser.add_argument("d", type=str, help="device")
parser.add_argument("o", type=str, help="operation")
parser.add_argument("-v", "--value", type=int, nargs='*', help="value or list to send in the operation")
I am looking that if I call manageAdam -s it would work and don't ask for the positional arguments, something like the -h, which can be called without any other positional argument that is defined. Is it possible?
There is no built-in way to do this. You might be able to achieve something by writing some custom Action classes that keep track on the parser about their state, but I believe it will become quite messy and buggy.
I believe the best bet is to simply improve your UI. The -s is not an option. It's a separate command that completely alters how your script executes. In such cases you should use the subparsers functionality which allows to introduce sub-commands. This is a better interface then the one you thought, and is used by a lot of other tools (e.g. Git/mercurial).
In this case you'd have a config command to handle the configuration and a run (or how you want to call it) command to perform the operations on the device:
subparsers = parser.add_subparsers(dest='command')
parser_config = subparsers.add_parser('config', help='Configuration')
parser_run = subparsers.add_parser('run', help='Execute operation on device')
parser_run.add_argument('d', type=str, ...)
parser_run.add_argument('o', type=str, ...)
parser_run.add_argument('-v', type=int, nargs='*', ...)
# later:
args = parser.parse_args()
if args.command == 'config':
print('Configuration')
else:
print('Run operation')
Used from the command line as:
$ manageAdam config
# or
$ manageAdam run <device> <operation> <values...>
No, there are no such way.
You can make all arguments optional and set default value to None then perform check that all of them aren't None otherwise raise argparse.ArgumentError, if manageAdam provided skip check for other arguments.

Python argparse - creating subcommands with no labels

I want to create a command parser mycommand, using argparse, with two subcommands read and write: read should have just one argument which is some path, and write should have two arguments one of which is a path and the other a value. It should be possible to execute the command in the following way:
mycommand read <path>
mycommand write <path> <value>
without using labels for the <path>, <value> arguments, i.e. without requiring --path. How can I do this?
This is pretty straight forward following the docs:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
read = subparsers.add_parser('read')
read.add_argument('path')
write = subparsers.add_parser('write')
write.add_argument('path')
write.add_argument('value')
print(parser.parse_args(['read', 'foo']))
print(parser.parse_args(['write', 'foo', 'bar']))
Note that this doesn't tell you what parser parsed the arguments. If you want that, you can simply add a dest to the add_subparsers command:
subparsers = parser.add_subparsers(dest='subparser')
Finally, you can add a default attribute for each subparser that you can use to perform the actions specific to that subparser. This is spelled out in the docs too, but for completeness, in our example, it might look something like this1:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser')
def handle_read(args):
print('Handling read')
print(args)
read = subparsers.add_parser('read')
read.add_argument('path')
read.set_defaults(handler=handle_read)
def handle_write(args):
print('Handling write')
print(args)
write = subparsers.add_parser('write')
write.add_argument('path')
write.add_argument('value')
write.set_defaults(handler=handle_write)
args = parser.parse_args()
args.handler(args)
1I added the dest to subparsers in this example too for illustrative purposes -- Using argparse with handler functions probably makes that attribute on args obsolete.

In python, how to get subparsers to read in parent parser's argument?

Here is an example code:
import argparse
parser=argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparser=parser.add_subparser()
a=subparser.add_parser('run')
a.add_argument('required_sub_arg')
a.add_argument('arg_a')
b=subparser.add_parser('b')
parser.parse_args()
I want it to read in -main_arg if I enter program run required_sub_arg -main_arg -arg_a
Right now, it doesn't recognize -main_arg as a valid argument.
PSA to recent readers
As this question still has visits in 2018, before doing anything this complex with argparse, please consider using docopt or click instead. It will improve both your sanity and that of anyone who might read or modify your code. Thank you.
Original answer
As is, you have a few issues.
First, parser.parse_args is a method that returns a namespace of parser's arguments, so you should do something like
args = parser.parse_args()
Then args.main_args to get-main_arg from a call like
program -main_arg run required_sub_arg -arg_a
Your issue with main_arg is that you have created a argument to parser named main_arg, and you make a call like
program run required_sub_arg -main_arg -arg_a
that refers to an argument to a named main_arg. Since a doesn't have such an argument, it is invalid.
In order to refer to a parser's argument from one of its subparser, you have to make said subparser inherit the arguments of its parent. This is done with
a=parser.add_subparser('run', parents=[parser])
You have mistaken subparser for child parser. See http://docs.python.org/dev/py3k/library/argparse.html and https://code.google.com/p/argparse/issues/detail?id=54 for more informations.
For anyone else using argparse that arrives here looking for a way to display "common" sub-parser arguments in the "main" help screen, here's one approach:
import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('--shared', action='store_true', help='some shared arg')
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--parent', action='store_true', help='parent only arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
run.add_argument('--fast', action='store_true', help='run only arg')
parser.epilog = "--- Arguments common to all sub-parsers ---" \
+ common.format_help().replace(common.format_usage(), '')
args = parser.parse_args()
Main help:
$ program.py -h
usage: program.py [-h] {run} ...
positional arguments:
{run}
optional arguments:
-h, --help show this help message and exit
--parent parent only arg
--- Arguments common to all sub-parsers ---
optional arguments:
--shared some shared arg
run sub-parser help:
$ program.py run -h
usage: program.py run [-h] [--shared]
optional arguments:
-h, --help show this help message and exit
--shared some shared arg
--fast run only arg
To address the actual question, since the accepted answer doesn't run for me, here's some additional information on why it doesn't seem possible to truly share argparse arguments with the same name across both parent and child/sub-parser parsers.
First, the problem with the following code:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[parser])
args = parser.parse_args()
Is that it leads to the following error, because both parent parser and sub-parser run define the -h/--help argument (by default).
Argparse.ArgumentError: argument -h/--help: conflicting option strings: -h, --help
While this error can be avoided by suppressing the -h/--help option (with add_help=False) on either the parent or the child, it's nice to have the help option at both levels.
Another potential way to avoid conflicting help options is to move common arguments to a shared parser, common:
import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('-main_arg', action='store_true')
parser = argparse.ArgumentParser(parents=[common])
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
args = parser.parse_args()
print(args)
While this appears to work on the surface, in practice, it doesn't work as intended:
$ program.py run # OK
Namespace(main_arg=False)
$ program.py run -main_arg # OK
Namespace(main_arg=True)
$ program.py -main_arg run # BAD: expected main_arg to be True
Namespace(main_arg=False)
The behavior observed when parsing program.py -main_arg run illustrates a key relationship: a parent argparser and its sub-parsers are independent parsers, where the parent parses all arguments up to the sub-parser "command" positional argument, and then the selected sub-parser parses the remaining arguments in the same Namespace as the parent with no regard for attributes that may have been set by the parent.

Categories