Skip to content

Commit edc3556

Browse files
committed
Add TYPE_CHECKING for runtime Qt imports
1 parent 1ec3d80 commit edc3556

1 file changed

Lines changed: 46 additions & 67 deletions

File tree

src/qasync/__init__.py

Lines changed: 46 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -22,84 +22,62 @@
2222
import time
2323
from concurrent.futures import Future
2424
from queue import Queue
25+
from typing import TYPE_CHECKING, Literal, cast, get_args
2526

2627
logger = logging.getLogger(__name__)
2728

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))
5232

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
5933

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}
6339
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)
6556
except ImportError:
6657
continue
67-
else:
68-
break
69-
70-
if not QtModule:
7158
raise ImportError("No Qt implementations found")
7259

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)
8260

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
8664

8765
QApplication = QtWidgets.QApplication
8866
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")
9471
QApplication = QtWidgets.QApplication
95-
AllEvents = QtCore.QEventLoop.ProcessEventsFlags(0x00)
9672

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"))
10075

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)
10381

10482
from ._common import with_logger # noqa
10583

@@ -828,16 +806,17 @@ def __log_error(cls, *args, **kwds):
828806
sys.stderr.write("{!r}, {!r}\n".format(args, kwds))
829807

830808

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
834811

835-
if os.name == "nt":
836-
from ._windows import _ProactorEventLoop
812+
class QIOCPEventLoop(_QEventLoop, _ProactorEventLoop): ...
837813

838-
QIOCPEventLoop = type("QIOCPEventLoop", (_QEventLoop, _ProactorEventLoop), {})
839814
QEventLoop = QIOCPEventLoop
840815
else:
816+
from ._unix import _SelectorEventLoop # noqa: F401
817+
818+
class QSelectorEventLoop(_QEventLoop, _SelectorEventLoop): ...
819+
841820
QEventLoop = QSelectorEventLoop
842821

843822

0 commit comments

Comments
 (0)