pythonqtpysidemypypyside6

Type Hinting and Type Checking for IntEnum custom types


Qt has several IntEnum's that support custom , user-specified types or roles. A few examples are:

In both cases, a user type/role is created by choosing an integer >= the User type/role

myType = QtCore.QEvent.Type.User + 1

The problem is that all of the functions that deal with these type/roles expect an instance of the IntEnum, not an int, and mypy will report an error.

from PySide6.QtCore import QEvent

class MyEvent(QEvent):

    def __init__(self) -> None:
        super().__init__(QEvent.Type.User + 1)

Mypy error:

No overload variant of "__init__" of "QEvent" matches argument type "int"

Integrated type checking in VS code with Pylance gives a similar error:

No overloads for "__init__" match the provided arguments PylancereportCallIssue
QtCore.pyi(2756, 9): Overload 2 is the closest match
Argument of type "int" cannot be assigned to parameter "type" of type "Type" in function "__init__"
  "int" is not assignable to "Type" PylancereportArgumentType

What type hinting can I do from my end to satisfy mypy? Is this something that needs to be changed in Qt type hinting?


Solution

  • In PySide6/PyQt6, the type of a user-defined int enum member should be preserved by using the constructor of the enum:

    from PySide6.QtCore import QEvent
    
    class MyEvent(QEvent):
        def __init__(self) -> None:
            super().__init__(QEvent.Type(QEvent.Type.User + 1))
    

    Assuming the latest PySide6 stubs are installed, a file with the above contents will produce no errors when checked with the mypy command-line tool.

    NB: the implementation defines _missing_ to handle unknown members, and this works for all enums that subclass IntEnum, regardless of whether the values make any sense:

    >>> QEvent.Type.User
    <Type.User: 1000>
    >>> QEvent.Type(QEvent.Type.User + 1)
    <Type.1001: 1001>
    >>> QEvent.Type.MaxUser
    <Type.MaxUser: 65535>
    >>> QEvent.Type(QEvent.Type.MaxUser + 10)
    <Type.65545: 65545>
    >>>
    >>> QFrame.Shape.__members__
    mappingproxy({'NoFrame': <Shape.NoFrame: 0>, 'Box': <Shape.Box: 1>, 'Panel': <Shape.Panel: 2>, 'WinPanel': <Shape.WinPanel: 3>, 'HLine': <Shape.HLine: 4>, 'VLine': <Shape.VLine: 5>, 'StyledPanel': <Shape.StyledPanel: 6>})
    >>> QFrame.Shape(42)
    <Shape.42: 42>