Repository: pyinstaller/pyinstaller

Issue:
`TypeError: unsupported operand type(s) for |: 'KeyboardModifier' and 'Key'` with PySide 6.4.0.1 application
<!--
Welcome to the PyInstaller issue tracker! Before creating an issue, please heed the following:

1. This tracker should only be used to report bugs and request features / enhancements to PyInstaller
    - For questions and general support, use the discussions forum.
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
   the original discussion.
3. When making a bug report, make sure you provide all required information. The easier it is for
   maintainers to reproduce, the faster it'll be fixed.
-->

<!-- +++ ONLY TEXT +++ DO NOT POST IMAGES +++ -->

## Description of the issue

When running a frozen PySide6 application using PySide 6.4.0.1, the `|` operator between Qt.KeyboardModifier and Qt.Key stops working, for example:
```
Traceback (most recent call last):
  File "script.py", line 28, in <module>
    window = MainWindow()
  File "script.py", line 18, in __init__
    button_action.setShortcut(QKeySequence(Qt.AltModifier | Qt.Key_D))
TypeError: unsupported operand type(s) for |: 'KeyboardModifier' and 'Key'
[10109] Failed to execute script 'script' due to unhandled exception!
```

Note that the script used for this works just fine if invoked directly from a development environment, and it works both in a development and a frozen build if downgraded to 6.3.2.

Before filing this I made a repo to demonstrate the bug, but most of the info is included in this issue template already. https://github.com/twizmwazin/pyside-pyinstaller-unsupported-operand-bug

### Context information (for bug reports)

* Output of `pyinstaller --version`: ```5.6.2```
* Version of Python: Python 3.10.8
* Platform: Ubuntu 20.04 on WSL 2
* How you installed Python: Python 3.10.8 from source
* Did you also try this on another platform? Does it work there?
First discovered after upgrading to PySide 6.4.0.1 in Azure DevOps CI using Ubuntu 20.04 and MS's build of Python 3.10. Also reproduced on Windows 11 using Python 3.10.8 64-bit installed using the python.org installer.


* try the latest development version, using the following command:

```shell
pip install https://github.com/pyinstaller/pyinstaller/archive/develop.zip
```

* follow *all* the instructions in our "If Things Go Wrong" Guide
  (https://github.com/pyinstaller/pyinstaller/wiki/If-Things-Go-Wrong) and

### Make sure [everything is packaged correctly](https://github.com/pyinstaller/pyinstaller/wiki/How-to-Report-Bugs#make-sure-everything-is-packaged-correctly)

  * [x] start with clean installation
  * [x] use the latest development version
  * [x] Run your frozen program **from a command window (shell)** — instead of double-clicking on it
  * [x] Package your program in **--onedir mode**
  * [x] Package **without UPX**, say: use the option `--noupx` or set `upx=False` in your .spec-file
  * [x] Repackage you application in **verbose/debug mode**. For this, pass the option `--debug` to `pyi-makespec` or `pyinstaller` or use `EXE(..., debug=1, ...)` in your .spec file.


### A minimal example program which shows the error

```
#!/usr/bin/env python3

from PySide6.QtCore import Qt
from PySide6.QtGui import QAction, QKeySequence
from PySide6.QtWidgets import QApplication, QToolBar, QMainWindow


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My Awesome App")

        toolbar = QToolBar("My main toolbar")
        self.addToolBar(toolbar)

        button_action = QAction("Your button", self)
        button_action.setShortcut(QKeySequence(Qt.AltModifier | Qt.Key_D))
        button_action.triggered.connect(self.onMyToolBarButtonClick)
        toolbar.addAction(button_action)

    def onMyToolBarButtonClick(self, s):
        print("click", s)


if __name__ == "__main__":
    app = QApplication()
    window = MainWindow()
    window.show()
    app.exec()
```

### Stacktrace / full error message


```
Traceback (most recent call last):
  File "script.py", line 31, in <module>
    window = MainWindow()
  File "script.py", line 21, in __init__
    button_action.setShortcut(QKeySequence(Qt.AltModifier | Qt.Key_D))
TypeError: unsupported operand type(s) for |: 'KeyboardModifier' and 'Key'
```

Please also see <https://github.com/pyinstaller/pyinstaller/wiki/How-to-Report-Bugs>
for more about what would use to solve the issue.



Discussion:
Hmmm, indeed. The minimal reproducer is actually just trying to combine a key and modifier:
``` python
from PySide6 import QtCore

print(type(QtCore.Qt.AltModifier))
print(type(QtCore.Qt.Key_D))

res = QtCore.Qt.AltModifier | QtCore.Qt.Key_D
print(type(res))
``` 

Looks like the type has changed; under 6.3.1., the outputs are:
```
<class 'PySide6.QtCore.Qt.KeyboardModifier'>
<class 'PySide6.QtCore.Qt.Key'>
<class 'PySide6.QtCore.Qt.KeyboardModifiers'>
```
while under >= 6.4.0, they are:
``` 
<enum 'KeyboardModifier'>
<enum 'Key'>
<class 'PySide6.QtCore.QKeyCombination'>
```

And PySide6 6.4.0. [release blog](https://www.qt.io/blog/qt-for-python-release-6.4-is-finally-here) does mention changes to the enum system and "forgiveness mode".

Will dig deeper to find out why it works when unfrozen, but not when frozen.
We need to explicitly collect `PySide6.support.deprecated`.

So adding ` --hiddenimport PySide6.support.deprecated` to your PyInstaller command should do the trick until we update the hooks.

Resolving patch:
diff --git a/PyInstaller/hooks/hook-PySide6.py b/PyInstaller/hooks/hook-PySide6.py
--- a/PyInstaller/hooks/hook-PySide6.py
+++ b/PyInstaller/hooks/hook-PySide6.py
@@ -9,11 +9,17 @@
 # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
 #-----------------------------------------------------------------------------
 
+from PyInstaller.utils.hooks import is_module_satisfies
 from PyInstaller.utils.hooks.qt import get_qt_binaries, pyside6_library_info
 
 # Only proceed if PySide6 can be imported.
 if pyside6_library_info.version is not None:
     hiddenimports = ['shiboken6', 'inspect']
 
+    # Starting with PySide6 6.4.0, we need to collect PySide6.support.deprecated for | and & operators to work with
+    # Qt key and key modifiers enums. See #7249.
+    if is_module_satisfies("PySide6 >= 6.4.0"):
+        hiddenimports += ['PySide6.support.deprecated']
+
     # Collect required Qt binaries.
     binaries = get_qt_binaries(pyside6_library_info)


Test patch:
diff --git a/tests/functional/test_qt.py b/tests/functional/test_qt.py
--- a/tests/functional/test_qt.py
+++ b/tests/functional/test_qt.py
@@ -486,3 +486,15 @@ def test_Qt_QtMultimedia_with_true_property(pyi_builder, QtPyLib):
         app = QtCore.QCoreApplication()
         """.format(QtPyLib), **USE_WINDOWED_KWARG
     )
+
+
+# In PySide6 >= 6.4.0, we need to collect `PySide6.support.deprecated` module for logical operators between Qt key and
+# key modifier enums to work. See #7249.
+@requires('PySide6')
+def test_Qt_PySide6_key_enums(pyi_builder):
+    pyi_builder.test_source(
+        """
+        from PySide6 import QtCore
+        key = QtCore.Qt.AltModifier | QtCore.Qt.Key_D
+        """
+    )