Argparse with OR logic on arguments - python

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)

Related

Python argparse prevent long and short flags at same time

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()

python: argparse with optional command-line arguments

I'd like to implement arguments parsing.
./app.py -E [optional arg] -T [optional arg]
The script requires at least one of parameters: -E or -T
What should I pass in parser.add_argument to have such functionality?
UPDATE
For some reason(s) the suggested solution with add_mutually_exclusive_group didn't work, when I added nargs='?' and const= attributes:
parser = argparse.ArgumentParser(prog='PROG')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-F', '--foo', nargs='?', const='/tmp')
group.add_argument('-B', '--bar', nargs='?', const='/tmp')
parser.parse_args([])
Running as script.py -F still throws error:
PROG: error: one of the arguments -F/--foo -B/--bar is required
However, the following workaround helped me:
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('-F', '--foo', nargs='?', const='/tmp')
parser.add_argument('-B', '--bar', nargs='?', const='/tmp')
args = parser.parse_args()
if (not args.foo and not args.bar) or (args.foo and args.bar):
print('Must specify one of -F/-B options.')
sys.exit(1)
if args.foo:
foo_func(arg.foo)
elif args.bar:
bar_func(args.bar)
...
You can make them both optional and check in your code if they are set.
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
parser.add_argument('--bar')
args = parser.parse_args()
if args.foo is None and args.bar is None:
parser.error("please specify at least one of --foo or --bar")
If you want only one of the two arguments to be present, see [add_mutually_exclusive_group] (https://docs.python.org/2/library/argparse.html#mutual-exclusion) with required=True
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> group = parser.add_mutually_exclusive_group(required=True)
>>> group.add_argument('--foo', action='store_true')
>>> group.add_argument('--bar', action='store_false')
>>> parser.parse_args([])
usage: PROG [-h] (--foo | --bar)
PROG: error: one of the arguments --foo --bar is required

python-daemon + argparse?

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.

How to handle missing arguments in argparse?

I need to do the following:
if there weren't argument's for script variable test == False
elif there was argument -t or --test variable test == True
else there were another arguments get them as string and pass to third code
Now I got an error:
# ./cli something
usage: cli [-t]
cli: error: unrecognized arguments: something
My code for first 2 steps:
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-t', '--test', action="store_true")
args = parser.parse_args()
if args.test is True:
intro = None
Within argparse you need that as a separate parameter, for example:
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-t', '--test', action="store_true")
parser.add_argument('params', nargs='+')
args = parser.parse_args()
if args.test is True:
intro = None
elif args.params:
pass # process the params here
else:
pass # no params whatsoever

argparse subparser help display

I can't seem to get the subparser help commands to properly display their help.
if I do command.py -h, I get the full help menu, but the subcommand/subparser help isn't showing.
command.py search -h prints the parent help
command.py import -h prints the parent help as well
When I typed command.py import -h, I wanted the help screen for the import subcommand and not the parent help.
I can't seem to figure out what is missing/broken.
parser = argparse.ArgumentParser(description = 'description', epilog='some epilog', add_help=True)
parser.add_argument('username', help='username')
parser.add_argument('apikey', help='API key')
parser.add_argument('--host', default=host, help='set the API host. Defaults to %s' % host)
parser.add_argument('--port', default=port, type=int, help='set the API port. Defaults to %s' % port)
parser.add_argument('--tls', dest='tls', action='store_true', help='set TLS (default value)')
parser.add_argument('--notls', dest='tls', action='store_false', help='turn off TLS')
parser.add_argument('--proxy', help='proxy url example: https://username:password#some.proxyserver.com:8080')
parser.add_argument('--type', default='https', choices=['http', 'https'], help='set proxy type')
parser.set_defaults(tls=True)
subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', help='sub-command help')
search_subparser = subparsers.add_parser('search', help='search help')
search_subparser.add_argument('--itype', choices=valid_intel_type, help='restrict by intelligence type')
search_subparser.add_argument('--severity', choices=valid_severity, help='restrict by severity')
search_subparser.add_argument('--classification', choices=valid_classification, help='restrict by classification')
search_subparser.add_argument('--status', choices=valid_status, help='parse by status')
search_subparser.add_argument('--output', choices=['json', 'csv'], default=output, help='output format. Defaults to %s' % output)
import_subparser = subparsers.add_parser('import', help='import help')
import_subparser.add_argument('file', help='file of indicators to import')
import_subparser.add_argument('itype', choices=valid_intel_type, help='intelligence type to import')
import_subparser.add_argument('--classification', default='private', choices=valid_classification, help='set classification level for import')
import_subparser.add_argument('--confidence', default=30, type=int, help='set cconfidence level for import')
import_subparser.add_argument('--severity', default='medium', choices=valid_severity, help='set severity level for import')
import_subparser.add_argument('--notes', default=None, nargs='*', help='set analyst notes for import file')
args = parser.parse_args()
The answer, for anyone else who uses multiple positional arguments, is that you have to supply the positional arguments along with the subcommand.
in my case:
command.py <username> <apikey> search -h
or:
command.py <username> <apikey> import -h

Categories