2 # Copyright © 2009- The Spyder Development Team
3 # Copyright © 2014-2015 Colin Duquesnoy
5 # Licensed under the terms of the MIT License
6 # (see LICENSE.txt for details)
9 **QtPy** is a shim over the various Python Qt bindings. It is used to write
10 Qt binding independent libraries or applications.
12 If one of the APIs has already been imported, then it will be used.
14 Otherwise, the shim will automatically select the first available API (PyQt5, PySide2,
15 PyQt6 and PySide6); in that case, you can force the use of one
16 specific bindings (e.g. if your application is using one specific bindings and
17 you need to use library that use QtPy) by setting up the ``QT_API`` environment
23 For PyQt5, you don't have to set anything as it will be used automatically::
25 >>> from qtpy import QtGui, QtWidgets, QtCore
26 >>> print(QtWidgets.QWidget)
31 Set the QT_API environment variable to 'pyside2' before importing other
35 >>> os.environ['QT_API'] = 'pyside2'
36 >>> from qtpy import QtGui, QtWidgets, QtCore
37 >>> print(QtWidgets.QWidget)
43 >>> os.environ['QT_API'] = 'pyqt6'
44 >>> from qtpy import QtGui, QtWidgets, QtCore
45 >>> print(QtWidgets.QWidget)
51 >>> os.environ['QT_API'] = 'pyside6'
52 >>> from qtpy import QtGui, QtWidgets, QtCore
53 >>> print(QtWidgets.QWidget)
67 class PythonQtError(RuntimeError):
68 """Generic error superclass for QtPy."""
71 class PythonQtWarning(RuntimeWarning):
72 """Warning class for QtPy."""
75 class PythonQtValueError(ValueError):
76 """Error raised if an invalid QT_API is specified."""
79 class QtBindingsNotFoundError(PythonQtError
, ImportError):
80 """Error raised if no bindings could be selected."""
82 _msg
= "No Qt bindings could be found"
85 super().__init
__(self
._msg
)
88 class QtModuleNotFoundError(ModuleNotFoundError
, PythonQtError
):
89 """Raised when a Python Qt binding submodule is not installed/supported."""
91 _msg
= "The {name} module was not found."
92 _msg_binding
= "{binding}"
95 def __init__(self
, *, name
, msg
=None, **msg_kwargs
):
97 binding
= self
._msg
_binding
.format(binding
=API_NAME
)
98 msg
= msg
or f
"{self._msg} {self._msg_extra}".strip()
99 msg
= msg
.format(name
=name
, binding
=binding
, **msg_kwargs
)
100 super().__init
__(msg
, name
=name
)
103 class QtModuleNotInOSError(QtModuleNotFoundError
):
104 """Raised when a module is not supported on the current operating system."""
106 _msg
= "{name} does not exist on this operating system."
109 class QtModuleNotInQtVersionError(QtModuleNotFoundError
):
110 """Raised when a module is not implemented in the current Qt version."""
112 _msg
= "{name} does not exist in {version}."
114 def __init__(self
, *, name
, msg
=None, **msg_kwargs
):
116 version
= "Qt5" if QT5
else "Qt6"
117 super().__init
__(name
=name
, version
=version
)
120 class QtBindingMissingModuleError(QtModuleNotFoundError
):
121 """Raised when a module is not supported by a given binding."""
123 _msg_extra
= "It is not currently implemented in {binding}."
126 class QtModuleNotInstalledError(QtModuleNotFoundError
):
127 """Raise when a module is supported by the binding, but not installed."""
129 _msg_extra
= "It must be installed separately"
131 def __init__(self
, *, missing_package
=None, **superclass_kwargs
):
132 self
.missing_package
= missing_package
133 if missing_package
is not None:
134 self
._msg
_extra
+= " as {missing_package}."
135 super().__init
__(missing_package
=missing_package
, **superclass_kwargs
)
138 # Qt API environment variable name
141 # Names of the expected PyQt5 api
142 PYQT5_API
= ["pyqt5"]
144 PYQT6_API
= ["pyqt6"]
146 # Names of the expected PySide2 api
147 PYSIDE2_API
= ["pyside2"]
149 # Names of the expected PySide6 api
150 PYSIDE6_API
= ["pyside6"]
152 # Minimum supported versions of Qt and the bindings
153 QT5_VERSION_MIN
= PYQT5_VERSION_MIN
= "5.9.0"
154 PYSIDE2_VERSION_MIN
= "5.12.0"
155 QT6_VERSION_MIN
= PYQT6_VERSION_MIN
= PYSIDE6_VERSION_MIN
= "6.2.0"
157 QT_VERSION_MIN
= QT5_VERSION_MIN
158 PYQT_VERSION_MIN
= PYQT5_VERSION_MIN
159 PYSIDE_VERSION_MIN
= PYSIDE2_VERSION_MIN
161 # Detecting if a binding was specified by the user
162 binding_specified
= QT_API
in os
.environ
166 "pyside2": "PySide2",
168 "pyside6": "PySide6",
170 API
= os
.environ
.get(QT_API
, "pyqt5").lower()
172 if API
not in API_NAMES
:
173 raise PythonQtValueError(
174 f
"Specified QT_API={QT_API.lower()!r} is not in valid options: "
178 is_old_pyqt
= is_pyqt46
= False
180 QT4
= QT6
= PYQT4
= PYQT6
= PYSIDE
= PYSIDE2
= PYSIDE6
= False
183 PYSIDE_VERSION
= None
187 def _parse_int(value
):
188 """Convert a value into an integer"""
196 """Parse a version string into a tuple of ints"""
197 return tuple(_parse_int(x
) for x
in version
.split('.'))
200 # Unless `FORCE_QT_API` is set, use previously imported Qt Python bindings
201 if not os
.environ
.get("FORCE_QT_API"):
202 if "PyQt5" in sys
.modules
:
203 API
= initial_api
if initial_api
in PYQT5_API
else "pyqt5"
204 elif "PySide2" in sys
.modules
:
205 API
= initial_api
if initial_api
in PYSIDE2_API
else "pyside2"
206 elif "PyQt6" in sys
.modules
:
207 API
= initial_api
if initial_api
in PYQT6_API
else "pyqt6"
208 elif "PySide6" in sys
.modules
:
209 API
= initial_api
if initial_api
in PYSIDE6_API
else "pyside6"
213 from PyQt5
.QtCore
import (
214 PYQT_VERSION_STR
as PYQT_VERSION
,
216 from PyQt5
.QtCore
import (
217 QT_VERSION_STR
as QT_VERSION
,
222 if sys
.platform
== "darwin":
223 macos_version
= parse(platform
.mac_ver()[0])
224 qt_ver
= parse(QT_VERSION
)
225 if macos_version
< parse("10.10") and qt_ver
>= parse("5.9"):
227 "Qt 5.9 or higher only works in "
228 "macOS 10.10 or higher. Your "
229 "program will fail in this "
232 elif macos_version
< parse("10.11") and qt_ver
>= parse("5.11"):
234 "Qt 5.11 or higher only works in "
235 "macOS 10.11 or higher. Your "
236 "program will fail in this "
245 os
.environ
[QT_API
] = API
247 if API
in PYSIDE2_API
:
249 from PySide2
import __version__
as PYSIDE_VERSION
# analysis:ignore
250 from PySide2
.QtCore
import __version__
as QT_VERSION
# analysis:ignore
255 if sys
.platform
== "darwin":
256 macos_version
= parse(platform
.mac_ver()[0])
257 qt_ver
= parse(QT_VERSION
)
258 if macos_version
< parse("10.11") and qt_ver
>= parse("5.11"):
260 "Qt 5.11 or higher only works in "
261 "macOS 10.11 or higher. Your "
262 "program will fail in this "
271 os
.environ
[QT_API
] = API
275 from PyQt6
.QtCore
import (
276 PYQT_VERSION_STR
as PYQT_VERSION
,
278 from PyQt6
.QtCore
import (
279 QT_VERSION_STR
as QT_VERSION
,
288 os
.environ
[QT_API
] = API
290 if API
in PYSIDE6_API
:
292 from PySide6
import __version__
as PYSIDE_VERSION
# analysis:ignore
293 from PySide6
.QtCore
import __version__
as QT_VERSION
# analysis:ignore
299 raise QtBindingsNotFoundError
from None
301 os
.environ
[QT_API
] = API
304 # If a correct API name is passed to QT_API and it could not be found,
305 # switches to another and informs through the warning
306 if initial_api
!= API
and binding_specified
:
308 f
"Selected binding {initial_api!r} could not be found; "
309 f
"falling back to {API!r}",
315 # Set display name of the Qt API
316 API_NAME
= API_NAMES
[API
]
318 with contextlib
.suppress(ImportError, PythonQtError
):
319 # QtDataVisualization backward compatibility (QtDataVisualization vs. QtDatavisualization)
320 # Only available for Qt5 bindings > 5.9 on Windows
321 from . import QtDataVisualization
as QtDatavisualization
# analysis:ignore
324 def _warn_old_minor_version(name
, old_version
, min_version
):
325 """Warn if using a Qt or binding version no longer supported by QtPy."""
327 f
"{name} version {old_version} is not supported by QtPy. "
328 "To ensure your application works correctly with QtPy, "
329 f
"please upgrade to {name} {min_version} or later."
331 warnings
.warn(warning_message
, PythonQtWarning
, stacklevel
=2)
334 # Warn if using an End of Life or unsupported Qt API/binding minor version
336 if QT5
and (parse(QT_VERSION
) < parse(QT5_VERSION_MIN
)):
337 _warn_old_minor_version("Qt5", QT_VERSION
, QT5_VERSION_MIN
)
338 elif QT6
and (parse(QT_VERSION
) < parse(QT6_VERSION_MIN
)):
339 _warn_old_minor_version("Qt6", QT_VERSION
, QT6_VERSION_MIN
)
342 if PYQT5
and (parse(PYQT_VERSION
) < parse(PYQT5_VERSION_MIN
)):
343 _warn_old_minor_version("PyQt5", PYQT_VERSION
, PYQT5_VERSION_MIN
)
344 elif PYQT6
and (parse(PYQT_VERSION
) < parse(PYQT6_VERSION_MIN
)):
345 _warn_old_minor_version("PyQt6", PYQT_VERSION
, PYQT6_VERSION_MIN
)
347 if PYSIDE2
and (parse(PYSIDE_VERSION
) < parse(PYSIDE2_VERSION_MIN
)):
348 _warn_old_minor_version("PySide2", PYSIDE_VERSION
, PYSIDE2_VERSION_MIN
)
349 elif PYSIDE6
and (parse(PYSIDE_VERSION
) < parse(PYSIDE6_VERSION_MIN
)):
350 _warn_old_minor_version("PySide6", PYSIDE_VERSION
, PYSIDE6_VERSION_MIN
)