I want to be able to bring up an interactive python terminal from my python application. Some, but not all, variables in my program needs to be exposed to the interpreter.
Currently I use a sub-classed and modified QPlainTextEdit
and route all "commands" there to eval
or exec
, and keep track of a separate namespace in a dict. However there got to be a more elegant and robust way! How?
Here is an example doing just what I want, but it is with IPython and pyGTK... http://ipython.scipy.org/moin/Cookbook/EmbeddingInGTK
Below is what I currently have. But there are so many corner cases that I probably missed some. It is very slow, try a large print loop... It got to be a simpler and less bug prone way, ...I hope!!
It is the def runCommand(self)
function that is the key to understanding my problem. I ideally don't want to improve it, I rather want to replace its content with something simpler and smarter.
The functionality of the console.updateNamespace({'myVar1' : app, 'myVar2' : 1234})
statement in "main" is also important.
import sys, os import traceback from PyQt4 import QtCore from PyQt4 import QtGui class Console(QtGui.QPlainTextEdit): def __init__(self, prompt='$> ', startup_message='', parent=None): QtGui.QPlainTextEdit.__init__(self, parent) self.prompt = prompt self.history = [] self.namespace = {} self.construct = [] self.setGeometry(50, 75, 600, 400) self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere) self.setUndoRedoEnabled(False) self.document().setDefaultFont(QtGui.QFont("monospace", 10, QtGui.QFont.Normal)) self.showMessage(startup_message) def updateNamespace(self, namespace): self.namespace.update(namespace) def showMessage(self, message): self.appendPlainText(message) self.newPrompt() def newPrompt(self): if self.construct: prompt = '.' * len(self.prompt) else: prompt = self.prompt self.appendPlainText(prompt) self.moveCursor(QtGui.QTextCursor.End) def getCommand(self): doc = self.document() curr_line = unicode(doc.findBlockByLineNumber(doc.lineCount() - 1).text()) curr_line = curr_line.rstrip() curr_line = curr_line[len(self.prompt):] return curr_line def setCommand(self, command): if self.getCommand() == command: return self.moveCursor(QtGui.QTextCursor.End) self.moveCursor(QtGui.QTextCursor.StartOfLine, QtGui.QTextCursor.KeepAnchor) for i in range(len(self.prompt)): self.moveCursor(QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor) self.textCursor().removeSelectedText() self.textCursor().insertText(command) self.moveCursor(QtGui.QTextCursor.End) def getConstruct(self, command): if self.construct: prev_command = self.construct[-1] self.construct.append(command) if not prev_command and not command: ret_val = '\n'.join(self.construct) self.construct = [] return ret_val else: return '' else: if command and command[-1] == (':'): self.construct.append(command) return '' else: return command def getHistory(self): return self.history def setHisory(self, history): self.history = history def addToHistory(self, command): if command and (not self.history or self.history[-1] != command): self.history.append(command) self.history_index = len(self.history) def getPrevHistoryEntry(self): if self.history: self.history_index = max(0, self.history_index - 1) return self.history[self.history_index] return '' def getNextHistoryEntry(self): if self.history: hist_len = len(self.history) self.history_index = min(hist_len, self.history_index + 1) if self.history_index < hist_len: return self.history[self.history_index] return '' def getCursorPosition(self): return self.textCursor().columnNumber() - len(self.prompt) def setCursorPosition(self, position): self.moveCursor(QtGui.QTextCursor.StartOfLine) for i in range(len(self.prompt) + position): self.moveCursor(QtGui.QTextCursor.Right) def runCommand(self): command = self.getCommand() self.addToHistory(command) command = self.getConstruct(command) if command: tmp_stdout = sys.stdout class stdoutProxy(): def __init__(self, write_func): self.write_func = write_func self.skip = False def write(self, text): if not self.skip: stripped_text = text.rstrip('\n') self.write_func(stripped_text) QtCore.QCoreApplication.processEvents() self.skip = not self.skip sys.stdout = stdoutProxy(self.appendPlainText) try: try: result = eval(command, self.namespace, self.namespace) if result != None: self.appendPlainText(repr(result)) except SyntaxError: exec command in self.namespace except SystemExit: self.close() except: traceback_lines = traceback.format_exc().split('\n') # Remove traceback mentioning this file, and a linebreak for i in (3,2,1,-1): traceback_lines.pop(i) self.appendPlainText('\n'.join(traceback_lines)) sys.stdout = tmp_stdout self.newPrompt() def keyPressEvent(self, event): if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): self.runCommand() return if event.key() == QtCore.Qt.Key_Home: self.setCursorPosition(0) return if event.key() == QtCore.Qt.Key_PageUp: return elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Backspace): if self.getCursorPosition() == 0: return elif event.key() == QtCore.Qt.Key_Up: self.setCommand(self.getPrevHistoryEntry()) return elif event.key() == QtCore.Qt.Key_Down: self.setCommand(self.getNextHistoryEntry()) return elif event.key() == QtCore.Qt.Key_D and event.modifiers() == QtCore.Qt.ControlModifier: self.close() super(Console, self).keyPressEvent(event) welcome_message = ''' --------------------------------------------------------------- Welcome to a primitive Python interpreter. --------------------------------------------------------------- ''' if __name__ == '__main__': app = QtGui.QApplication(sys.argv) console = Console(startup_message=welcome_message) console.updateNamespace({'myVar1' : app, 'myVar2' : 1234}) console.show(); sys.exit(app.exec_())
PyQt is a GUI widgets toolkit. It is a Python interface for Qt, one of the most powerful, and popular cross-platform GUI library. PyQt is a blend of Python programming language and the Qt library. This introductory tutorial will assist you in creating graphical applications with the help of PyQt.
Bit late I know, but I recommend the code.InteractiveConsole class: http://docs.python.org/py3k/library/code.html#code.InteractiveConsole
You might look into using threads to keep the UI responsive while printing big loops. This would also help keep your tracebacks clean.
Keeping variables in a dict is the way to go – it's what Python itself does internally. As far as exposing “some, but not all” of them, consider just exposing them all. Much easier. If you're concerned about security, beware that you can't reliably hide anything in Python.
As for the hideous cursor/text manipulation: take advantage of the fact that you have a GUI. With a terminal, you just have one “text box”, but in Qt, it might be more appropriate to have a log/result view and a separate command box.
The log view would display the entered commands and results in a read-only textbox.
The command textbox would allow you to enter a command cleanly.
This approach is used in some web frameworks – e.g. via WebError:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With