I'm trying to build an interactive menu using py-click. Basically just a structure that:
Lists the available commands from the current menu (using a click group)
Present user with a command that prompts for his selection (users enters a # / cmd name)
Invoke the command from within that selection menu
Those command could lead to either another menu or execute application code
Then return to main menu/previous menu as relevant once the code has run
My code:
import click
#click.group(invoke_without_command=True)
#click.pass_context
def main_group(ctx):
""" Lists all the submenu options available"""
cmds = main_group.list_commands(ctx)
click.echo(f"Available options:")
for idx, cmd_str in enumerate(cmds):
click.echo(f"{idx}:{cmd_str}")
click.echo(f"Now that you know all the options, let's make a selection:")
ctx.invoke(main_group.get_command(ctx, "selection"))
#main_group.command()
#click.option('--next_cmd', prompt='Next command:', help="Enter the number corresponding to the desired command")
#click.pass_context
def selection(ctx, next_cmd):
click.echo(f"You've selected {next_cmd}")
# check that selection is valid
# invoke the desired command
# return to parent previous command
#main_group.command()
def submenu_1():
click.echo('A submenu option ')
#main_group.command()
def submenu_2():
click.echo('Another option')
if __name__ == '__main__':
main_group()
However, the output from the above is:
Available options:
0:application-code
1:selection
2:submenu-1
3:submenu-2
Now that you know all the options, let's make a selection:
You've selected None
Process finished with exit code 0
Basically, the prompt from the selection command has no effect. But the selection command itself works, because if I run it directly:
if __name__ == '__main__':
# main_group()
selection()
then I am actually prompted for my selection. So... why is the prompt being ignored? Or is the basic premise behind my approach to building this the issue, e.g. Click isn't meant for that?
EDIT:
Going through a bunch of git repo that uses this library, including the examples they provide, I haven't been able to find any that build a structure somewhat similar to what I want. Basically the paradigm of building a click-application seems to be isolated command that perform action that modify the state of something, not so much a navigation through a menu offering different options. Not to say it's impossible, but it doesn't seem to be supported out of the box either.
No answers given to this question. I've spent some more time on this. I've made a little bit of progress on this but I am still not able to have something such as:
Main Menu
- Option 1
- Option 2
- Option 3
- Submenu 1
and then
Submenu 1
- Option 1
- Option 2
- Submenu 2
- Back to Main
etc. My conclusion is that click isn't the right tool to use for that. In fact, I'm not sure the alternative would necessarily make it much easier either (argparse, docopt, ...). Maybe to do that the best approach would be to just build a class structure yourself.
Or then, go with an approach that's closer to what the shell or docker use, don't bother with any menu navigation and just launch atomic commands that act on the state of the application.
Related
Is there a way to group multiple commands, each with their own different parameters under a single function.
At first glance, a MultiCommand or Group might seem like a natural way of doing what I'd like, i.e. have a single main command act as the Group (and pass the invoke_without_command=True flag) then nest auxiliary Commands beneath it as subcommands. However this doesn't quite have the behavior that I'd want, since I'd like all the options from all commands to be able to be specified without explicitly invoking a subcommand. Additionally, using a Group would also not display the help text of the subcommands without invoking the subcommand on the command line as well.
I guess what I'd ideally like to have is a way to group multiple commands together without the nesting inherent to Click's Group API.
Sorry if this question might be somewhat general. Any help, ideas or tips that can point me in the right direction would be much appreciated.
Here's an outline of what I'd like (file name: cli_test.py):
import click
#click.command()
#click.option('--db-file', type=click.File(mode='r'))
def db_reader(db_file):
click.echo(db_file)
#click.command()
#click.option('--xval', type=float)
#click.option('--yval', type=float)
def get_vals(xval, yval):
return xval, yval
#click.command()
#click.option('--absolutize/--no-absolutize')
def flagger(absolutize):
click.echo(absolutize)
#click.command()
def cli_runner():
db = db_reader.main(standalone_mode=False)
vals = flagger.main(standalone_mode=False)
flag = flagger.main(standalone_mode=False)
if __name__ == '__main__':
cli_runner()
Essentially, I'd like a single command that can be run on the CLI (cli_runner in the above example), which would be able to take the parameters of all Click commands called within it, and then dispatch the processing of them to the appropriate Command instance. However as it stands right now, if I were to invoke on the CLI: $ python cli_test.py --xval 4 I'd get the error Error: no such option: --xval. I have also tried playing around with the pass_context and then ctx.invoke() approach, but the same problem exists.
I suppose I could pass parameters from all contained commands into cli_runner, but that would defeat the purpose of what I want to do, which is to ultimately have 3-4 modular "subcommands", that can then be grouped together in different combinations into larger interfaces that serve slightly different use cases.
Is there a way to generate (and export) help documentation using click for all commands and subcommands?
For example,
cli --help all --destination help-docs.txt
would generate help for commands and subcommands following the
cli command subcommand
format and put them into the help-docs.txt file.
The only way I can think that I would accomplish this is to use
cli command subcommand --help
on every subcommand that I wanted to generate help for and cat the output to a file, but it would be nice if there where an easier way to accomplish this using Click --help functionality.
This code will do for Click 7, using mostly documented APIs. You'd basically call recursive_help somewhere, e.g. as a separate subcommand, and pass it your top-level group object.
def recursive_help(cmd, parent=None):
ctx = click.core.Context(cmd, info_name=cmd.name, parent=parent)
print(cmd.get_help(ctx))
print()
commands = getattr(cmd, 'commands', {})
for sub in commands.values():
recursive_help(sub, ctx)
Update 2019-10-05:
one way to use this, assuming cli is a click.group, would be:
#cli.command()
def dumphelp():
recursive_help(cli)
Since you are using click package, I know two cool solutions:
Is to use click-man to auto-generate Python click CLI man page.
Is to use md-click to auto-generate Python click CLI help in md file format.
The title isnt very accurate i think
Here are my script at the right
Screenshot
It's a bot to automatize some actions. Now i want to add some gui to it but i dont know how.
Like you see at the left, i have " import questions" but cuz of it when I launch the tkinter file, it automatically launches the questions without taking my openBtn code into account.
How can I add gui to each command of my questions.py?
You need to break your questions.py script into actual functions . Python will simply execute all actions in questions.py when the namespace is imported before it reaches the logic below the import statements in testkinter.py.
So in questions.py remove your while True: statements in favor of function definitions like:
def check_database(param):
database check logic here
Then link the functions defined in questions.py to Tkinter button actions in testkinter.py like this:
w = tkinter.Button( fenetre, command=check_database )
(I edited the whole question to be more clear)
Hello,
I have never had any affairs with Python GUI libraries. I know there are plenty and well documented, but as I need only one single snippet, I would dislike to dive deep into documentations to seek for a way how to do it. If I am going to write a GUI program, I surely would do that, but this is needed only as a few lines for my ad hoc script.
What would be the easiest and the most straightforward way for me (GUI noob) to write in Python following piece of code? Less lines = more happiness.
Grab a JPEG picture by filename.
Display it's thumbnail.
Below the thumbnail display a textfield so the user can type in a caption.
Wait until user hits ENTER key on his/her keyboard. In that case, close and return the input.
...or wait until user hits DELETE key. In that case, close and return an information about the decision (to delete the picture).
Dependencies or Linux-only solutions are okay. I need to run this on Xubuntu machine. Any code snippets, please? I believe this is a matter of 5 minutes for someone skilled in Python GUI field. I would need to study loads of library docs. Thank you!
Below is a minimal python script that more or less fits the spec.
It requires python2 and pyqt4 packages to be installed, and it won't work with python3 (although it could quite easily be adapted to do so if necessary).
If the user types in a valid caption and presses enter, the script will return with status code 0 and print the caption to stdout; otherwise, if the user enters an invalid caption (empty or whitespace only), or simply closes the dialog without doing anything, the script will return with status code 1 and print nothing.
example bash usage:
$ CAPTION=$(python imgviewer.py image.jpg)
$ [ $? -eq 0 ] && echo $CAPTION
imgviewer.py:
import sys, os
from PyQt4 import QtGui, QtCore
class Dialog(QtGui.QDialog):
def __init__(self, path):
QtGui.QDialog.__init__(self)
self.viewer = QtGui.QLabel(self)
self.viewer.setMinimumSize(QtCore.QSize(400, 400))
self.viewer.setScaledContents(True)
self.viewer.setPixmap(QtGui.QPixmap(path))
self.editor = QtGui.QLineEdit(self)
self.editor.returnPressed.connect(self.handleReturnPressed)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.viewer)
layout.addWidget(self.editor)
def handleReturnPressed(self):
if self.editor.text().simplified().isEmpty():
self.reject()
else:
self.accept()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
args = app.arguments()[1:]
if len(args) == 1:
dialog = Dialog(args[0])
if dialog.exec_() == QtGui.QDialog.Accepted:
print dialog.editor.text().simplified().toLocal8Bit().data()
sys.exit(0)
else:
print 'ERROR: wrong number of arguments'
sys.exit(1)
There are several good GUI libraries for Python. The "standard" library that comes built-in with python is tkinter:http://wiki.python.org/moin/TkInter. Some says that wxPython is much more powerful and straightforward: http://www.wxpython.org/.
I think that you can start with wxPython, they have many many tutorials and examples you can dig into (just run the DEMO).
They have an example called "ImageBrowser" which might be a very good starting point.
Regarding the communication between the different apps, you can use "pipes" and "redirections" to communicate. But if everything is written in python, I think this is the wrong way to go, you can show the image form within your python script and get the result internally.
I have a VBA toolbar that i have been working on. It has two buttons, and near the end of the process, it calls a python script.
What I want to do is depending on which of the two buttons is clicked, a certain part of the python script will run, so I want to pass a value that is linked to the button which is then sent to the python script and run.
How do I do this?
Thanks
You can pass command line options to the python script, just like you can with other command line programs. Depending on which button was pressed, pass different switches to your program.
In your case, it may be simplest just to pass in one value on the command line depending on the button that was pressed and pick this from the sys.argv variable:
import sys
def fooClicked():
# Stuff to do when Foo was clicked
def barClicked():
# Stuff to do when Bar was clicked
button = sys.argv[1]
if button == 'foo':
fooClicked()
elif button == 'bar':
barClicked()
(You could use a dict to look up methods but that may be too advanced, don't know how comfortable you are with Python).
So, if you call this script with python.exe H:\Report_v7.py foo the fooClicked function will be called.
If this is going to grow to more than just two buttons, I'd use the optparse module to define your options and run different code paths depending on the options chosen.
If you upgrade to Python 2.7, then use the new (better) argparse module instead.