gitcmds: run "git diff" without taking a lock
[git-cola.git] / qtpy / __init__.py
blob12ca2e9e56df8ea74e7a7ce6187b9e4c5b352927
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)
8 """
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
18 variable.
20 PyQt5
21 =====
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)
28 PySide2
29 ======
31 Set the QT_API environment variable to 'pyside2' before importing other
32 packages::
34 >>> import os
35 >>> os.environ['QT_API'] = 'pyside2'
36 >>> from qtpy import QtGui, QtWidgets, QtCore
37 >>> print(QtWidgets.QWidget)
39 PyQt6
40 =====
42 >>> import os
43 >>> os.environ['QT_API'] = 'pyqt6'
44 >>> from qtpy import QtGui, QtWidgets, QtCore
45 >>> print(QtWidgets.QWidget)
47 PySide6
48 =======
50 >>> import os
51 >>> os.environ['QT_API'] = 'pyside6'
52 >>> from qtpy import QtGui, QtWidgets, QtCore
53 >>> print(QtWidgets.QWidget)
55 """
57 import os
58 import platform
59 import sys
60 import warnings
62 # Version of QtPy
63 __version__ = '2.3.0'
66 class PythonQtError(RuntimeError):
67 """Error raised if no bindings could be selected."""
70 class PythonQtWarning(Warning):
71 """Warning if some features are not implemented in a binding."""
74 class PythonQtValueError(ValueError):
75 """Error raised if an invalid QT_API is specified."""
78 class QtBindingsNotFoundError(PythonQtError):
79 """Error raised if no bindings could be selected."""
80 _msg = 'No Qt bindings could be found'
82 def __init__(self):
83 super().__init__(self._msg)
86 class QtModuleNotFoundError(ModuleNotFoundError, PythonQtError):
87 """Raised when a Python Qt binding submodule is not installed/supported."""
88 _msg = 'The {name} module was not found.'
89 _msg_binding = '{binding}'
90 _msg_extra = ''
92 def __init__(self, *, name, msg=None, **msg_kwargs):
93 global API_NAME
94 binding = self._msg_binding.format(binding=API_NAME)
95 msg = msg or f'{self._msg} {self._msg_extra}'.strip()
96 msg = msg.format(name=name, binding=binding, **msg_kwargs)
97 super().__init__(msg, name=name)
100 class QtModuleNotInOSError(QtModuleNotFoundError):
101 """Raised when a module is not supported on the current operating system."""
102 _msg = '{name} does not exist on this operating system.'
105 class QtModuleNotInQtVersionError(QtModuleNotFoundError):
106 """Raised when a module is not implemented in the current Qt version."""
107 _msg = '{name} does not exist in {version}.'
109 def __init__(self, *, name, msg=None, **msg_kwargs):
110 global QT5, QT6
111 version = 'Qt5' if QT5 else 'Qt6'
112 super().__init__(name=name, version=version)
115 class QtBindingMissingModuleError(QtModuleNotFoundError):
116 """Raised when a module is not supported by a given binding."""
117 _msg_extra = 'It is not currently implemented in {binding}.'
120 class QtModuleNotInstalledError(QtModuleNotFoundError):
121 """Raise when a module is supported by the binding, but not installed."""
122 _msg_extra = 'It must be installed separately'
124 def __init__(self, *, missing_package=None, **superclass_kwargs):
125 self.missing_package = missing_package
126 if missing_package is not None:
127 self._msg_extra += ' as {missing_package}.'
128 super().__init__(missing_package=missing_package, **superclass_kwargs)
131 # Qt API environment variable name
132 QT_API = 'QT_API'
134 # Names of the expected PyQt5 api
135 PYQT5_API = ['pyqt5']
137 PYQT6_API = ['pyqt6']
139 # Names of the expected PySide2 api
140 PYSIDE2_API = ['pyside2']
142 # Names of the expected PySide6 api
143 PYSIDE6_API = ['pyside6']
145 # Minimum supported versions of Qt and the bindings
146 QT5_VERSION_MIN = PYQT5_VERSION_MIN = '5.9.0'
147 PYSIDE2_VERSION_MIN = '5.12.0'
148 QT6_VERSION_MIN = PYQT6_VERSION_MIN = PYSIDE6_VERSION_MIN = '6.2.0'
150 QT_VERSION_MIN = QT5_VERSION_MIN
151 PYQT_VERSION_MIN = PYQT5_VERSION_MIN
152 PYSIDE_VERSION_MIN = PYSIDE2_VERSION_MIN
154 # Detecting if a binding was specified by the user
155 binding_specified = QT_API in os.environ
157 API_NAMES = {'pyqt5': 'PyQt5', 'pyside2': 'PySide2',
158 'pyqt6': 'PyQt6', 'pyside6': 'PySide6'}
159 API = os.environ.get(QT_API, 'pyqt5').lower()
160 initial_api = API
161 if API not in API_NAMES:
162 raise PythonQtValueError(
163 f'Specified QT_API={repr(QT_API.lower())} is not in valid options: '
164 f'{API_NAMES}')
166 is_old_pyqt = is_pyqt46 = False
167 QT5 = PYQT5 = True
168 QT4 = QT6 = PYQT4 = PYQT6 = PYSIDE = PYSIDE2 = PYSIDE6 = False
170 PYQT_VERSION = None
171 PYSIDE_VERSION = None
172 QT_VERSION = None
174 # Unless `FORCE_QT_API` is set, use previously imported Qt Python bindings
175 if not os.environ.get('FORCE_QT_API'):
176 if 'PyQt5' in sys.modules:
177 API = initial_api if initial_api in PYQT5_API else 'pyqt5'
178 elif 'PySide2' in sys.modules:
179 API = initial_api if initial_api in PYSIDE2_API else 'pyside2'
180 elif 'PyQt6' in sys.modules:
181 API = initial_api if initial_api in PYQT6_API else 'pyqt6'
182 elif 'PySide6' in sys.modules:
183 API = initial_api if initial_api in PYSIDE6_API else 'pyside6'
185 if API in PYQT5_API:
186 try:
187 from PyQt5.QtCore import PYQT_VERSION_STR as PYQT_VERSION # analysis:ignore
188 from PyQt5.QtCore import QT_VERSION_STR as QT_VERSION # analysis:ignore
190 QT5 = PYQT5 = True
191 except ImportError:
192 API = 'pyside2'
193 else:
194 os.environ[QT_API] = API
196 if API in PYSIDE2_API:
197 try:
198 from PySide2 import __version__ as PYSIDE_VERSION # analysis:ignore
199 from PySide2.QtCore import __version__ as QT_VERSION # analysis:ignore
201 PYQT5 = False
202 QT5 = PYSIDE2 = True
203 except ImportError:
204 API = 'pyqt6'
205 else:
206 os.environ[QT_API] = API
208 if API in PYQT6_API:
209 try:
210 from PyQt6.QtCore import PYQT_VERSION_STR as PYQT_VERSION # analysis:ignore
211 from PyQt6.QtCore import QT_VERSION_STR as QT_VERSION # analysis:ignore
213 QT5 = PYQT5 = False
214 QT6 = PYQT6 = True
216 except ImportError:
217 API = 'pyside6'
218 else:
219 os.environ[QT_API] = API
221 if API in PYSIDE6_API:
222 try:
223 from PySide6 import __version__ as PYSIDE_VERSION # analysis:ignore
224 from PySide6.QtCore import __version__ as QT_VERSION # analysis:ignore
226 QT5 = PYQT5 = False
227 QT6 = PYSIDE6 = True
229 except ImportError:
230 raise QtBindingsNotFoundError()
231 else:
232 os.environ[QT_API] = API
235 # If a correct API name is passed to QT_API and it could not be found,
236 # switches to another and informs through the warning
237 if API != initial_api and binding_specified:
238 warnings.warn('Selected binding "{}" could not be found, '
239 'using "{}"'.format(initial_api, API), RuntimeWarning)
242 # Set display name of the Qt API
243 API_NAME = API_NAMES[API]
245 try:
246 # QtDataVisualization backward compatibility (QtDataVisualization vs. QtDatavisualization)
247 # Only available for Qt5 bindings > 5.9 on Windows
248 from . import QtDataVisualization as QtDatavisualization # analysis:ignore
249 except (ImportError, PythonQtError):
250 pass