I am trying to use argparse to accept required command line options. I have defined a function like so
def get_args():
parser = argparse.ArgumentParser(description='Help Desk Calendar Tool')
parser.add_argument('-s', '--start', type=str, required=True, metavar='YYYY-MM-DD')
parser.add_argument('-e','--end', type=str, required=True, metavar='YYYY-MM-DD')
parser.add_argument('-m','--mode', type=str, required=True , metavar='add|del')
args = parser.parse_args()
start = args.start
end = args.end
mode = args.mode
return start,end,mode
What I am trying to do is for the option --mode I would like it to ONLY accept an parameter of either add or del. I could do this from an if statement but was wondering if argparse has a built in way of accomplishing this task. I looked at nargs but wasn't too clear if that's the path I need to go down
I think you are asking about choices:
parser.add_argument('-m','--mode', type=str, required=True, choices=['add', 'del'])
Demo:
$ python test.py -s 10 -e 20 -m invalid
usage: test.py [-h] -m {add,del}
test.py: error: argument -m/--mode: invalid choice: 'invalid' (choose from 'add', 'del')
Related
I use the following code to parse argument to my script (simplified version):
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-l", "--library", required=True)
ap.add_argument("--csv2fasta", required=False)
args = vars(ap.parse_args())
For every way the script can be run, the -l/--library flag should be required (required=True), but is there a way that it can use the setting required=False when you only use the --csv2fasta flag?
You have to write your test after parsing arguments, here's what I do for such cases:
def parse_args():
ap = argparse.ArgumentParser()
ap.add_argument("-l", "--library")
ap.add_argument("--csv2fasta")
args = ap.parse_args()
if not args.library and not args.csv2fasta:
ap.error("--library is required unless you provide --csv2fasta argument")
return args
$ python3 test-args.py
usage: test-args.py [-h] [-l LIBRARY] [--csv2fasta CSV2FASTA]
test-args.py: error: --library is required unless you provide --csv2fasta argument
$ python3 test-args.py --csv2fasta value
I have the following code:
# Get parsed arguments
args = argparse.ArgumentParser(description=Messages().Get(112))
# Get the arguments for sinit
args.add_argument('init', help=Messages().Get(100), action="store_true")
args.add_argument('--url', default=None, help=Messages().Get(101))
# Get the arguments for schema import
args.add_argument('schema-import', help=Messages().Get(104), action="store_true")
args.add_argument('--file', default=None, help=Messages().Get(104))
The --url argument should only be used with init. For example: script.py schema-import --url should not be accepted but script.py schema-import --file should.
How to set arguments as child arguments?
As mentioned there might be a way to do this with argparse, I'm not sure, but in any event I find it more transparent to explicitly handle argument dependencies in application logic. This should achieve what I think you want:
import argparse
import sys
args = argparse.ArgumentParser(description="please only use the '--url' argument if you also use the 'init' argument")
# Going to use aliases here it's more conventional. So user can use, eg,
# -i or --init for the first argument.
args.add_argument('-i', '--init', help='init help', action="store_true")
args.add_argument('-u', '--url', default=None, help='init help')
args.add_argument('-s', '--schema-import', help='schema-import help', action="store_true")
args.add_argument('-f', '--file', help='file help')
def main():
arguments = args.parse_args()
if arguments.url and not arguments.init:
# You can log an output or raise an exception if you want
# But most likely a print statment is most appropriate
# Since you are interacting with the CLI.
print("You can only use the URL option with init. Exiting")
sys.exit(0)
print("gaurd clauses passed. Here is my code...")
...
if __name__ == "__main__":
main()
Test results (my file called temp.py):
$python temp.py -u https://www.google.com
You can only use the URL option with init. Exiting
$
$python temp.py -i -u https://www.google.com
Gaurd clauses passed. Here is my code...
Why bother with doing all the logic when you can let argparse do all the work for you?
Simply use Sub-commands to define different "branches" of execution:
args = argparse.ArgumentParser(description=Messages().Get(112))
subparsers = args.add_subparsers()
parser_init = subparsers.add_parser('init', help=Messages().Get(100))
parser_init.add_argument('--url', default=None, help=Messages().Get(101))
parser_schema = subparsers.add_parser('schema-import', help=Messages().Get(104))
parser_schema.add_argument('--file', default=None, help=Messages().Get(104))
And this will give you what you want without any logic added:
>>> print(args.parse_args(['schema-import', '--url', "some.url"]))
usage: args.py [-h] {init,schema-import} ...
args.py: error: unrecognized arguments: --url some.url
>>> print(args.parse_args(['schema-import', '--file', "some.file"]))
Namespace(file='some.file')
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.
Basic intended usage:
my_framework create Project_title /path/to/project
OR
my_framework create Project_title (ie. use current working directory)
OR
my_framework update (ie. update my_framework rather than creating a new project)
I know I can make name optional by providing it with a default, but, in reality name is not optional provided the user has entered create as the first argument.
Best solution I've come up with is to use a default value for name and then, if the argument name equals its default value, throw an error. But if there's a way to make argparse do this work for me I'd rather learn to do it.
Writing two scripts, my_framework_create and my_framework_update doesn't appeal to me aesthetically.
#!/usr/bin/env python
import argparse
import os
import shutil
from subprocess import call
template_path = "/usr/local/klibs/template"
parser = argparse.ArgumentParser("MY_FRAMEWORK CLI", description='Creates a new MY_FRAMEWORK project or updates MY_FRAMEWORK')
parser.add_argument('action', choices=['create', 'update'], type=str, help='<help text>')
parser.add_argument('name', type=str, help='<help text>')
parser.add_argument('path', default=os.getcwd(), nargs="?", type=str, help='<help text>')
args = parser.parse_args()
if args.action == "create":
# do the create stuff
if args.action == "update":
# do the update stuff
The best way to do this is with a subparser
An example from the docs:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(title='subcommands',
... description='valid subcommands',
... help='additional help')
>>> subparsers.add_parser('foo')
>>> subparsers.add_parser('bar')
>>> parser.parse_args(['-h'])
usage: [-h] {foo,bar} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
valid subcommands
{foo,bar} additional help
In your case you would have create and update as separate subparsers.
Example:
def create(args):
# do the create stuff
print(args)
def update(args):
# do the update stuff
print(args)
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands',
description='valid subcommands',
help='additional help')
create_parser = subparsers.add_parser('create')
create_parser.add_argument('name', type=str)
create_parser.set_defaults(func=create)
update_parser = subparsers.add_parser('update')
update_parser.set_defaults(func=update)