Maintain compatibility with PyQt5 add-ons in PyQt6 builds (#1440)

* Alias PyQt5 to PyQt6 on PyQt6 builds

Restores basic compatibility with PyQt5 add-ons

* Register QtCore early to work around sip error

* Monkey-patch unscoped enums that are in use by add-ons back in

Enums whose namespace moved with PyQt6 were determined using the tooling in https://github.com/qutebrowser/qutebrowser/issues/5904

Relevant enums for the Anki add-on ecosystem were found by grepping through all AnkiWeb add-ons and a selection of GitHub-released add-ons.

* Add full Qt.Key namespace

Maintains compatibility with add-ons that allow specifying key bindings via Qt.Key enums

* Reintroduce PyQt6.Qt as an alias for QtCore.Qt

* Alias classes shifted from QtWidgets to QtGui

* Add missing enums

Adds ≈200 enums that were missed during the initial grep

* Map exec_ calls to exec

* Tweak section headers

* Fix QtWebEngineWidgets imports failing due to delayed import

Addesses: "QtWebEngineWidgets must be imported before a QCoreApplication instance is created"

* Register additional aliases for top-level Qt modules

Given how we have had to deal with side-effects when not registering other aliased imports ahead of time, it seems safer to also register the remaining few with sys.modules.

* Handle calls to deprecated PyQt resource API graciously

* Create QtWebEngineWidgets aliases for classes moved to QtWebEngineCore

* Alias QShortcut

* Restore QWebEnginePage.view()

* Alias sip to PyQt6.sip

* Alias QtCore.QRegExp to QtCore.QRegularExpression

* Restructure aqt.qt into package

Pre-requirement for aliasing the PyQt5.Qt namespace correctly.

Should hopefully also make it easier to keep an overview as Qt-compat-related modules were proliferating.

* Properly alias PyQt5.Qt

PyQt5.Qt used to serve as a common namespace for all Qt classes, not just QtCore.Qt.*

While this changes does not make all classes accessible via PyQt5.Qt, it does so for the most important Qt submodules, which should cover most add-on breakages.

* Simplify Qt resource system legacy handling

* Also alias PyQt6.Qt

Covers imports of the form `from PyQt5 import import Qt` (due to previous aliasing of PyQt5 to PyQt6)

* Add missing enums

Better approach to grepping through add-ons yielded additional hits

* Run formatters

* Satisfy pylint
This commit is contained in:
Aristotelis 2021-10-28 11:57:42 +02:00 committed by GitHub
parent afc56a8398
commit 2d1c058106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1331 additions and 30 deletions

View File

@ -10,33 +10,10 @@ import traceback
from typing import Callable, Union
try:
from PyQt6 import sip
from PyQt6.QtCore import *
# conflicting Qt and qFuzzyCompare definitions require an ignore
from PyQt6.QtGui import * # type: ignore[misc]
from PyQt6.QtNetwork import QLocalServer, QLocalSocket, QNetworkProxy
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtWebEngineCore import *
from PyQt6.QtWebEngineWidgets import *
from PyQt6.QtWidgets import *
from . import compat # needs to be imported first
from .qt6 import *
except:
from PyQt5.QtCore import * # type: ignore
from PyQt5.QtGui import * # type: ignore
from PyQt5.QtNetwork import ( # type: ignore
QLocalServer,
QLocalSocket,
QNetworkProxy,
)
from PyQt5.QtWebChannel import QWebChannel # type: ignore
from PyQt5.QtWebEngineCore import * # type: ignore
from PyQt5.QtWebEngineWidgets import * # type: ignore
from PyQt5.QtWidgets import * # type: ignore
try:
from PyQt5 import sip # type: ignore
except ImportError:
import sip # type: ignore
from .qt5 import * # type: ignore
from anki.utils import isMac, isWin

1265
qt/aqt/qt/compat.py Normal file

File diff suppressed because it is too large Load Diff

22
qt/aqt/qt/qt5.py Normal file
View File

@ -0,0 +1,22 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# make sure not to optimize imports on this file
# pylint: skip-file
"""
PyQt5 imports
"""
from PyQt5.QtCore import * # type: ignore
from PyQt5.QtGui import * # type: ignore
from PyQt5.QtNetwork import QLocalServer, QLocalSocket, QNetworkProxy # type: ignore
from PyQt5.QtWebChannel import QWebChannel # type: ignore
from PyQt5.QtWebEngineCore import * # type: ignore
from PyQt5.QtWebEngineWidgets import * # type: ignore
from PyQt5.QtWidgets import * # type: ignore
try:
from PyQt5 import sip # type: ignore
except ImportError:
import sip # type: ignore

View File

@ -13,9 +13,9 @@ from typing import cast
import aqt
from .qt import *
from .sound import Recorder
from .utils import showWarning
from . import * # isort:skip
from ..sound import Recorder # isort:skip
from ..utils import showWarning # isort:skip
class QtAudioInputRecorder(Recorder):

17
qt/aqt/qt/qt5qt.py Normal file
View File

@ -0,0 +1,17 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# make sure not to optimize imports on this file
# pylint: skip-file
"""
Compatibility shim for PyQt5.Qt
"""
from typing import Any
from .qt5 import *
def __getattr__(name: str) -> Any:
return getattr(Qt, name) # type: ignore

20
qt/aqt/qt/qt6.py Normal file
View File

@ -0,0 +1,20 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# make sure not to optimize imports on this file
# pylint: disable=unused-import
"""
PyQt6 imports
"""
from PyQt6 import sip
from PyQt6.QtCore import *
# conflicting Qt and qFuzzyCompare definitions require an ignore
from PyQt6.QtGui import * # type: ignore[misc]
from PyQt6.QtNetwork import QLocalServer, QLocalSocket, QNetworkProxy
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtWebEngineCore import *
from PyQt6.QtWebEngineWidgets import *
from PyQt6.QtWidgets import *

View File

@ -671,7 +671,7 @@ class RecordDialog(QDialog):
namedtmp("rec.wav"), self.mw, self._parent
)
else:
from .qt5 import QtAudioInputRecorder as Qt5Recorder
from aqt.qt.qt5extra import QtAudioInputRecorder as Qt5Recorder
self._recorder = Qt5Recorder(namedtmp("rec.wav"), self.mw, self._parent)
self._recorder.start(self._start_timer)