Hello I am starting with argparse. My goal is to build a CLI with main commands that accept arguments and redirect to the corresponding commands functions. Here is what I did so far:
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
command1_parser = subparsers.add_parser('command1')
command1_parser.set_defaults(func=command1)
command1_parser.add_argument('--name', dest='name')
command2_parser = subparsers.add_parser('command2')
command2_parser.set_defaults(func=command2)
command2_parser.add_argument('--frequency', dest='frequency')
args = parser.parse_args()
def command1():
# do something with args.name
def command2():
# do something with args.frequency
if __name__ == '__main__':
main()
When I do:
entrypoint command1 --name Hello
Or:
entrypoint command2 --frequency 10
It fails to catch the corresponding args. What I am doing wrong? Thanks!
Because you need to invoke the function manually by args.func(args):
import argparse
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
command1_parser = subparsers.add_parser('command1')
command1_parser.set_defaults(func=command1)
command1_parser.add_argument('--name', dest='name')
command2_parser = subparsers.add_parser('command2')
command2_parser.set_defaults(func=command2)
command2_parser.add_argument('--frequency', dest='frequency')
args = parser.parse_args()
args.func(args)
def command1(args):
print("command1: %s" % args.name)
def command2(args):
print("comamnd2: %s" % args.frequency)
if __name__ == '__main__':
main()
Related
I'm going to run the command line utility multiple times in parallel using Python.
I know that multithreading is better to use for I/O operations, multiprocessing - for CPU oriented operations.
But what should I use for parallel subprocess.run?
I also know that I can create a pool from the subprocess module, but how is it different from pools from the multiprocessing and threading modules? And why shouldn't I just put subprocess.run function into multiprocessing or threading pools?
Or maybe there are some criteria when it is better to put a utility run cmd into a pool of threads or processes?
(In my case, I'm going to run the "ffmpeg" utility)
In a situation like this, I tend to run subprocesses from a ThreadPoolExecutor, basically because it's easy.
Example (from here):
from datetime import datetime
from functools import partial
import argparse
import concurrent.futures as cf
import logging
import os
import subprocess as sp
import sys
__version__ = "2021.09.19"
def main():
"""
Entry point for dicom2jpg.
"""
args = setup()
if not args.fn:
logging.error("no files to process")
sys.exit(1)
if args.quality != 80:
logging.info(f"quality set to {args.quality}")
if args.level:
logging.info("applying level correction.")
convert_partial = partial(convert, quality=args.quality, level=args.level)
starttime = str(datetime.now())[:-7]
logging.info(f"started at {starttime}.")
with cf.ThreadPoolExecutor(max_workers=os.cpu_count()) as tp:
for infn, outfn, rv in tp.map(convert_partial, args.fn):
logging.info(f"finished conversion of {infn} to {outfn} (returned {rv})")
endtime = str(datetime.now())[:-7]
logging.info(f"completed at {endtime}.")
def setup():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--log",
default="warning",
choices=["debug", "info", "warning", "error"],
help="logging level (defaults to 'warning')",
)
parser.add_argument("-v", "--version", action="version", version=__version__)
parser.add_argument(
"-l",
"--level",
action="store_true",
default=False,
help="Correct color levels (default: no)",
)
parser.add_argument(
"-q", "--quality", type=int, default=80, help="JPEG quailty level (default: 80)"
)
parser.add_argument(
"fn", nargs="*", metavar="filename", help="DICOM files to process"
)
args = parser.parse_args(sys.argv[1:])
logging.basicConfig(
level=getattr(logging, args.log.upper(), None),
format="%(levelname)s: %(message)s",
)
logging.debug(f"command line arguments = {sys.argv}")
logging.debug(f"parsed arguments = {args}")
# Check for requisites
try:
sp.run(["convert"], stdout=sp.DEVNULL, stderr=sp.DEVNULL)
logging.info("found “convert”")
except FileNotFoundError:
logging.error("the program “convert” cannot be found")
sys.exit(1)
return args
def convert(filename, quality, level):
"""
Convert a DICOM file to a JPEG file.
Removing the blank areas from the Philips detector.
Arguments:
filename: name of the file to convert.
quality: JPEG quality to apply
level: Boolean to indicate whether level adustment should be done.
Returns:
Tuple of (input filename, output filename, convert return value)
"""
outname = filename.strip() + ".jpg"
size = "1574x2048"
args = [
"convert",
filename,
"-units",
"PixelsPerInch",
"-density",
"300",
"-depth",
"8",
"-crop",
size + "+232+0",
"-page",
size + "+0+0",
"-auto-gamma",
"-quality",
str(quality),
]
if level:
args += ["-level", "-35%,70%,0.5"]
args.append(outname)
cp = sp.run(args, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
return (filename, outname, cp.returncode)
if __name__ == "__main__":
main()
Alternatively, you can manage a bunch of subprocesses (in the form of Popen objects) directly, as shown below.
(This was older code, now modified for Python 3)
import os
import sys
import subprocess
from multiprocessing import cpu_count
from time import sleep
def checkfor(args):
"""Make sure that a program necessary for using this script is
available.
Arguments:
args -- string or list of strings of commands. A single string may
not contain spaces.
"""
if isinstance(args, str):
if " " in args:
raise ValueError("No spaces in single command allowed.")
args = [args]
try:
with open("/dev/null", "w") as bb:
subprocess.check_call(args, stdout=bb, stderr=bb)
except Exception:
print("Required program '{}' not found! exiting.".format(args[0]))
sys.exit(1)
def startconvert(fname):
"""Use the convert(1) program from the ImageMagick suite to convert the
image and crop it."""
size = "1574x2048"
args = [
"convert",
fname,
"-units",
"PixelsPerInch",
"-density",
"300",
"-crop",
size + "+232+0",
"-page",
size + "+0+0",
fname + ".png",
]
with open("/dev/null") as bb:
p = subprocess.Popen(args, stdout=bb, stderr=bb)
print("Start processing", fname)
return (fname, p)
def manageprocs(proclist):
"""Check a list of subprocesses for processes that have ended and
remove them from the list.
"""
for it in proclist:
fn, pr = it
result = pr.poll()
if result is not None:
proclist.remove(it)
if result == 0:
print("Finished processing", fn)
else:
s = "The conversion of {} exited with error code {}."
print(s.format(fn, result))
sleep(0.5)
def main(argv):
"""Main program.
Keyword arguments:
argv -- command line arguments
"""
if len(argv) == 1:
path, binary = os.path.split(argv[0])
print("Usage: {} [file ...]".format(binary))
sys.exit(0)
del argv[0] # delete the name of the script.
checkfor("convert")
procs = []
maxprocs = cpu_count()
for ifile in argv:
while len(procs) == maxprocs:
manageprocs(procs)
procs.append(startconvert(ifile))
while len(procs) > 0:
manageprocs(procs)
# This is the main program ##
if __name__ == "__main__":
main(sys.argv)
I am having a hard time passing the arguments as value for my script in python. Here's my code:
import request, json, sys
def main():
url = 'https://jsonplaceholder.typicode.com/posts'
r = requests.get(url)
data = json.loads(r.text)
if len(sys.argv) != 3:
print("Usage must equal [userId] [postId]")
exit()
for user in data:
if user['userId'] == sys.argv[1] and user['id'] == sys.argv[2]:
print('here i am')
print(user)
if __name__ == "__main__":
main()
When I run python -m test 1 1, nothing happens. But it does trigger when I don't have enough arguments or too many.
The problem is that command line arguments are strings and the data you seek are integers. You could convert arg[1] and arg[2] to integers or you could use the argparse module to build a more comprehensive command line parser.
import requests, json, sys, argparse
def main():
parser = argparse.ArgumentParser(description='Do all the things')
parser.add_argument('user_id', type=int,
help='the user id')
parser.add_argument('id', type=int,
help='the other id')
args = parser.parse_args()
url = 'https://jsonplaceholder.typicode.com/posts'
r = requests.get(url)
data = json.loads(r.text)
for user in data:
if user['userId'] == args.user_id and user['id'] == args.id:
print('here i am')
print(user)
if __name__ == "__main__":
main()
I have this function that is pinging a server to get its response. I want to to able to give the IP address used in the function to argument parser as a command line argument instead of hardcoding it or taking it as user input.
Currently the function looks like:
def get_sipresponse():
var = input("Enter Opensips IP: ")
p = sys.path.append("/home/asad.javed/")
response = sip.ping(var)
response.get('Status')
print(response)
return p, response
My main function where I am using argument parser looks like:
def main():
parser = ap.ArgumentParser()
parser.add_argument('-s', '--start', help='start script', action='store_true')
parser.add_argument('--getOSresponse', help='Opensips response', action='store_true')
args = parser.parse_args()
elif args.start
for k,v in reversed_dictionary.items():
print(v, "=", k);time.sleep(5);
proc = subprocess.call([k], shell=True);time.sleep(5);
if proc != 0:
if proc < 0:
print("Killed by signal!", -proc)
else:
print("\nReturn code: \n", proc)
else:
print("\nSuccess\n");get_sipresponse()
elif args.getOSresponse:
get_sipresponse()
In your main, under the setup for other arguments in the arg parser, add this line:
parser.add_argument('--ip')
Then change get_sipresponse() to this:
def get_sipresponse(ip):
p = sys.path.append("/home/asad.javed/")
response = sip.ping(ip)
response.get('Status')
print(response)
return p, response
Lastly from main, you can call get_sipresponse like this:
get_sipresponse(args.ip)
If you return a "dict" from your get_sipresponse():
def get_sipresponse():
var = input("Enter Opensips IP: ")
p = sys.path.append("/home/asad.javed/")
response = sip.ping(var)
response.get('Status')
print(response)
return {'ip': var ,
'path': p,
'resp': response}
Then you can use it in your main() function.
def main():
target_ip = get_sipresponse()['ip']
......
.....
...
I want to be able to achieve something like this:
'python program host add 192.168.1.1'
'python program host remove 192.168.1.1'
I don't know how to properly code 'add' or 'remove' subparser.
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
host_cmd = subparsers.add_parser('host')
host_cmd.set_defaults(func=host)
add_cmd.add_argument('add', dest='add')
I want to be able to read the variable that the user pass in (in this case, 192.168.1.1).
With current code you can create only host --add 192.168.1.1 but it is much simpler code.
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='parser')
host_cmd = subparsers.add_parser('host')
host_cmd.add_argument('--add')
host_cmd.add_argument('--remove')
args = parser.parse_args()
print(args)
if args.parser == 'host':
if args.add is not None:
print('add host:', args.add)
if args.remove is not None:
print('remove host:', args.remove)
You need subparser in subparser - host add 192.168.1.1
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='parser')
host_cmd = subparsers.add_parser('host')
host_subparsers = host_cmd.add_subparsers(dest='parser_host')
host_add_cmd = host_subparsers.add_parser('add')
host_add_cmd.add_argument('ip')
host_remove_cmd = host_subparsers.add_parser('remove')
host_remove_cmd.add_argument('ip')
args = parser.parse_args()
print(args)
if args.parser == 'host':
if args.parser_host == 'add':
print('add host:', args.ip)
elif args.parser_host == 'remove':
print('remove host:', args.ip)
EDIT: example for host add port 80 but there is conflict with host add 192.168.1.1 so I removed it
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='parser')
host_cmd = subparsers.add_parser('host')
host_subparsers = host_cmd.add_subparsers(dest='parser_host')
host_add_cmd = host_subparsers.add_parser('add')
#host_add_cmd.add_argument('ip')
add_subparsers = host_add_cmd.add_subparsers(dest='parser_add')
host_add_port_cmd = add_subparsers.add_parser('port')
host_add_port_cmd.add_argument('add_port')
host_remove_cmd = host_subparsers.add_parser('remove')
host_remove_cmd.add_argument('ip')
args = parser.parse_args()
print(args)
if args.parser == 'host':
if args.parser_host == 'add':
if args.parser_add == 'port':
print('add port', args.add_port)
elif args.parser_host == 'remove':
print('remove', args.ip)
I am parsing and checking command-lines parameters with usage.Options.
#!/usr/bin/python
from twisted.python import usage
import sys
class Options(usage.Options):
"""
Defines the default input parameters
"""
optParameters = [
["param", "p", 1, "Int Parameter", int],
]
if __name__ == "__main__":
options = Options()
try:
options.parseOptions()
except Exception, e:
print '%s: %s' % (sys.argv[0], e)
print '%s: Try --help for usage details.' % (sys.argv[0])
sys.exit(1)
else:
if options['param'] < 0 or options['param'] > 10:
print "param out of the range [0,10]"
sys.exit(1)
I don't know how to check that value of thi input param is a number. If a user accidentally inserts a letter he gets this:
Parameter type enforcement failed: invalid literal for int() with base
10: 'd'
Why don't use optparse?
from optparse import OptionParser
options, args = parser.parse_args()
def args():
parser = OptionParser(usage='usage: %prog [options]', version='%prog 1.0.0')
parser.add_option(.....)
return parser
Update:
You can have something like this. Add and replace everything you want:
class Main:
def __init__(self):
parser = self.get_arg()
self.set_arg(parser)
def set_arg(self, parser):
options, args = parser.parse_args()
if len(sys.argv) == 1:
print 'Error: Usage: python %s <options>' % sys.argv[0]
sys.exit()
input_file = options.input_file
flag = options.flag
def get_arg(self):
parser = OptionParser(usage='usage: %prog [options]', version='%prog 1.1.0')
parser.add_option('-i', '--input-file', action='store', type='string', dest='input_file', default=None,
help='Input file.')
parser.add_option('-f', '--flag', action='store_true', dest='flag', default=False,
help='A flag in your app')
return parser
if __name__ == '__main__':
Main()