I have this code:
parser = argparse.ArgumentParser()
parser.add_argument('-L', '--list', action='store_true', help='list options')
args = parser.parse_args()
I want this:
$./example.py --list
or
$./example.py -L
but not:
$./example -L --list # or --list -L
Is there an elegant way to avoid that both flags being used at the same time?
You can form a mutually exclusive group. That isn't ideal, since you'll end up repeating arguments to each, but that can be wrapped up into a helper function if need be.
parser = argparse.ArgumentParser()
arg_group = parser.add_mutually_exclusive_group()
arg_group.add_argument('-L', action='store_true', help='list options')
arg_group.add_argument('--list', action='store_true', help='list options')
args = parser.parse_args()
If you wanted to pretty this up a bit (to avoid repeating yourself), you could do something like:
class MutexArgParser(argparse.ArgumentParser):
def add_mutex_arguments(self, flags, *args, **kwargs):
arg_group = self.add_mutually_exclusive_group()
for flag in flags:
arg_group.add_argument(flag, *args, **kwargs)
parser = MutexArgParser()
parser.add_mutex_arguments(['-L', '--list'], action='store_true', help='list options')
args = parser.parse_args()
Related
I've got a parser with some sub-parsers. I setup a global argument to be used on all subparser. Here's the relevant snippet
parser = argparse.ArgumentParser(prog="my_prog", add_help=False)
parser.add_argument('-d', '--debug', action='store_true', help='debug flag')
subparsers = parser.add_subparsers(dest="subparser_name", help='some help notes')
parser_cmd1 = subparsers.add_parser('cmd1', parents=[parser])
parser_cmd1.add_argument('-f', '-foo', type=str, action=foo, required=False, help='foo command')
parser_cmd2 = subparsers.add_parser('cmd2', parents=[parser])
parser_cmd2.add_argument('-b', '-bar', type=str, action=bar, required=False, help='bar command')
args = parser.parse_args()
parser = args.subparser_name
print(args)
if args.debug:
logging.basicConfig(level=logging.INFO)
if parser == 'cmd1':
if args.foo:
//do foo stuff
if parser == 'cmd2':
if args.bar:
//do bar stuff
So you can a command like such my_prog.py cmd1 -d -f inp_str. Here's the problem: subparser_name is None. The output of print(args) looks kind of like this
Namespace(debug=True, foo="inp_str", subparser_name=None)
Before I added the global debug argument, subparser_name would be the name of the command I ran, i.e. 'cmd1' or 'cmd2'. Now, it's 'None'. Even with the parents=[parser] addition in the subparser creation. How can I fix this? How do I know which command was called?
Split out the common args to a separate ArgumentParser, which is then used as parent for the sub parsers. Also your foo and bar options were specified using -foo and -bar whi should be --foo and --bar. Also you didn't have default values for these so e.g. when -f/--foo wasn't specified args.foo correctly didn't exist.
This works better:
import argparse
common_args = argparse.ArgumentParser(prog="my_prog", add_help=False)
common_args.add_argument('-d', '--debug', action='store_true', help='debug flag')
parser = argparse.ArgumentParser(prog="my_prog", add_help=True)
subparsers = parser.add_subparsers(dest="subparser_name", help='some help notes')
parser_cmd1 = subparsers.add_parser('cmd1', parents=[common_args])
parser_cmd1.add_argument('-f', '--foo', type=str, default='', required=False, help='foo command')
parser_cmd2 = subparsers.add_parser('cmd2', parents=[common_args])
parser_cmd2.add_argument('-b', '--bar', type=str, default='', required=False, help='bar command')
args = parser.parse_args()
parser = args.subparser_name
print(args)
if args.debug:
logging.basicConfig(level=logging.INFO)
if parser == 'cmd1':
if args.foo:
#//do foo stuff
print( f"foo {args.foo}" )
if parser == 'cmd2':
if args.bar:
#//do bar stuff
print( f"bar {args.bar}" )
run with:
args.py cmd1 -f asd
output:
Namespace(subparser_name='cmd1', debug=False, foo='asd')
foo asd
Update:
If you want to be able to use e.g. args.py -d cmd1 then on the creation of parser, specify parents=[common_args]
parser = argparse.ArgumentParser(prog="my_prog", add_help=True, parents=[common_args])
Next time you ask a question ensure you only post code as a minimal reproducible example - i.e. that can be run without adding anything
The subparser's defaults have priority of any values set by the main parser - default or user input. The main does set the subparser_name to 'cmd1', but the subparser changes it back to the default None.
While not evident in your test case, defining debug at both levels has the same problem. The subparser's default overwrites anything set in the main.
In general, it is not a good idea to use the same dest in the main and subparsers. Flags can be the same, but the dest should be different - at least if you want to see anything set by the main.
And using the main parser as a parent to the sub, is just asking for confusion.
I am coding an argument parser for my script:
import argparse
parser = argparse.ArgumentParser(description='My parser.')
parser.add_argument('path',
type=str)
parser.add_argument('-a',
'--all',
action='store_true')
parser.add_argument('-t',
'--type',
type=str)
parser.add_argument('-d',
'--date',
type=str)
This is the logic I want to implement:
path: must be provided always.
--all: if it is provided, the --type and --date should not appear.
--type and --date: must be provided only if the --all flag is not introduced.
The command would look something like this:
python myscript.py mypath [-a] OR [-t mytype -d mydate]
How can I implement this logic?
You can do something like this:
from argparse import ArgumentParser
parser = ArgumentParser(description='My parser.')
parser.add_argument('path',
type=str)
parser.add_argument('-a',
'--all',
action='store_true')
parser.add_argument('-t',
'--type',
type=str)
parser.add_argument('-d',
'--date',
type=str)
args = parser.parse_args()
if args.all:
print('all argument flow')
else:
if not args.type or not args.date:
print('you need to put either all or specify both type and date')
else:
print(args.type, args.date)
print('and',args.path)
Subcommands like git commit and git status can be easily parsed with argparse using add_subparsers. How does one get nested settings input for choices selected from command line?
Let's say I want to play music with all custom settings:
play.py --path /path/to.file --duration 100 --filter equalizer --effect echo equalizer_settings 1 2 echo_settings 5
Here --filter equalizer and --effect echo are first level choices but I need to get settings for those as secondary arguments. Ex: echo_settings 5 and equalizer_settings 1 2.
Secondary settings could be more than one and preferably with named arguments.
Listed below is what I have so far...
import argparse
parser = argparse.ArgumentParser(description='Play music the way I want it.')
parser.add_argument('-p', '--path', type=str, required=True, help='File path')
parser.add_argument('-d', '--duration', type=int, default=50, help='Play duration')
parser.add_argument('-f', '--filter', choices=['none', 'equalizer'], default='none', help='Filter selection')
parser.add_argument('-e', '--effect', choices=['none', 'echo', 'surround'], default='none', help='Effect selection')
subparsers = parser.add_subparsers(help='Settings of optional parameters')
equalizer_parser = subparsers.add_parser("equalizer_settings")
equalizer_parser.add_argument('equalizer_min_range', type=int)
equalizer_parser.add_argument('equalizer_max_range', type=int)
echo_parser = subparsers.add_parser("echo_settings")
echo_parser.add_argument('echo_strength', type=int)
surround_parser = subparsers.add_parser("surround_settings")
surround_parser.add_argument('surround_strength', type=int)
args = parser.parse_args()
print(args)
Currently this errors in error: unrecognized arguments
You can only use one subparser at a time, i.e. one of those 3 settings. There are some advanced ways of using several subparsers in sequence, but I don't think we want to go there (see previous SO argparse questions).
But do you really need to use subparsers? Why not just another set of optionals (flagged) arguments, something like:
parser.add_argument("--equalizer_settings", nargs=2, type=int)
parser.add_argument("--echo_settings", type=int)
parser.add_argument("--surround_strength", type=int
You are just adding some parameters, not invoking some sort of action command.
I made a script that takes a few arguments to run. Originally it could be run automatically with the argument 'auto', but I'm trying to daemonize it so it will run the run the script with the specified arguments as a daemon. The problem is that python-daemon and argparse don't seem to get along when it comes to deciding who parses what.
parser = argparse.ArgumentParser(usage='pyfilter.py <file> <options> <actions>')
parser.add_argument('file', help='blacklist file containing IPs', type=str)
subparsers = parser.add_subparsers(help='help', dest='action')
parser_update = subparsers.add_parser('update', help='update help')
parser_update.add_argument('-c', '--check', help="check IPs for abuse reports", dest="check", type=str, nargs=1)
parser_blacklist = subparsers.add_parser('blacklist', help='create iptables rules for malicious IPs specified'
'in the provided file')
parser_clear = subparsers.add_parser('clear', help='clear iptables')
parser_auto = subparsers.add_parser('auto', help='automatically run update and blacklist on a loop')
parser_auto.add_argument('-i', '--interval', help='specify the loop interval', dest='interval', type=int, nargs=1)
parser_auto.add_argument('-c', '--check', help="check IPs for abuse reports", dest="check", type=str, nargs=1)
parser.add_argument('-p', '--port', help='specify the port to block', type=int)
parser.add_argument('-v', '--verbose', help='write output to screen', nargs=1)
args = parser.parse_args()
. . .
class pyfilterDaemon():
def __init__(self):
self.stdin_path = '/dev/null'
self.stdout_path = '/dev/tty'
self.stderr_path = '/dev/tty'
self.pidfile_path = '/tmp/pyfilter.pid'
self.pidfile_timeout = 5
def run(self):
while True:
update()
blacklist()
time.sleep(interval)
. . .
def main():
#handle()
d = pyfilterDaemon()
daemon_runner = runner.DaemonRunner(d)
daemon_runner.start()
Here's the commands I've tried to make this work:
root#tfof:~# ./pyfilter.py start
ERROR: File 'start' not found # argparse parsed 'start' accidentally
root#tfof:~# ./pyfilter.py /etc/blacklist.lst -v yes auto
usage: checkfilter.py stop|restart|start # now the argparse arguments are satisfied, but python-daemon is looking for its argument
root#tfof:~# ./pyfilter.py /etc/blacklist.lst -v yes auto start
usage: pyfilter.py <file> <options> <actions>
pyfilter.py: error: unrecognized arguments: start # argparse is trying to parse 'start'
Would it be possible to pass off the 'start' argument to python-daemon or something? Or if I could just get rid of the argparsing it'd be fine, but the 'file' argument is a must.
Argparse takes arguments from sys.argv per default (see here).
It is not surprising that the behaviour you see here is happening,
as you just call the parse_args function with the default arguments.
You can just pass whatever you want to parse to it, instead of sys.argv.
See this question for an example.
So consume whatever you need for python-deamon and then parse the remaining args with argparse.
For example:
example.py
parser = argparse.ArgumentParser(description="Will take arguments... or none")
parser.add_argument("-a", action="store_true")
parser.add_argument("-b", action="store_true")
parser.add_argument("-c", action="store_true")
parser.add_argument("-d", action="store_true")
args = parser.parse_args()
print args
I want example.py to set a to True, but only if either:
The -a flag is used
No flags are used
I tried messing around with
parser.set_defaults(a=True, b=False)
and
parser.add_argument("-a", action="store_true", default=True)
but they will set a to True even if I decide to use the b flag.
yes using the default values will set a to True even other arguments are specified. This will violate your second requirement, following is a simple fix with a naive condition checking.
parser = argparse.ArgumentParser(description="Will take arguments... or none")
parser.add_argument("-a", action="store_true")
parser.add_argument("-b", action="store_true")
parser.add_argument("-c", action="store_true")
parser.add_argument("-d", action="store_true")
args = parser.parse_args()
if not (args.b or args.c or args.d):
args.a=True
print args
Sounds like you want a 'radio button' effect - choosing just one of several alternatives. An alternative to a set of flags would be an argument with choices.
parser.add_argument('flag', choices=['a','b','c','d'], default='a', nargs='?')
You can check the result in args.flag, which will be one of 4 strings.
Obviously the positional argument couple replaced by a flag, e.g. -f.
I went for the following solution:
parser = argparse.ArgumentParser(description="Will take arguments... or none")
lettergroup = parser.add_mutually_exclusive_group()
lettergroup.add_argument("-a", action="store_const", dest="letter", const="a", default="a")
lettergroup.add_argument("-b", action="store_const", dest="letter", const="b")
lettergroup.add_argument("-c", action="store_const", dest="letter", const="c")
lettergroup.add_argument("-d", action="store_const", dest="letter", const="d")
args = parser.parse_args()
Now, the value is stored in args.letter. If no flag is called, args.letter will have the value a. If two flags are called at the same time, the parser will throw an error.
Just another way to solve this problem.