##// END OF EJS Templates
fix for PySide2 cannot be activated since it is directly deactivated again in the else part
Martin Bergtholdt -
Show More
@@ -1,330 +1,330 b''
1 1 """
2 2 This module contains factory functions that attempt
3 3 to return Qt submodules from the various python Qt bindings.
4 4
5 5 It also protects against double-importing Qt with different
6 6 bindings, which is unstable and likely to crash
7 7
8 8 This is used primarily by qt and qt_for_kernel, and shouldn't
9 9 be accessed directly from the outside
10 10 """
11 11 import sys
12 12 import types
13 13 from functools import partial
14 14 from importlib import import_module
15 15
16 16 from IPython.utils.version import check_version
17 17
18 18 # Available APIs.
19 19 QT_API_PYQT = 'pyqt' # Force version 2
20 20 QT_API_PYQT5 = 'pyqt5'
21 21 QT_API_PYQTv1 = 'pyqtv1' # Force version 2
22 22 QT_API_PYQT_DEFAULT = 'pyqtdefault' # use system default for version 1 vs. 2
23 23 QT_API_PYSIDE = 'pyside'
24 24 QT_API_PYSIDE2 = 'pyside2'
25 25
26 26 api_to_module = {QT_API_PYSIDE2: 'PySide2',
27 27 QT_API_PYSIDE: 'PySide',
28 28 QT_API_PYQT: 'PyQt4',
29 29 QT_API_PYQTv1: 'PyQt4',
30 30 QT_API_PYQT5: 'PyQt5',
31 31 QT_API_PYQT_DEFAULT: 'PyQt4',
32 32 }
33 33
34 34
35 35 class ImportDenier(object):
36 36 """Import Hook that will guard against bad Qt imports
37 37 once IPython commits to a specific binding
38 38 """
39 39
40 40 def __init__(self):
41 41 self.__forbidden = set()
42 42
43 43 def forbid(self, module_name):
44 44 sys.modules.pop(module_name, None)
45 45 self.__forbidden.add(module_name)
46 46
47 47 def find_module(self, fullname, path=None):
48 48 if path:
49 49 return
50 50 if fullname in self.__forbidden:
51 51 return self
52 52
53 53 def load_module(self, fullname):
54 54 raise ImportError("""
55 55 Importing %s disabled by IPython, which has
56 56 already imported an Incompatible QT Binding: %s
57 57 """ % (fullname, loaded_api()))
58 58
59 59 ID = ImportDenier()
60 60 sys.meta_path.insert(0, ID)
61 61
62 62
63 63 def commit_api(api):
64 64 """Commit to a particular API, and trigger ImportErrors on subsequent
65 65 dangerous imports"""
66 66
67 67 if api == QT_API_PYSIDE2:
68 68 ID.forbid('PySide')
69 69 ID.forbid('PyQt4')
70 70 ID.forbid('PyQt5')
71 if api == QT_API_PYSIDE:
71 elif api == QT_API_PYSIDE:
72 72 ID.forbid('PySide2')
73 73 ID.forbid('PyQt4')
74 74 ID.forbid('PyQt5')
75 75 elif api == QT_API_PYQT5:
76 76 ID.forbid('PySide2')
77 77 ID.forbid('PySide')
78 78 ID.forbid('PyQt4')
79 79 else: # There are three other possibilities, all representing PyQt4
80 80 ID.forbid('PyQt5')
81 81 ID.forbid('PySide2')
82 82 ID.forbid('PySide')
83 83
84 84
85 85 def loaded_api():
86 86 """Return which API is loaded, if any
87 87
88 88 If this returns anything besides None,
89 89 importing any other Qt binding is unsafe.
90 90
91 91 Returns
92 92 -------
93 93 None, 'pyside2', 'pyside', 'pyqt', 'pyqt5', or 'pyqtv1'
94 94 """
95 95 if 'PyQt4.QtCore' in sys.modules:
96 96 if qtapi_version() == 2:
97 97 return QT_API_PYQT
98 98 else:
99 99 return QT_API_PYQTv1
100 100 elif 'PySide.QtCore' in sys.modules:
101 101 return QT_API_PYSIDE
102 102 elif 'PySide2.QtCore' in sys.modules:
103 103 return QT_API_PYSIDE2
104 104 elif 'PyQt5.QtCore' in sys.modules:
105 105 return QT_API_PYQT5
106 106 return None
107 107
108 108
109 109 def has_binding(api):
110 110 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
111 111
112 112 Parameters
113 113 ----------
114 114 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
115 115 Which module to check for
116 116
117 117 Returns
118 118 -------
119 119 True if the relevant module appears to be importable
120 120 """
121 121 module_name = api_to_module[api]
122 122 from importlib.util import find_spec
123 123
124 124 required = ['QtCore', 'QtGui', 'QtSvg']
125 125 if api in (QT_API_PYQT5, QT_API_PYSIDE2):
126 126 # QT5 requires QtWidgets too
127 127 required.append('QtWidgets')
128 128
129 129 for submod in required:
130 130 try:
131 131 spec = find_spec('%s.%s' % (module_name, submod))
132 132 except ImportError:
133 133 # Package (e.g. PyQt5) not found
134 134 return False
135 135 else:
136 136 if spec is None:
137 137 # Submodule (e.g. PyQt5.QtCore) not found
138 138 return False
139 139
140 140 if api == QT_API_PYSIDE:
141 141 # We can also safely check PySide version
142 142 import PySide
143 143 return check_version(PySide.__version__, '1.0.3')
144 144
145 145 return True
146 146
147 147
148 148 def qtapi_version():
149 149 """Return which QString API has been set, if any
150 150
151 151 Returns
152 152 -------
153 153 The QString API version (1 or 2), or None if not set
154 154 """
155 155 try:
156 156 import sip
157 157 except ImportError:
158 158 return
159 159 try:
160 160 return sip.getapi('QString')
161 161 except ValueError:
162 162 return
163 163
164 164
165 165 def can_import(api):
166 166 """Safely query whether an API is importable, without importing it"""
167 167 if not has_binding(api):
168 168 return False
169 169
170 170 current = loaded_api()
171 171 if api == QT_API_PYQT_DEFAULT:
172 172 return current in [QT_API_PYQT, QT_API_PYQTv1, None]
173 173 else:
174 174 return current in [api, None]
175 175
176 176
177 177 def import_pyqt4(version=2):
178 178 """
179 179 Import PyQt4
180 180
181 181 Parameters
182 182 ----------
183 183 version : 1, 2, or None
184 184 Which QString/QVariant API to use. Set to None to use the system
185 185 default
186 186
187 187 ImportErrors rasied within this function are non-recoverable
188 188 """
189 189 # The new-style string API (version=2) automatically
190 190 # converts QStrings to Unicode Python strings. Also, automatically unpacks
191 191 # QVariants to their underlying objects.
192 192 import sip
193 193
194 194 if version is not None:
195 195 sip.setapi('QString', version)
196 196 sip.setapi('QVariant', version)
197 197
198 198 from PyQt4 import QtGui, QtCore, QtSvg
199 199
200 200 if not check_version(QtCore.PYQT_VERSION_STR, '4.7'):
201 201 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
202 202 QtCore.PYQT_VERSION_STR)
203 203
204 204 # Alias PyQt-specific functions for PySide compatibility.
205 205 QtCore.Signal = QtCore.pyqtSignal
206 206 QtCore.Slot = QtCore.pyqtSlot
207 207
208 208 # query for the API version (in case version == None)
209 209 version = sip.getapi('QString')
210 210 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
211 211 return QtCore, QtGui, QtSvg, api
212 212
213 213
214 214 def import_pyqt5():
215 215 """
216 216 Import PyQt5
217 217
218 218 ImportErrors rasied within this function are non-recoverable
219 219 """
220 220 import sip
221 221
222 222 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
223 223
224 224 # Alias PyQt-specific functions for PySide compatibility.
225 225 QtCore.Signal = QtCore.pyqtSignal
226 226 QtCore.Slot = QtCore.pyqtSlot
227 227
228 228 # Join QtGui and QtWidgets for Qt4 compatibility.
229 229 QtGuiCompat = types.ModuleType('QtGuiCompat')
230 230 QtGuiCompat.__dict__.update(QtGui.__dict__)
231 231 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
232 232
233 233 api = QT_API_PYQT5
234 234 return QtCore, QtGuiCompat, QtSvg, api
235 235
236 236
237 237 def import_pyside():
238 238 """
239 239 Import PySide
240 240
241 241 ImportErrors raised within this function are non-recoverable
242 242 """
243 243 from PySide import QtGui, QtCore, QtSvg
244 244 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
245 245
246 246 def import_pyside2():
247 247 """
248 248 Import PySide2
249 249
250 250 ImportErrors raised within this function are non-recoverable
251 251 """
252 252 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
253 253
254 254 # Join QtGui and QtWidgets for Qt4 compatibility.
255 255 QtGuiCompat = types.ModuleType('QtGuiCompat')
256 256 QtGuiCompat.__dict__.update(QtGui.__dict__)
257 257 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
258 258 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
259 259
260 260 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
261 261
262 262
263 263 def load_qt(api_options):
264 264 """
265 265 Attempt to import Qt, given a preference list
266 266 of permissible bindings
267 267
268 268 It is safe to call this function multiple times.
269 269
270 270 Parameters
271 271 ----------
272 272 api_options: List of strings
273 273 The order of APIs to try. Valid items are 'pyside', 'pyside2',
274 274 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
275 275
276 276 Returns
277 277 -------
278 278
279 279 A tuple of QtCore, QtGui, QtSvg, QT_API
280 280 The first three are the Qt modules. The last is the
281 281 string indicating which module was loaded.
282 282
283 283 Raises
284 284 ------
285 285 ImportError, if it isn't possible to import any requested
286 286 bindings (either becaues they aren't installed, or because
287 287 an incompatible library has already been installed)
288 288 """
289 289 loaders = {
290 290 QT_API_PYSIDE2: import_pyside2,
291 291 QT_API_PYSIDE: import_pyside,
292 292 QT_API_PYQT: import_pyqt4,
293 293 QT_API_PYQT5: import_pyqt5,
294 294 QT_API_PYQTv1: partial(import_pyqt4, version=1),
295 295 QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None)
296 296 }
297 297
298 298 for api in api_options:
299 299
300 300 if api not in loaders:
301 301 raise RuntimeError(
302 302 "Invalid Qt API %r, valid values are: %s" %
303 303 (api, ", ".join(["%r" % k for k in loaders.keys()])))
304 304
305 305 if not can_import(api):
306 306 continue
307 307
308 308 #cannot safely recover from an ImportError during this
309 309 result = loaders[api]()
310 310 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
311 311 commit_api(api)
312 312 return result
313 313 else:
314 314 raise ImportError("""
315 315 Could not load requested Qt binding. Please ensure that
316 316 PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available,
317 317 and only one is imported per session.
318 318
319 319 Currently-imported Qt library: %r
320 320 PyQt4 available (requires QtCore, QtGui, QtSvg): %s
321 321 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
322 322 PySide >= 1.0.3 installed: %s
323 323 PySide2 installed: %s
324 324 Tried to load: %r
325 325 """ % (loaded_api(),
326 326 has_binding(QT_API_PYQT),
327 327 has_binding(QT_API_PYQT5),
328 328 has_binding(QT_API_PYSIDE),
329 329 has_binding(QT_API_PYSIDE2),
330 330 api_options))
General Comments 0
You need to be logged in to leave comments. Login now