I would like to create a very simple terminal menu that uses files and directories so I tried the following:
if __name__ == '__main__':
p = argparse.ArgumentParser(NAME)
p.add_argument('-data:', '--input_data', default=sys.stdin)
p.add_argument('-labels:', '--targets', default=sys.stdin)
p.add_argument('-input_directory:', '--input', default=sys.stdin)
p.add_argument('-output_data:', '--output', default=sys.stdout)
opts = p.parse_args()
if not any([opts.input, opts.output, opts.targets,opts.input_data]):
p.print_usage()
quit()
print '\npress -h to see the help\n'
elif any([opts.targets,opts.input_data]):
print '\ncompleted action\n'
p.print_usage()
#Recibe (datos, etiquetas)
perform_stuff(opts.input_data, opts.targets)
quit()
#Here is the problem:
elif any([opts.input, opts.output]):
print '\ncompleted action\n'
retrive(opts.input)
write(opts.output)
Note that for simplicity I omited the perform_stuff(path/in/file, path/out/file), retrive(opts.input) and write(opts.output) implementation. The problem is that the only command that works is:
if not any([opts.input, opts.output, opts.targets,opts.input_data]):
p.print_usage()
quit()
print '\npress -h to see the help\n'
elif any([opts.targets,opts.input_data]):
print '\ncompleted action\n'
p.print_usage()
#Recibe (datos, etiquetas)
perform_stuff(opts.input_data, opts.targets)
quit()
On the other hand, when I try to run:
#Here is the problem:
elif any([opts.input, opts.output]):
print '\ncompleted action\n'
retrive(opts.input)
write(opts.output)
My simple program just freeze. I am new with argparse, I guess that the problem is correlated with the proper use of if/else statements. Therefore, How should I create the menu of my simple program?
Here I've cleaned up and condensed your code, with a focus on getting the if/else tree working.
In the parser I rewrote the short options as single characters. I also left the defaults as None. That makes if tests easier. (what should it do if opts.input is stdin and you test if not opts.input:?).
import argparse
def quit():
import sys
sys.exit(1)
def perform_stuff(*args):
print 'stuff', args
def retrive(*args):
print 'retrieve', args
if __name__ == '__main__':
p = argparse.ArgumentParser()
# single letter `-d` string; use default None
p.add_argument('-d', '--input_data')
p.add_argument('-t', '--targets')
p.add_argument('-i', '--input')
p.add_argument('-o', '--output')
opts = p.parse_args()
print 'opts', opts
if not any([opts.input, opts.output, opts.targets, opts.input_data]):
p.print_usage()
print 'press -h to see the help'
quit()
elif any([opts.targets,opts.input_data]):
perform_stuff(opts.input_data, opts.targets)
quit()
elif any([opts.input, opts.output]):
retrive(opts.input, opts.output)
else:
print 'else'
Sample runs:
1814:~/mypy$ python stack36877714.py
opts Namespace(input=None, input_data=None, output=None, targets=None)
usage: stack36877714.py [-h] [-d INPUT_DATA] [-t TARGETS] [-i INPUT]
[-o OUTPUT]
press -h to see the help
1815:~/mypy$ python stack36877714.py -d input_data
opts Namespace(input=None, input_data='input_data', output=None, targets=None)
stuff ('input_data', None)
1815:~/mypy$ python stack36877714.py -t targets
opts Namespace(input=None, input_data=None, output=None, targets='targets')
stuff (None, 'targets')
1815:~/mypy$ python stack36877714.py -i input
opts Namespace(input='input', input_data=None, output=None, targets=None)
retrieve ('input', None)
1816:~/mypy$ python stack36877714.py -o output
opts Namespace(input=None, input_data=None, output='output', targets=None)
retrieve (None, 'output')
Your functions could be written like:
def retrieve(in_arg, out_arg):
if in_arg is None:
data = sys.stdin.read()
# cannot open/close stdin
else:
with open(in_arg,'r') as f:
data = f.read()
if out_arg is None:
# write to sys.stdout
else:
# write to open(out_arg,'w')
Related
I am trying to write tests (using pytest) for a script, but I don't know how to create/pass arguments to main(), especially when it doesn't recieve arguments. and even if I change it to def main(args = None): then it'll initiate it on the first line.
tests.py
def test_main(capfd):
main()
out, err = capfd.readouterr()
assert out == "my expected output"
script.py
def init_parser():
parser = argparse.ArgumentParser(description="The script searches one or \
more named input files for lines \
containing a match to a regular \
expression pattern.")
parser.add_argument('regex', help='the regular expression.')
parser.add_argument('infile', nargs='*', type=argparse.FileType('r'), default=[sys.stdin],
help='the name of the file(s) to search.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-u', '--underscore', action='store_true', help='prints \
"^" under the matching text.')
group.add_argument('-c', '--color', action='store_true', help='highlights \
matching text.')
group.add_argument('-m', '--machine', action='store_true', help='generates \
machine readable output.')
return parser
def main():
args = init_parser().parse_args()
for file in args.infile:
for i, line in enumerate(iter(file.readline, '')):
for substring in re.finditer(args.regex, line):
if args.underscore:
underscore_output(file.name, i + 1, line[:-1],
substring.start(), substring.end())
elif args.color:
color_output(file.name, i + 1, line[:-1],
substring.group())
elif args.machine:
machine_output(file.name, i + 1, substring.start(),
substring.group())
else:
print_line(file.name, i + 1, line[:-1])```
In the test code, you would patch out sys.argv before calling main:
def test_main(monkeypatch):
monkeypatch.setattr("sys.argv", ["script.py", "test_regex", "test_infile"])
main()
# put your assertions here
parse_args can take an explicit None argument to tell it to parse sys.argv[1:]. Write your code so that it can be easily tested:
def main(args=None):
args = init_parser().parse_args(args)
...
In production use, you'll call main() to let it parse sys.argv. For tests, you'll pass a specific set of arguments.
def test_main(capfd):
main(['[a-z]*', 'foo.txt', '-u']) # For example
out, err = capfd.readouterr()
assert out == "my expected output"
I am working on a cli application using python. I have a set of arguments that are mutually exclusive and a set of arguments that must be there if one of those mutually exclusive arguments is passed.
I have got it working using brute force and lengthy if conditions, but I feel like there is a neat way to do this. Researching about that told me subparsers might be useful, however I am not able to correctly use subparsers at all.
The conditions I want are the following-
Main activities
+-----+--------+--------+--------+---------+
| get | create | delete | update | remove |
+-----+--------+--------+--------+---------+
These main activities are mutually exclusive.
If get is specified, then there should not be any more arguements.
If delete is specified then there should not be any more arguements.
If remove is specified then -r is mandatory.
If update is specified then -f mandatory, and-cd is optional.
If create is specified, then -d is mandatory, -m and -f are optional where -m and -f are mutually exclusive.
The brute force code is as follows -
import argparse
parser = argparse.ArgumentParser(description='Check args')
#get
parser.add_argument('-g', '--get', help='')
#create
parser.add_argument('-c', '--create', help='')
parser.add_argument('-d', '--display', help='')
parser.add_argument('-m', '--message', help='')
parser.add_argument('-f', '--file', help='')
#update
parser.add_argument('-u', '--update', help='')
parser.add_argument('-cd', '--changed', help='')
#delete
parser.add_argument('-del', '--delete', help='')
#remove
parser.add_argument('-rm', help='')
parser.add_argument('-r', '--remove', help='')
args = vars(parser.parse_args())
if args['get'] is not None:
if args['create'] is None and args['display'] is None and args['message'] is None and args['file'] is None and args['update'] is None and args['changed'] is None and args['delete'] is None and args['rm'] is None and args['remove'] is None:
print(args['get'])
else:
print('Dont mix get with others')
exit()
if args['create']:
if args['get'] is None and args['message'] is None and args['file'] is None and args['update'] is None and args['changed'] is None and args['delete'] is None and args['rm'] is None and args['remove'] is None:
print(args['create'])
else:
print('Dont mix create with others')
exit()
if args['display'] is None:
print('Missing display')
if args['update']:
if args['get'] is None and args['create'] is None and args['display'] is None and args['message'] is None and args['delete'] is None and args['rm'] is None and args['remove'] is None:
print(args['update'])
else:
print('Dont mix update with others')
exit()
if args['file'] is None:
print('Missing file')
if args['delete']:
if args['get'] is None and args['create'] is None and args['display'] is None and args['message'] is None and args['file'] is None and args['update'] is None and args['changed'] is None and args['rm'] is None and args['remove'] is None:
print(args['delete'])
else:
print('Dont mix delete with others')
exit()
if args['rm']:
if args['get'] is None and args['create'] is None and args['display'] is None and args['message'] is None and args['file'] is None and args['update'] is None and args['changed'] is None and args['delete'] is None:
print(args['rm'])
else:
print('Dont mix resource management with others')
exit()
if args['remove'] is None:
print('Missing remove')
Is there any way to make it more pythonic ?
EDIT 1:
Why have I not included code using subparser ?
Because so far I have understood, subparsers don't themselves take any values. So in my case I want to be able to execute the script like this -
prog -g xxyy
prog -c xxyy -d 'Hello World'
prog -u xxyy -f 'file.md' -cd 'Foo bar baz'
Where as using subparsers, they would become something like (which I don't want, correct me if I am wrong)-
prog get -g xxyy
prog create -c xxyy -d 'Hello World'
EDIT 2
I figured out how to have mutually exclusive arguements using add_mutually_exclusive_group
parser = argparse.ArgumentParser(description='Mutex')
group = parser.add_mutually_exclusive_group()
group.add_argument('-g')
group.add_argument('-c')
group.add_argument('-u')
group.add_argument('-del')
group.add_argument('-rm')
EDIT 3
I am not able to get the subparse work. The following code (working with a subset) throws the error error: invalid choice: 'world' (choose from '-m', '-f') if the command is $ python3 parse2.py -c hello -m world
parser = argparse.ArgumentParser(description='Mutex')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-g')
group.add_argument('-c')
subparser = parser.add_subparsers()
parser_a = subparser.add_parser('-m')
parser_b = subparser.add_parser('-f')
args = parser.parse_args()
print(args)
EDIT 4
I have almost solved my issue with the following -
import argparse
parser = argparse.ArgumentParser()
sp = parser.add_subparsers(dest='main') # , required=True) in Py3.7
sp.required = True # in py 3.6
p1 = sp.add_parser('get')
p1.add_argument('id')
p2 = sp.add_parser('update')
p2.add_argument('id')
p2.add_argument('-f', required=True)
p2.add_argument('-cd')
p3 = sp.add_parser('create')
p3.add_argument('name')
p3.add_argument('-d', required=True)
p3_mutex = p3.add_mutually_exclusive_group()
p3_mutex.add_argument('-f')
p3_mutex.add_argument('-m')
p4 = sp.add_parser('delete')
p4.add_argument('id')
p5 = sp.add_parser('remove')
p5.add_argument('id')
p5.add_argument('-r', required=True)
args = parser.parse_args()
print(args)
However I want to know if it is possible to add the add_mutually_exclusive_group for get, delete, create, update and remove or do I have to do that using if conditions?
A start of a subparser version:
import argparse
parser = argparse.ArgumentParser()
sp = parser.add_subparsers(dest='main') # , required=True) in Py3.7
sp.required = True # in py 3.6
p1 = sp.add_parser('get')
p1.add_argument('xxyy')
p2 = sp.add_parser('update')
p2.add_argument('xxyy')
p2.add_argument('-r', required=True)
p2.add_argument('--cd')
# and so forth
args = parser.parse_args()
print(args)
sample runs
0914:~/mypy$ python3 stack53307678.py -h
usage: stack53307678.py [-h] {get,update} ...
positional arguments:
{get,update}
optional arguments:
-h, --help show this help message and exit
0914:~/mypy$ python3 stack53307678.py get abc
Namespace(main='get', xxyy='abc')
0914:~/mypy$ python3 stack53307678.py update abc -r foo --cd x
Namespace(cd='x', main='update', r='foo', xxyy='abc')
I simplifying my problem here. I need to do this:
python test.py --arg1 '--no-route53'
I added the parser like this:
parser.add_argument("--arg1", nargs="?", type=str, help="option")
args = parser.parse_args()
I wanted to get the '--no-route53' as a whole string and use it later in my script. But I keep getting this error:
test.py: error: unrecognized arguments: --no-route53
How can I work around it?
UPDATE1: if i give extra space after '--no-route53', like this and it worked:
python test.py --arg1 '--no-route53 '
I had the exact same issue a while ago. I ended up pre-parsing the sys.argvlist:
def bug_workaround(argv):
# arg needs to be prepended by space in order to not be interpreted as
# an option
add_space = False
args = []
for arg in argv[1:]: # Skip argv[0] as that should not be passed to parse_args
if add_space:
arg = " " + arg
add_space = True if arg == "--args" else False
args.append(arg)
return args
parser = argparse.ArgumentParser(description='My progrm.')
.
.
.
parser.add_argument('--args', type=str,
help='Extra args (in "quotes")')
args = parser.parse_args(bug_workaround(sys.argv))
# Prune added whitespace (for bug workaround)
if args.args:
args.args = args.args[1:]
I'd like to use option parser to print the result of a computation to the command line. So far, I have
parser = OptionParser()
parser.add_option('-s','--some', help = "Print some of the Suspects")
parser.add_option('-a','--all',help = "Print all of the Suspects")
(opts,args) = parser.parse_args()
If the user passes -s, I would like the first 25 rows of a dataframe to be printed (I know how to do this). If -a is passed, I would like the entire dataframe to be printed. What do I have left to do?
from optparse import OptionParser
parser = OptionParser()
parser.add_option('-s','--some', help = "Print some of the Suspects")
parser.add_option('-a','--all',help = "Print all of the Suspects")
(opts,args) = parser.parse_args()
if opts.some:
print "some results"
if opts.all:
print "all results"
I am making a small program in Python (PyGTK) that prints out a calendar (Gregorian) for a year the user inputs.
Here is my code:
#!/usr/bin/env python
import pygtk, gtk, subprocess
pygtk.require("2.0")
class Base:
def printing(self, widget):
text = self.textbox.get_text()
printingit = "cal -y %s | lpr" % (text)
process = subprocess.Popen(printingit.split(), stdout=subprocess.PIPE)
output = process.communicate()[0]
def __init__(self):
self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.win.set_position(gtk.WIN_POS_CENTER)
self.win.set_size_request(350, 200)
self.win.set_resizable(False)
self.win.set_title("Calendar")
self.win.connect('destroy',lambda w: gtk.main_quit())
self.textbox = gtk.Entry()
self.textbox.set_size_request(70, 30)
self.lable = gtk.Label("Year:")
self.button = gtk.Button("Print")
self.button.set_size_request(60, 45)
self.button.connect("clicked", self.printing)
box = gtk.Fixed()
box.put(self.lable, 160, 25)
box.put(self.textbox, 140, 40)
box.put(self.button, 145, 100)
self.win.add(box)
self.win.show_all()
def main(self):
gtk.main()
if __name__ == "__main__":
base = Base()
base.main()
It's not working when actually printing the command cal -y %s | lpr % (text). I have made it so it replaces the textbox's text with the final command it should get and it changes to what I want it to be cal -y 2015 | lpr. I tried just putting that into terminal and it worked as usual, it's confusing me a lot!
I ran the program in terminal and this is the message I recieve when it tries to print:
Usage: cal [general options] [-hjy] [[month] year]
cal [general options] [-hj] [-m month] [year]
ncal [general options] [-bhJjpwySM] [-s country_code] [[month] year]
ncal [general options] [-bhJeoSM] [year]
General options: [-NC3] [-A months] [-B months]
For debug the highlighting: [-H yyyy-mm-dd] [-d yyyy-mm]
If anyone understands why this is happening I would be extremely grateful! Thank you in advance =D
Harry
If you want to use shell syntax (pipe) in your command, you need to pass the command as as string to the Popen constructor, not as list. And you must use shell=True:
output = subprocess.check_output(printingit, shell=True)
Without that, the executed command would be the same as:
cal '-y' 'text' '|' 'lpr'
But as you're getting part of the input from a text field, you shouldn't directly pass it tho a shell.
Alternatively, you can create the pipe yourself:
lpr = subprocess.Popen('lpr', stdin=subprocess.PIPE, stdout=subprocess.PIPE)
process = subprocess.Popen(['cal', '-y', text], stdout=lpr.stdin)
output = lpr.communicate()
process.wait()
By the way, instead of using subprocess to invoke cal you could use the calendar module. cal -y 2012 does the same as calendar.calendar(2014), so you could replace your code with:
cal = calendar.calendar(int(text))
process = subprocess.Popen(['lpr'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
output = process.communicate(cal) # cal.encode(locale.getpreferredencoding(False)) for python3