Search code examples
pythonpython-3.xqtqmlpyside2

Use QEnum like a Python Enum


Let's say I have a python enum:

class UserState(Enum):
    OFFLINE = auto()
    ONLINE = auto()
    BUSY = auto()

I can access the different options with UserState.ONLINE, UserState.OFFLINE or UserState.BUSY. If I wanted to make this a QEnum so I can use it in QML, I'd need to wrap it inside a QObject like this:

class UserState(QObject):
    @QEnum
    class Options(Enum):
        OFFLINE = auto()
        ONLINE = auto()
        BUSY = auto()

In QML I can access this enum now the same way I'd access a normal python enum in python. However if I wanted to access this enum from python, I'd have to write UserState.Options.ONLINE.

How can I create an enum that will work in python as well as QML using the same syntax?

I have found a solution for this which I will post in the answers section. However it involves nested metaclasses which just doesn't look right. I think the optimal solution would be a class that derives from QObject as well as Enum to have all the functionality for every context.

If anyone can provide a version which works like that, I will make that the accepted answer. Otherwise you can tell me, why my solution actually is a good one.


Solution

  • Here is a class I have written which adds the support I'm asking for in the question:

    class CustomEnumMeta(type(Enum)):
        def __new__(cls, name, bases, attrs):
            # don't change type of base class
            if name == "CustomEnum":
                return super().__new__(cls, name, bases, attrs)
    
            original_attrs = attrs.copy()
            enum = super().__new__(cls, name, bases, attrs)
    
            class WrapperMeta(type(QObject)):
                def __new__(cls, wrapper_name, wrapper_bases, wrapper_attrs):
                    return super().__new__(cls, wrapper_name, wrapper_bases, {**original_attrs, **wrapper_attrs})
    
            class Wrapper(QObject, metaclass=WrapperMeta):
                QEnum(enum)
    
            Wrapper.__name__ = name
    
            return Wrapper
    
    
    class CustomEnum(Enum, metaclass=CustomEnumMeta):
        pass
    

    The CustomEnum class can be inherited just like a normal python enum:

    class UserState(CustomEnum):
        OFFLINE = auto()
        ONLINE = auto()
        BUSY = auto()
    

    Now you can use UserState the same way in python as in QML. This is a valid statement for both python and QML: UserState.ONLINE

    My implementation works by replacing the original class with a QObject, nesting the original class. And then copying all the attributes of the nested class to the outer class to make them accessible from python.