Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Alternative to Q_CLASSINFO macro (PyQt5)



  • I need to use a macro from the Qt framework in a class header (before init). In the example below, I would like to replace "obhub" by a variable, but I couldn't find how to do it. I need this to make a module and reuse the code more easily. Is there a way to bypass Q_CLASSINFO?

    This is the working, static version:

    #!/usr/bin/python3
    from PyQt5 import QtCore, QtDBus
     
     
    class QDBusObject(QtCore.QObject):
        def __init__(self, parent):
            QtCore.QObject.__init__(self)
            self.__dbusAdaptor = QDBusServerAdapter(self, parent)
            self.start()
     
        def start(self):
            bus = QtDBus.QDBusConnection.sessionBus()
            bus.registerObject("/org/obhub/session", self)
            bus.registerService("org.obhub.session")
            return bus
     
     
    class QDBusServerAdapter(QtDBus.QDBusAbstractAdaptor):
        QtCore.Q_CLASSINFO("D-Bus Interface", "org.obhub.session")
        QtCore.Q_CLASSINFO('D-Bus Introspection',
        '  <interface name="org.obhub.session">\n'
        '    <method name="parse">\n'
        '      <arg direction="in" type="s" name="cmd"/>\n'
        '    </method>\n'
        '  </interface>\n')
     
        def __init__(self, server, parent):
            super().__init__(server)
            self.parent = parent
     
        @QtCore.pyqtSlot(str)
        def parse(self, cmd):
          pass
    

    Failed attempt 1, decorator:

    #!/usr/bin/python3
    from PyQt5 import QtCore, QtDBus
     
     
    class QDBusObject(QtCore.QObject):
        def __init__(self, parent):
            QtCore.QObject.__init__(self)
            self.__dbusAdaptor = QDBusServerAdapter(self, parent)
            self.start()
     
        def start(self):
            bus = QtDBus.QDBusConnection.sessionBus()
            bus.registerObject("/org/obhub/session", self)
            bus.registerService("org.obhub.session")
            return bus
     
     
    def qDbusHeader(func):
        def wrapper(*args, **kargs):
                QtCore.Q_CLASSINFO("D-Bus Interface", "org.obhub.session")
                QtCore.Q_CLASSINFO('D-Bus Introspection',
                '  <interface name="org.obhub.session">\n'
                '    <method name="parse">\n'
                '      <arg direction="in" type="s" name="cmd"/>\n'
                '    </method>\n'
                '  </interface>\n')
     
                func(*args, **kargs)
        return wrapper
     
     
    @qDbusHeader
    class QDBusServerAdapter(QtDBus.QDBusAbstractAdaptor):
        def __init__(self, server, parent):
            super().__init__(server)
            self.parent = parent
     
        @QtCore.pyqtSlot(str)
        def parse(self, cmd):
          pass
    

    Failed attempt 2, metaclass:

    #!/usr/bin/python3
    from PyQt5 import QtCore, QtDBus
     
     
    class QDBusObject(QtCore.QObject):
        def __init__(self, parent):
            QtCore.QObject.__init__(self)
            self.__dbusAdaptor = MetaClass(self, parent)
            self.start()
     
        def start(self):
            bus = QtDBus.QDBusConnection.sessionBus()
            bus.registerObject("/org/obhub/session", self)
            bus.registerService("org.obhub.session")
            return bus
     
     
    class QDBusServerAdapter(QtDBus.QDBusAbstractAdaptor):
        def __init__(self, server, parent):
            super().__init__(server)
            self.parent = parent
     
        @QtCore.pyqtSlot(str)
        def parse(self, cmd):
          pass
     
     
    class MetaClass(QDBusServerAdapter):
        def __init__(self, server, parent):
            QtCore.Q_CLASSINFO("D-Bus Interface", "org.obhub.session")
            QtCore.Q_CLASSINFO('D-Bus Introspection',
            '  <interface name="org.obhub.session">\n'
            '    <method name="parse">\n'
            '      <arg direction="in" type="s" name="cmd"/>\n'
            '    </method>\n'
            '  </interface>\n')
            QDBusServerAdapter.__init__(self, server, parent)
    

    Failed attempt #3, factory function:

    #!/usr/bin/python3
    from PyQt5 import QtCore, QtDBus
     
     
    class QDBusObject(QtCore.QObject):
        def __init__(self, parent):
            QtCore.QObject.__init__(self)
            self.__dbusAdaptor = factory_func("obhub", self, parent)
            self.start()
     
        def start(self):
            bus = QtDBus.QDBusConnection.sessionBus()
            bus.registerObject("/org/obhub/session", self)
            bus.registerService("org.obhub.session")
            return bus
     
    def factory_func(org_name, server, parent):
        class QDBusServerAdapter(QtDBus.QDBusAbstractAdaptor):
            QtCore.Q_CLASSINFO(f"D-Bus Interface", "org.{org_name}.session")
            QtCore.Q_CLASSINFO('D-Bus Introspection',
            f'  <interface name="org.{org_name}.session">\n'
            '    <method name="parse">\n'
            '      <arg direction="in" type="s" name="cmd"/>\n'
            '    </method>\n'
            '  </interface>\n')
     
            def __init__(self, server, parent):
                super().__init__(server)
                self.parent = parent
     
            @QtCore.pyqtSlot(str)
            def parse(self, cmd):
                pass
        return QDBusServerAdapter
    

  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Can you describe your use case with more details. It's not really clear what you want to achieve.



  • Thanks for your reply. I am using QtDBus as a command line interface. When the application is launched, it verify if a dbus already exist. If it does, it simply pass the command throught it and exit. Else, it create a new instance of the program. Therefore the app is opened only once and can receive commands system wide.

    I use the example below in many of my apps, just by replacing "foo" with the app name. Therefore I would like to use qdbus.py as a module without having to modify it's code manually every time. To do so I need to customize the content of Q_CLASSINFO dynamically, or to find an alternate way to apply the same settings on the class object.

    In the attempts above, no error occur, but the dbus object is not created with the appropriate header. So I thought that perhaps, there might be other ways to set "D-Bus Interface" and "D-Bus Introspection" properties, that would be more suitable for portable code.

    __init__.py

    #!/usr/bin/python3
    from PyQt5 import QtDBus
    import sys
    
    
    def main():
        # Look for the dbus object
        bus = QtDBus.QDBusConnection.sessionBus()
        interface = QtDBus.QDBusInterface("org.foo.session", "/org/foo/session", "org.foo.session", bus)
    
        # Pass the arguments to the existing bus
        cmd = "%".join(str(arg) for arg in sys.argv[1:])
        if interface.isValid():
            interface.call("parse", cmd)
            sys.exit(0)
    
        else:
            # Create a new instance
            import foo
            foo.main()
    
    if __name__ == '__main__':
        main()
    

    foo.py

    #!/usr/bin/python3
    import os
    import sys
    
    from PyQt5 import QtWidgets, QtCore, QtDBus
    
    import qdbus
    
    
    class Main(QtWidgets.QMainWindow):
        def __init__(self, parent):
            super().__init__()
            self.parent = parent
            self.bus = qdbus.QDBusObject(self)
            self.bus.cli.connect(self.parseCommands)
    
        def parseCommands(self, cmd):
            if "action" in cmd:
                for action in cmd["action"]:
                    print("exec:", action)
    
            if "echo" in cmd:
                print(cmd["echo"])
    
            if "quit" in cmd:
                self.parent.exit()
    
    
    def main():
        app = QtWidgets.QApplication([])
        app.setQuitOnLastWindowClosed(False)
        daemon = Main(app)
        sys.exit(app.exec_())
    

    qdbus.py

    #!/usr/bin/python3
    from PyQt5 import QtCore, QtDBus
    
    global name; name = "foo"
    
    
    class QDBusObject(QtCore.QObject):
        cli = QtCore.pyqtSignal(object)
    
        def __init__(self, parent):
            QtCore.QObject.__init__(self)
            self.__dbusAdaptor = QDBusServerAdapter(self)
            self.start()
    
        def start(self):
            bus = QtDBus.QDBusConnection.sessionBus()
            bus.registerObject(f"/org/{name}/session", self)
            bus.registerService(f"org.{name}.session")
            return bus
    
    
    class QDBusServerAdapter(QtDBus.QDBusAbstractAdaptor):
        QtCore.Q_CLASSINFO("D-Bus Interface", f"org.{name}.session")
        QtCore.Q_CLASSINFO("D-Bus Introspection",
        f'<interface name="org.{name}.session">\n'
        '  <method name="parse">\n'
        '    <arg direction="in" type="s" name="cmd"/>\n'
        '  </method>\n'
        '</interface>\n')
    
        def __init__(self, parent):
            super().__init__(parent)
            self.parent = parent
    
        @QtCore.pyqtSlot(str)
        def parse(self, cmd):
            # Serialize the string of commands
            if cmd:
                commands = {}
                current = ""
                for arg in cmd.split("%"):
                    if arg.startswith("-"):
                        current = arg.lstrip("-")
                    elif current:
                        commands[current].append(arg)
                    if current not in commands:
                        commands[current] = []
                self.parent.cli.emit(commands)
    

  • Lifetime Qt Champion

    I don't see any replacement of {name} anywhere in your code.


Log in to reply