|
22 | 22 | import time |
23 | 23 | from concurrent.futures import Future |
24 | 24 | from queue import Queue |
| 25 | +from typing import TYPE_CHECKING, Literal, cast, get_args |
25 | 26 |
|
26 | 27 | logger = logging.getLogger(__name__) |
27 | 28 |
|
28 | | -QtModule = None |
29 | | - |
30 | | -# If QT_API env variable is given, use that or fail trying |
31 | | -qtapi_env = os.getenv("QT_API", "").strip().lower() |
32 | | -if qtapi_env: |
33 | | - env_to_mod_map = { |
34 | | - "pyqt5": "PyQt5", |
35 | | - "pyqt6": "PyQt6", |
36 | | - "pyqt": "PyQt6", |
37 | | - "pyside6": "PySide6", |
38 | | - "pyside2": "PySide2", |
39 | | - "pyside": "PySide6", |
40 | | - } |
41 | | - if qtapi_env in env_to_mod_map: |
42 | | - QtModuleName = env_to_mod_map[qtapi_env] |
43 | | - else: |
44 | | - raise ImportError( |
45 | | - "QT_API environment variable set ({}) but not one of [{}].".format( |
46 | | - qtapi_env, ", ".join(env_to_mod_map.keys()) |
47 | | - ) |
48 | | - ) |
49 | | - |
50 | | - logger.info("Forcing use of {} as Qt Implementation".format(QtModuleName)) |
51 | | - QtModule = importlib.import_module(QtModuleName) |
| 29 | +# runtime preference order is the same as this literal |
| 30 | +QtFlavor = Literal["PyQt6", "PyQt5", "PySide6", "PySide2"] |
| 31 | +QT_ALL = cast(tuple[QtFlavor, ...], get_args(QtFlavor)) |
52 | 32 |
|
53 | | -# If a Qt lib is already imported, use that |
54 | | -if not QtModule: |
55 | | - for QtModuleName in ("PyQt5", "PyQt6", "PySide2", "PySide6"): |
56 | | - if QtModuleName in sys.modules: |
57 | | - QtModule = sys.modules[QtModuleName] |
58 | | - break |
59 | 33 |
|
60 | | -# Try importing qt libs |
61 | | -if not QtModule: |
62 | | - for QtModuleName in ("PyQt5", "PyQt6", "PySide2", "PySide6"): |
| 34 | +def _get_qt_flavor() -> QtFlavor: |
| 35 | + env = os.getenv("QT_API", "").strip().lower() |
| 36 | + # prioritize env var |
| 37 | + if env: |
| 38 | + lookup = {name.lower(): name for name in QT_ALL} |
63 | 39 | try: |
64 | | - QtModule = importlib.import_module(QtModuleName) |
| 40 | + name = lookup[env] |
| 41 | + except KeyError as err: |
| 42 | + raise ImportError( |
| 43 | + f"QT_API={env!r} is not one of {', '.join(QT_ALL)}" |
| 44 | + ) from err |
| 45 | + logger.info("Forcing use of %s as Qt implementation", name) |
| 46 | + return cast(QtFlavor, name) |
| 47 | + # if already imported, use it |
| 48 | + for name in QT_ALL: |
| 49 | + if name in sys.modules: |
| 50 | + return cast(QtFlavor, name) |
| 51 | + # use the first available on system |
| 52 | + for name in QT_ALL: |
| 53 | + try: |
| 54 | + importlib.import_module(name) |
| 55 | + return cast(QtFlavor, name) |
65 | 56 | except ImportError: |
66 | 57 | continue |
67 | | - else: |
68 | | - break |
69 | | - |
70 | | -if not QtModule: |
71 | 58 | raise ImportError("No Qt implementations found") |
72 | 59 |
|
73 | | -QtCore = importlib.import_module(QtModuleName + ".QtCore", package=QtModuleName) |
74 | | -QtGui = importlib.import_module(QtModuleName + ".QtGui", package=QtModuleName) |
75 | | - |
76 | | -if QtModuleName == "PyQt5": |
77 | | - from PyQt5 import QtWidgets |
78 | | - from PyQt5.QtCore import pyqtSlot as Slot |
79 | | - |
80 | | - QApplication = QtWidgets.QApplication |
81 | | - AllEvents = QtCore.QEventLoop.ProcessEventsFlags(0x00) |
82 | 60 |
|
83 | | -elif QtModuleName == "PyQt6": |
84 | | - from PyQt6 import QtWidgets |
85 | | - from PyQt6.QtCore import pyqtSlot as Slot |
| 61 | +if TYPE_CHECKING: |
| 62 | + from PySide6 import QtCore, QtWidgets |
| 63 | + from PySide6.QtCore import Slot |
86 | 64 |
|
87 | 65 | QApplication = QtWidgets.QApplication |
88 | 66 | AllEvents = QtCore.QEventLoop.ProcessEventsFlag(0x00) |
89 | | - |
90 | | -elif QtModuleName == "PySide2": |
91 | | - from PySide2 import QtWidgets |
92 | | - from PySide2.QtCore import Slot |
93 | | - |
| 67 | +else: |
| 68 | + qt_flavor = _get_qt_flavor() |
| 69 | + QtCore = importlib.import_module(f"{qt_flavor}.QtCore") |
| 70 | + QtWidgets = importlib.import_module(f"{qt_flavor}.QtWidgets") |
94 | 71 | QApplication = QtWidgets.QApplication |
95 | | - AllEvents = QtCore.QEventLoop.ProcessEventsFlags(0x00) |
96 | 72 |
|
97 | | -elif QtModuleName == "PySide6": |
98 | | - from PySide6 import QtWidgets |
99 | | - from PySide6.QtCore import Slot |
| 73 | + # PyQt uses pyqtSlot, PySide uses Slot |
| 74 | + Slot = getattr(QtCore, "pyqtSlot", getattr(QtCore, "Slot")) |
100 | 75 |
|
101 | | - QApplication = QtWidgets.QApplication |
102 | | - AllEvents = QtCore.QEventLoop.ProcessEventsFlags(0x00) |
| 76 | + # PyQt6 uses ProcessEventsFlags, others use ProcessEventsFlag |
| 77 | + PEF = getattr(QtCore.QEventLoop, "ProcessEventsFlags", None) or getattr( |
| 78 | + QtCore.QEventLoop, "ProcessEventsFlag" |
| 79 | + ) |
| 80 | + AllEvents = PEF(0x00) |
103 | 81 |
|
104 | 82 | from ._common import with_logger # noqa |
105 | 83 |
|
@@ -828,16 +806,17 @@ def __log_error(cls, *args, **kwds): |
828 | 806 | sys.stderr.write("{!r}, {!r}\n".format(args, kwds)) |
829 | 807 |
|
830 | 808 |
|
831 | | -from ._unix import _SelectorEventLoop # noqa |
832 | | - |
833 | | -QSelectorEventLoop = type("QSelectorEventLoop", (_QEventLoop, _SelectorEventLoop), {}) |
| 809 | +if sys.platform == "win32": |
| 810 | + from ._windows import _ProactorEventLoop # noqa: F401 |
834 | 811 |
|
835 | | -if os.name == "nt": |
836 | | - from ._windows import _ProactorEventLoop |
| 812 | + class QIOCPEventLoop(_QEventLoop, _ProactorEventLoop): ... |
837 | 813 |
|
838 | | - QIOCPEventLoop = type("QIOCPEventLoop", (_QEventLoop, _ProactorEventLoop), {}) |
839 | 814 | QEventLoop = QIOCPEventLoop |
840 | 815 | else: |
| 816 | + from ._unix import _SelectorEventLoop # noqa: F401 |
| 817 | + |
| 818 | + class QSelectorEventLoop(_QEventLoop, _SelectorEventLoop): ... |
| 819 | + |
841 | 820 | QEventLoop = QSelectorEventLoop |
842 | 821 |
|
843 | 822 |
|
|
0 commit comments