Refined Qt GUI example in the logging cookbook. (GH-15045) (GH-15046)

(cherry picked from commit 472eced677)
This commit is contained in:
Miss Islington (bot) 2019-07-30 23:58:46 -07:00 committed by Vinay Sajip
parent 8194a2050d
commit 9b9cac4e5d
1 changed files with 39 additions and 17 deletions

View File

@ -2760,8 +2760,8 @@ The following example shows how to log to a Qt GUI. This introduces a simple
``QtHandler`` class which takes a callable, which should be a slot in the main
thread that does GUI updates. A worker thread is also created to show how you
can log to the GUI from both the UI itself (via a button for manual logging)
as well as a worker thread doing work in the background (here, just random
short delays).
as well as a worker thread doing work in the background (here, just logging
messages at random levels with random short delays in between).
The worker thread is implemented using Qt's ``QThread`` class rather than the
:mod:`threading` module, as there are circumstances where one has to use
@ -2769,7 +2769,7 @@ The worker thread is implemented using Qt's ``QThread`` class rather than the
The code should work with recent releases of either ``PySide2`` or ``PyQt5``.
You should be able to adapt the approach to earlier versions of Qt. Please
refer to the comments in the code for more detailed information.
refer to the comments in the code snippet for more detailed information.
.. code-block:: python3
@ -2789,22 +2789,24 @@ refer to the comments in the code for more detailed information.
Signal = QtCore.pyqtSignal
Slot = QtCore.pyqtSlot
logger = logging.getLogger(__name__)
#
# Signals need to be contained in a QObject or subclass in order to be correctly
# initialized.
#
class Signaller(QtCore.QObject):
signal = Signal(str)
signal = Signal(str, logging.LogRecord)
#
# Output to a Qt GUI is only supposed to happen on the main thread. So, this
# handler is designed to take a slot function which is set up to run in the main
# thread. In this example, the function takes a single argument which is a
# formatted log message. You can attach a formatter instance which formats a
# LogRecord however you like, or change the slot function to take some other
# value derived from the LogRecord.
# thread. In this example, the function takes a string argument which is a
# formatted log message, and the log record which generated it. The formatted
# string is just a convenience - you could format a string for output any way
# you like in the slot function itself.
#
# You specify the slot function to do whatever GUI updates you want. The handler
# doesn't know or care about specific UI elements.
@ -2817,7 +2819,7 @@ refer to the comments in the code for more detailed information.
def emit(self, record):
s = self.format(record)
self.signaller.signal.emit(s)
self.signaller.signal.emit(s, record)
#
# This example uses QThreads, which means that the threads at the Python level
@ -2827,6 +2829,13 @@ refer to the comments in the code for more detailed information.
def ctname():
return QtCore.QThread.currentThread().objectName()
#
# Used to generate random levels for logging.
#
LEVELS = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
logging.CRITICAL)
#
# This worker class represents work that is done in a thread separate to the
# main thread. The way the thread is kicked off to do work is via a button press
@ -2851,7 +2860,8 @@ refer to the comments in the code for more detailed information.
while not QtCore.QThread.currentThread().isInterruptionRequested():
delay = 0.5 + random.random() * 2
time.sleep(delay)
logger.debug('Message after delay of %3.1f: %d', delay, i, extra=extra)
level = random.choice(LEVELS)
logger.log(level, 'Message after delay of %3.1f: %d', delay, i, extra=extra)
i += 1
#
@ -2864,10 +2874,18 @@ refer to the comments in the code for more detailed information.
#
class Window(QtWidgets.QWidget):
COLORS = {
logging.DEBUG: 'black',
logging.INFO: 'blue',
logging.WARNING: 'orange',
logging.ERROR: 'red',
logging.CRITICAL: 'purple',
}
def __init__(self, app):
super(Window, self).__init__()
self.app = app
self.textedit = te = QtWidgets.QTextEdit(self)
self.textedit = te = QtWidgets.QPlainTextEdit(self)
# Set whatever the default monospace font is for the platform
f = QtGui.QFont('nosuchfont')
f.setStyleHint(f.Monospace)
@ -2880,7 +2898,7 @@ refer to the comments in the code for more detailed information.
self.handler = h = QtHandler(self.update_status)
# Remember to use qThreadName rather than threadName in the format string.
fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s'
formatter = logging.Formatter(f)
formatter = logging.Formatter(fs)
h.setFormatter(formatter)
logger.addHandler(h)
# Set up to terminate the QThread when we exit
@ -2932,14 +2950,17 @@ refer to the comments in the code for more detailed information.
# that's where the slots are set up
@Slot(str)
def update_status(self, status):
self.textedit.append(status)
def update_status(self, status, record):
color = self.COLORS.get(record.levelno, 'black')
s = '<pre><font color="%s">%s</font></pre>' % (color, status)
self.textedit.appendHtml(s)
@Slot()
def manual_update(self):
levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
logging.CRITICAL)
level = random.choice(levels)
# This function uses the formatted message passed in, but also uses
# information from the record to format the message in an appropriate
# color according to its severity (level).
level = random.choice(LEVELS)
extra = {'qThreadName': ctname() }
logger.log(level, 'Manually logged!', extra=extra)
@ -2947,6 +2968,7 @@ refer to the comments in the code for more detailed information.
def clear_display(self):
self.textedit.clear()
def main():
QtCore.QThread.currentThread().setObjectName('MainThread')
logging.getLogger().setLevel(logging.DEBUG)