Search code examples
pythonpyqtpyqt5pysidepyside2

Conflicting names between logging emit function and QT emit signal


I am trying to send a Qt signal every time the logging handler emit function is called. But I think MyLogHandler.emit and log.emit functions are conflicting.

from PySide2.QtCore import QObject, Signal
import logging

class MyLogHandler(logging.Handler, QObject):
    log = Signal(str)

    def emit(self, record):
        self.log.emit('send')

if __name__ == "__main__":
    logging.getLogger().addHandler(MyLogHandler())
    logging.warning('logging test')

Error:

TypeError: emit() takes 2 positional arguments but 3 were given

UPDATE:

I have tried to use composition (per @eyllanesc), but I still can't connect the signal to the QML file. I am not sure why I can't receive the signal in QML. It doesn't seem like it is emitting anything. What am I doing wrong?

from functools import cached_property
import logging
import sys

from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QCoreApplication, QObject, QTimer, Signal, QUrl

class Main(QObject):
    log = Signal(str)


class Log(object):
    def __init__(self):
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)s [%(levelname)s] %(message)s",
            handlers=[
                logging.FileHandler("debug.log", mode='w'),
                logging.StreamHandler(),
                MyLogHandler()
            ]
        )

class MyLogHandler(logging.Handler):
    @cached_property
    def main(self):
        return Main()

    def emit(self, record):
        msg = self.format(record)
        self.main.log.emit(msg)


if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    main = Main()

    Log()

    QTimer.singleShot(1000, lambda: logging.warning("logging test"))
    engine.rootContext().setContextProperty("main", main)
    engine.load(QUrl("Main3.qml"))
    app.exec_()

QML: Main3.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 500
    height: 500
    visible: true
    title: qsTr("MIST")

    Text {
        id: text
        anchors.fill: parent

    }

    Connections {
        target: main

        function onLog(msg) {
            text.text = msg
        }

    }
}

Solution

  • The problem is that both base classes have an emit() method that causes that collision. A workaround is not to use inheritance but composition:

    from functools import cached_property
    import logging
    
    from PySide2.QtCore import QCoreApplication, QObject, QTimer, Signal
    
    
    class Bridge(QObject):
        log = Signal(str)
    
    
    class MyLogHandler(logging.Handler):
        @cached_property
        def bridge(self):
            return Bridge()
    
        def emit(self, record):
            msg = self.format(record)
            self.bridge.log.emit(msg)
    
    
    if __name__ == "__main__":
        app = QCoreApplication()
    
        handler = MyLogHandler()
        handler.bridge.log.connect(print)
    
        logging.getLogger().addHandler(handler)
    
        QTimer.singleShot(1000, lambda: logging.warning("logging test"))
        QTimer.singleShot(2000, QCoreApplication.quit)
    
        app.exec_()
    

    Update:

    import logging
    import sys
    
    from PySide2.QtCore import QObject, QTimer, QUrl, Signal
    from PySide2.QtGui import QGuiApplication
    from PySide2.QtQml import QQmlApplicationEngine
    
    
    class Main(QObject):
        log = Signal(str)
    
    
    class QLogHandler(logging.Handler):
        def __init__(self, emitter):
            super().__init__()
            self._emitter = emitter
    
        @property
        def emitter(self):
            return self._emitter
    
        def emit(self, record):
            msg = self.format(record)
            self.emitter.log.emit(msg)
    
    
    def configure_logging(*, handlers):
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)s [%(levelname)s] %(message)s",
            handlers=handlers,
        )
    
    
    def main():
        app = QGuiApplication()
    
        main = Main()
    
        configure_logging(
            handlers=[
                logging.FileHandler("debug.log", mode="w"),
                logging.StreamHandler(),
                QLogHandler(main),
            ]
        )
    
        engine = QQmlApplicationEngine()
        engine.rootContext().setContextProperty("main", main)
        engine.load(QUrl("Main3.qml"))
    
        QTimer.singleShot(1000, lambda: logging.warning("logging test"))
    
        ret = app.exec_()
        sys.exit(ret)
    
    
    if __name__ == "__main__":
        main()