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